50#if !defined(CPU_ONLY) && !defined(GPU_ONLY)
67#if defined(HAVE_VIDEO_ENCODER)
70 const char*
formats_help =
"Output format (png, skp, mp4, or null)";
75#if defined(SK_BUILD_FOR_MAC) && defined(SK_FONTMGR_CORETEXT_AVAILABLE)
77#elif defined(SK_BUILD_FOR_ANDROID) && defined(SK_FONTMGR_ANDROID_AVAILABLE)
80#elif defined(SK_BUILD_FOR_UNIX) && defined(SK_FONTMGR_FONTCONFIG_AVAILABLE)
87static DEFINE_string2(writePath,
w,
nullptr,
"Output directory. Frames are names [0-9]{6}.png.");
92static DEFINE_double(fps, 0,
"Decode frames per second (default is animation native fps).");
96static DEFINE_int(threads, 0,
"Number of worker threads (0 -> cores count).");
104enum class OutputFormat {
112auto ms_since(std::chrono::steady_clock::time_point
start) {
113 const auto elapsed = std::chrono::steady_clock::now() -
start;
114 return std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
117std::unique_ptr<SkFILEWStream> make_file_stream(
size_t frame_index,
const char* extension) {
121 auto stream = std::make_unique<SkFILEWStream>(
path.c_str());
123 return stream->isValid() ? std::move(stream) : nullptr;
128 virtual ~FrameSink() =
default;
130 static std::unique_ptr<FrameSink>
Make(OutputFormat
fmt,
size_t frame_count);
134 virtual void finalize(
double fps) {}
137 FrameSink() =
default;
140 FrameSink(
const FrameSink&) =
delete;
141 FrameSink& operator=(
const FrameSink&) =
delete;
144class PNGSink final :
public FrameSink {
147 auto stream = make_file_stream(frame_index,
"png");
149 if (!
frame || !stream) {
165class NullSink final :
public FrameSink {
170#if defined(HAVE_VIDEO_ENCODER)
171class MP4Sink final :
public FrameSink {
173 explicit MP4Sink(
size_t frame_count) {
174 fFrames.resize(frame_count);
178 fFrames[frame_index].set_value(std::move(
frame));
181 void finalize(
double fps)
override {
184 fprintf(stderr,
"Invalid video stream configuration.\n");
187 std::vector<double> starved_ms;
188 starved_ms.reserve(fFrames.size());
190 for (
auto& frame_promise : fFrames) {
191 const auto start = std::chrono::steady_clock::now();
192 auto frame = frame_promise.get_future().get();
193 starved_ms.push_back(ms_since(
start));
195 if (!
frame)
continue;
202 auto mp4 =
encoder.endRecording();
205 .
write(mp4->data(), mp4->size());
209 double first = starved_ms[0];
210 std::sort(starved_ms.begin(), starved_ms.end());
211 double sum = std::accumulate(starved_ms.begin(), starved_ms.end(), 0);
212 printf(
"Encoder starved stats: "
213 "min %gms, med %gms, avg %gms, max %gms, sum %gms, first %gms (%s)\n",
214 starved_ms[0], starved_ms[fFrames.size()/2], sum/fFrames.size(), starved_ms.back(),
215 sum, first, first == starved_ms.back() ?
"ok" :
"BAD");
219 std::vector<std::promise<sk_sp<SkImage>>> fFrames;
223std::unique_ptr<FrameSink> FrameSink::Make(OutputFormat
fmt,
size_t frame_count) {
225 case OutputFormat::kPNG:
226 return std::make_unique<PNGSink>();
227 case OutputFormat::kSKP:
230 case OutputFormat::kNull:
231 return std::make_unique<NullSink>();
232 case OutputFormat::kMP4:
233#if defined(HAVE_VIDEO_ENCODER)
234 return std::make_unique<MP4Sink>(frame_count);
243class FrameGenerator {
245 virtual ~FrameGenerator() =
default;
247 static std::unique_ptr<FrameGenerator>
Make(FrameSink*, OutputFormat,
const SkMatrix&);
252 explicit FrameGenerator(FrameSink* sink) : fSink(sink) {}
257 FrameGenerator(
const FrameGenerator&) =
delete;
258 FrameGenerator& operator=(
const FrameGenerator&) =
delete;
261class CPUGenerator final :
public FrameGenerator {
264 static std::unique_ptr<FrameGenerator>
Make(FrameSink* sink,
const SkMatrix& matrix) {
268 static std::unique_ptr<FrameGenerator>
Make(FrameSink* sink,
const SkMatrix& matrix) {
271 SkDebugf(
"Could not allocate a %d x %d surface.\n", FLAGS_width, FLAGS_height);
275 return std::unique_ptr<FrameGenerator>(
new CPUGenerator(sink, std::move(
surface), matrix));
279 fSurface->getCanvas()->clear(kClearColor);
280 anim->
render(fSurface->getCanvas());
282 fSink->writeFrame(fSurface->makeImageSnapshot(), frame_index);
287 : FrameGenerator(sink)
290 fSurface->getCanvas()->concat(scale_matrix);
297class SKPGenerator final :
public FrameGenerator {
299#if defined(CPU_ONLY) || defined(GPU_ONLY)
300 static std::unique_ptr<FrameGenerator>
Make(FrameSink* sink,
const SkMatrix& matrix) {
304 static std::unique_ptr<FrameGenerator>
Make(FrameSink* sink,
const SkMatrix& scale_matrix) {
305 return std::unique_ptr<FrameGenerator>(
new SKPGenerator(sink, scale_matrix));
309 auto* canvas = fRecorder.beginRecording(FLAGS_width, FLAGS_height);
310 canvas->concat(fScaleMatrix);
313 auto frame = fRecorder.finishRecordingAsPicture();
314 auto stream = make_file_stream(frame_index,
"skp");
316 if (
frame && stream) {
327 SKPGenerator(FrameSink* sink,
const SkMatrix& scale_matrix)
328 : FrameGenerator(sink)
329 , fScaleMatrix(scale_matrix)
337class GPUGenerator final :
public FrameGenerator {
340 static std::unique_ptr<FrameGenerator>
Make(FrameSink* sink,
const SkMatrix& matrix) {
344 static std::unique_ptr<FrameGenerator>
Make(FrameSink* sink,
const SkMatrix& matrix) {
345 auto gpu_generator = std::unique_ptr<GPUGenerator>(
new GPUGenerator(sink, matrix));
347 return gpu_generator->isValid()
348 ? std::unique_ptr<FrameGenerator>(gpu_generator.release())
352 ~GPUGenerator()
override {
358 fSurface->getCanvas()->clear(kClearColor);
359 anim->
render(fSurface->getCanvas());
361 auto rec = std::make_unique<AsyncRec>(fSink, frame_index);
363 {0, 0, FLAGS_width, FLAGS_height},
364 SkSurface::RescaleGamma::kSrc,
365 SkImage::RescaleMode::kNearest,
366 AsyncCallback, rec.release());
372 GPUGenerator(FrameSink* sink,
const SkMatrix& matrix)
373 : FrameGenerator(sink)
375 fCtx = fFactory.getContextInfo(skgpu::ContextType::kGL).directContext();
383 fSurface->getCanvas()->concat(matrix);
385 fprintf(stderr,
"Could not initialize GL context.\n");
389 bool isValid()
const {
return !!fSurface; }
395 AsyncRec(FrameSink* sink,
size_t index) : sink(sink), index(index) {}
399 std::unique_ptr<const SkSurface::AsyncReadResult>
result) {
400 std::unique_ptr<const AsyncRec> rec(
reinterpret_cast<const AsyncRec*
>(ctx));
406 std::unique_ptr<const SkSurface::AsyncReadResult>
413 rec->sink->writeFrame(std::move(frame_image), rec->index);
423std::unique_ptr<FrameGenerator> FrameGenerator::Make(FrameSink* sink,
426 if (
fmt == OutputFormat::kSKP) {
427 return SKPGenerator::Make(sink, matrix);
431 ? GPUGenerator::Make(sink, matrix)
447 void report()
const {
448 SkDebugf(
"Animation loaded with %zu error%s, %zu warning%s.\n",
449 fErrors.size(), fErrors.size() == 1 ?
"" :
"s",
450 fWarnings.
size(), fWarnings.
size() == 1 ?
"" :
"s");
452 const auto&
show = [](
const LogEntry&
log,
const char prefix[]) {
454 if (!
log.fJSON.isEmpty())
459 for (
const auto& err : fErrors)
show(err,
" !! ");
460 for (
const auto& wrn : fWarnings)
show(wrn,
" ?? ");
464 std::vector<LogEntry> fErrors,
472int main(
int argc,
char** argv) {
477 if (FLAGS_input.isEmpty() || FLAGS_writePath.isEmpty()) {
478 SkDebugf(
"Missing required 'input' and 'writePath' args.\n");
483 if (0 == std::strcmp(FLAGS_format[0],
"png")) {
484 fmt = OutputFormat::kPNG;
485 }
else if (0 == std::strcmp(FLAGS_format[0],
"skp")) {
486 fmt = OutputFormat::kSKP;
487 }
else if (0 == std::strcmp(FLAGS_format[0],
"null")) {
488 fmt = OutputFormat::kNull;
489#if defined(HAVE_VIDEO_ENCODER)
490 }
else if (0 == std::strcmp(FLAGS_format[0],
"mp4")) {
491 fmt = OutputFormat::kMP4;
494 fprintf(stderr,
"Unknown format: %s\n", FLAGS_format[0]);
498 if (
fmt != OutputFormat::kMP4 && !
sk_mkdir(FLAGS_writePath[0])) {
507#if defined(SK_BUILD_FOR_MAC) && defined(SK_FONTMGR_CORETEXT_AVAILABLE)
509#elif defined(SK_BUILD_FOR_ANDROID) && defined(SK_FONTMGR_ANDROID_AVAILABLE)
511#elif defined(SK_BUILD_FOR_UNIX) && defined(SK_FONTMGR_FONTCONFIG_AVAILABLE)
518 auto logger = sk_make_sp<Logger>();
526 auto precomp_interceptor =
527 sk_make_sp<skottie_utils::ExternalAnimationPrecompInterceptor>(rp,
"__");
530 SkDebugf(
"Could not load %s.\n", FLAGS_input[0]);
542 .
make(
static_cast<const char*
>(data->data()), data->size());
544 SkDebugf(
"Could not parse animation: '%s'.\n", FLAGS_input[0]);
553 const auto t0 =
SkTPin(FLAGS_t0, 0.0, 1.0),
554 t1 =
SkTPin(FLAGS_t1, t0, 1.0),
555 native_fps = anim->
fps(),
556 frame0 = anim->
duration() * t0 * native_fps,
559 double fps = FLAGS_fps > 0 ? FLAGS_fps : native_fps;
561 SkDebugf(
"Invalid fps: %f.\n", fps);
565 auto frame_count =
static_cast<int>(
duration * fps);
566 static constexpr int kMaxFrames = 10000;
567 if (frame_count > kMaxFrames) {
568 frame_count = kMaxFrames;
571 const auto fps_scale = native_fps / fps;
573 printf(
"Rendering %f seconds (%d frames @%f fps).\n",
duration, frame_count, fps);
575 const auto sink = FrameSink::Make(
fmt, frame_count);
577 std::vector<double> frames_ms(frame_count);
579 const auto thread_count = FLAGS_gpu ? 0 : FLAGS_threads - 1;
588 std::unique_ptr<FrameGenerator> singleton_generator;
590 singleton_generator = FrameGenerator::Make(sink.get(),
fmt, scale_matrix);
593 tg.
batch(frame_count, [&](
int i) {
596 i = frame_count - 1 - i;
598 const auto start = std::chrono::steady_clock::now();
599 thread_local static auto* anim =
603 .
make(
static_cast<const char*
>(data->data()), data->size())
605 thread_local static auto*
gen = singleton_generator
606 ? singleton_generator.get()
607 : FrameGenerator::Make(sink.get(),
fmt, scale_matrix).release();
616 frames_ms[i] = ms_since(
start);
624 std::sort(frames_ms.begin(), frames_ms.end());
625 double sum = std::accumulate(frames_ms.begin(), frames_ms.end(), 0);
626 printf(
"Frame time stats: min %gms, med %gms, avg %gms, max %gms, sum %gms\n",
627 frames_ms[0], frames_ms[frame_count/2], sum/frame_count, frames_ms.back(), sum);
#define DEFINE_int(name, defaultValue, helpString)
#define DEFINE_bool2(name, shortName, defaultValue, helpString)
#define DEFINE_string2(name, shortName, defaultValue, helpString)
#define DEFINE_double(name, defaultValue, helpString)
@ kTopLeft_GrSurfaceOrigin
#define SkAssertResult(cond)
constexpr SkColor SK_ColorWHITE
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
#define sk_double_round2int(x)
SK_API sk_sp< SkFontMgr > SkFontMgr_New_Android(const SkFontMgr_Android_CustomFonts *custom)
SK_API sk_sp< SkFontMgr > SkFontMgr_New_Custom_Empty()
SK_API sk_sp< SkFontMgr > SkFontMgr_New_FontConfig(FcConfig *fc)
SK_API sk_sp< SkFontMgr > SkFontMgr_New_CoreText(CTFontCollectionRef)
static SkImage_Base * as_IB(SkImage *image)
static std::unique_ptr< SkEncoder > Make(SkWStream *dst, const SkPixmap *src, const SkYUVAPixmaps *srcYUVA, const SkColorSpace *srcYUVAColorSpace, const SkJpegEncoder::Options &options)
bool sk_mkdir(const char *path)
SK_API SkString static SkString SkStringPrintf()
static constexpr const T & SkTPin(const T &x, const T &lo, const T &hi)
constexpr size_t SkToSizeT(S x)
static void Parse(int argc, const char *const *argv)
static sk_sp< SkData > MakeFromFileName(const char path[])
bool write(const void *buffer, size_t size) override
static SkMatrix RectToRect(const SkRect &src, const SkRect &dst, ScaleToFit mode=kFill_ScaleToFit)
@ kCenter_ScaleToFit
scales and aligns to center
static SkString Join(const char *rootPath, const char *relativePath)
static SkString Dirname(const char *fullPath)
void batch(int N, std::function< void(int)> fn)
Builder & setResourceProvider(sk_sp< ResourceProvider >)
Builder & setFontManager(sk_sp< SkFontMgr >)
Builder & setPrecompInterceptor(sk_sp< PrecompInterceptor >)
Builder & setLogger(sk_sp< Logger >)
sk_sp< Animation > make(SkStream *)
Builder & setTextShapingFactory(sk_sp< SkShapers::Factory >)
void seekFrame(double t, sksg::InvalidationController *ic=nullptr)
const SkSize & size() const
void render(SkCanvas *canvas, const SkRect *dst=nullptr) const
virtual void log(Level, const char message[], const char *json=nullptr)=0
static sk_sp< CachingResourceProvider > Make(sk_sp< ResourceProvider > rp)
static sk_sp< DataURIResourceProviderProxy > Make(sk_sp< ResourceProvider > rp, ImageDecodeStrategy=ImageDecodeStrategy::kLazyDecode, sk_sp< const SkFontMgr > fontMgr=nullptr)
static sk_sp< FileResourceProvider > Make(SkString base_dir, ImageDecodeStrategy=ImageDecodeStrategy::kLazyDecode)
sk_sp< SkFontMgr > fontMgr
static FlMethodResponse * show(FlTextInputPlugin *self)
uint32_t uint32_t * format
SK_API sk_sp< SkImage > RasterFromPixmap(const SkPixmap &pixmap, RasterReleaseProc rasterReleaseProc, ReleaseContext releaseContext)
constexpr SkCodecs::Decoder Decoder()
constexpr SkCodecs::Decoder Decoder()
SK_API bool Encode(SkWStream *dst, const SkPixmap &src, const Options &options)
unsigned useCenter Optional< SkMatrix > matrix
std::string printf(const char *fmt,...) SK_PRINTF_LIKE(1
sk_sp< Factory > BestAvailable()
SK_API sk_sp< SkSurface > Raster(const SkImageInfo &imageInfo, size_t rowBytes, const SkSurfaceProps *surfaceProps)
SK_API sk_sp< SkSurface > RenderTarget(GrRecordingContext *context, skgpu::Budgeted budgeted, const SkImageInfo &imageInfo, int sampleCount, GrSurfaceOrigin surfaceOrigin, const SkSurfaceProps *surfaceProps, bool shouldCreateWithMips=false, bool isProtected=false)
constexpr SkCodecs::Decoder Decoder()
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
static SkString fmt(SkColor4f c)
static SkImageInfo MakeN32Premul(int width, int height)
static SkRect MakeIWH(int w, int h)
static constexpr SkRect MakeSize(const SkSize &size)
SkSerialImageProc fImageProc