| /* |
| * Copyright 2026 Google LLC |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "include/core/SkCanvas.h" |
| #include "include/core/SkData.h" |
| #include "include/core/SkPicture.h" |
| #include "include/core/SkRect.h" |
| #include "include/core/SkStream.h" |
| #include "src/capture/SkCapture.h" |
| #include "src/core/SkPicturePriv.h" |
| #include "src/core/SkRecord.h" |
| #include "src/core/SkRecordCanvas.h" |
| #include "src/core/SkRecordDraw.h" |
| #include "tools/dump_base.h" |
| #include "tools/flags/CommandLineFlags.h" |
| |
| #include <cstdio> |
| #include <cstdlib> |
| #include <filesystem> |
| #include <string> |
| |
| static DEFINE_string2(input, i, "", "Capture file(s) to dump."); |
| static DEFINE_bool2(assets, a, true, "Include assets in the dump."); |
| static DEFINE_string2(output, o, "", "Files to output text to"); |
| |
| static std::string resolve_path(const char* rawPath) { |
| std::filesystem::path p(rawPath); |
| const char* bazel_wd = std::getenv("BUILD_WORKING_DIRECTORY"); |
| if (p.is_relative() && bazel_wd != nullptr) { |
| p = std::filesystem::path(bazel_wd) / p; |
| } |
| return p.string(); |
| } |
| |
| void dump_assets(SkCapture::Metadata meta, sk_sp<SkCapture> capture, FILE* output = stdout) { |
| if (!output) output = stdout; |
| |
| fprintf(output, |
| "--------------------------------------------------------------------------------\n"); |
| fprintf(output, "ASSETS SECTION (%u assets):\n", meta.numAssets); |
| fprintf(output, |
| "--------------------------------------------------------------------------------\n"); |
| |
| for (uint32_t i = 0; i < meta.numAssets; ++i) { |
| sk_sp<SkPicture> picture = capture->getAsset(i); |
| if (!picture) { |
| fprintf(output, " Picture [%u]: <null>\n", i); |
| continue; |
| } |
| |
| SkRect cull = picture->cullRect(); |
| fprintf(output, |
| " Picture [%u]: Dimensions %dx%d (cull: [%g, %g, %g, %g])\n", |
| i, |
| SkScalarCeilToInt(cull.width()), |
| SkScalarCeilToInt(cull.height()), |
| cull.left(), |
| cull.top(), |
| cull.right(), |
| cull.bottom()); |
| |
| const int w = SkScalarCeilToInt(cull.width()); |
| const int h = SkScalarCeilToInt(cull.height()); |
| SkRecord record; |
| SkRecordCanvas recCanvas(&record, w, h); |
| picture->playback(&recCanvas); |
| |
| fprintf(output, " Record count: %d operations\n", record.count()); |
| if (record.count() == 0) { |
| fprintf(output, " (Empty picture - snapped for idle surface)\n"); |
| } else { |
| SkRecordDumper dumper(record.count(), output); |
| for (int j = 0; j < record.count(); ++j) { |
| record.visit(j, dumper); |
| } |
| } |
| fprintf(output, "\n"); |
| } |
| } |
| |
| void dump_timeline(SkCapture::Metadata meta, sk_sp<SkCapture> capture, FILE* output = stdout) { |
| if (!output) output = stdout; |
| |
| fprintf(output, |
| "--------------------------------------------------------------------------------\n"); |
| fprintf(output, "TIMELINE SECTION (%u recordings):\n", meta.numRecordingCaptures); |
| fprintf(output, |
| "--------------------------------------------------------------------------------\n"); |
| for (uint32_t i = 0; i < meta.numRecordingCaptures; ++i) { |
| const SkCapture::RecordingCapture* rec = capture->getRecordingCapture(i); |
| if (!rec) { |
| fprintf(output, " Recording [%u]: <null>\n", i); |
| continue; |
| } |
| fprintf(output, " Recording [%u]: %d draw task(s)\n", i, rec->fDrawTasks.size()); |
| for (int j = 0; j < rec->fDrawTasks.size(); ++j) { |
| uint32_t assetIdx = rec->fDrawTasks[j].fAssetIndex; |
| sk_sp<SkPicture> pic = capture->getAsset(assetIdx); |
| if (pic) { |
| SkRect cull = pic->cullRect(); |
| fprintf(output, |
| " [Task %d] -> Asset [%u] (Dimensions: %dx%d, cull: [%g, %g, %g, %g], " |
| "approxOps: %d)\n", |
| j, |
| assetIdx, |
| SkScalarCeilToInt(cull.width()), |
| SkScalarCeilToInt(cull.height()), |
| cull.left(), |
| cull.top(), |
| cull.right(), |
| cull.bottom(), |
| pic->approximateOpCount()); |
| } else { |
| fprintf(output, " [Task %d] -> Asset [%u] (<null>)\n", j, assetIdx); |
| } |
| } |
| fprintf(output, "\n"); |
| } |
| } |
| |
| void dump_capture_file(const char* path, bool include_assets = true, FILE* output = stdout) { |
| if (!output) output = stdout; |
| |
| fprintf(output, |
| "================================================================================\n"); |
| fprintf(output, "CAPTURE FILE: %s\n", path); |
| fprintf(output, |
| "================================================================================\n"); |
| |
| sk_sp<SkData> data = SkData::MakeFromFileName(path); |
| if (!data) { |
| fprintf(output, "Error: Could not read file '%s'\n\n", path); |
| return; |
| } |
| |
| fprintf(output, "File Size: %zu bytes\n", data->size()); |
| |
| sk_sp<SkCapture> capture = SkCapture::MakeFromData(data); |
| if (!capture) { |
| fprintf(output, "Error: Failed to deserialize SkCapture from '%s'\n\n", path); |
| return; |
| } |
| |
| // Metadata Section |
| SkCapture::Metadata meta = capture->getMetadata(); |
| fprintf(output, "Capture Format Version: %u\n", meta.version); |
| fprintf(output, "Number of Recorded Assets: %u\n", meta.numAssets); |
| fprintf(output, "Number of Recording Captures (Timeline): %u\n", meta.numRecordingCaptures); |
| |
| // Assets Section |
| if (include_assets) dump_assets(meta, capture, output); |
| |
| // Timeline Section |
| dump_timeline(meta, capture, output); |
| |
| fprintf(output, |
| "================================================================================\n\n"); |
| } |
| |
| int main(int argc, char** argv) { |
| CommandLineFlags::SetUsage( |
| R"(Dumps the contents and structure of SkCapture files (.capt). dump_capture |
| Usage: |
| dump_capture -i <file1.capt> [file2.capt ...] |
| dump_capture -i <file.capt> --noassets |
| )"); |
| CommandLineFlags::Parse(argc, argv); |
| |
| if (FLAGS_input.isEmpty()) { |
| CommandLineFlags::PrintUsage(); |
| return 1; |
| } |
| |
| if (FLAGS_output.isEmpty()) { |
| for (int i = 0; i < FLAGS_input.size(); ++i) { |
| std::string inPath = resolve_path(FLAGS_input[i]); |
| dump_capture_file(inPath.c_str(), FLAGS_assets); |
| } |
| return 0; |
| } |
| |
| if (FLAGS_output.size() != FLAGS_input.size()) { |
| fprintf(stderr, |
| "Error: Number of output files must match number of input files : output size " |
| "'%d' != input size '%d'" |
| "\n", |
| FLAGS_output.size(), |
| FLAGS_input.size()); |
| return 1; |
| } |
| |
| for (int i = 0; i < FLAGS_input.size(); ++i) { |
| std::string inPath = resolve_path(FLAGS_input[i]); |
| std::string outPath = resolve_path(FLAGS_output[i]); |
| |
| FILE* out = fopen(outPath.c_str(), "w"); |
| if (!out) { |
| fprintf(stderr, "Error: Failed to open output file '%s'\n", outPath.c_str()); |
| return 1; |
| } |
| |
| dump_capture_file(inPath.c_str(), FLAGS_assets, out); |
| |
| if (out != stdout) { |
| fclose(out); |
| } |
| } |
| |
| return 0; |
| } |