| /* | |
| * Copyright 2026 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #ifndef dump_base_DEFINED | |
| #define dump_base_DEFINED | |
| #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 "src/core/SkTime.h" | |
| #include "tools/flags/CommandLineFlags.h" | |
| #include <cstdio> | |
| #include <optional> | |
| // SkRecordDumper is a base visitor functor for inspecting and formatting canvas commands | |
| // via SkRecord::visit() (see src/core/SkRecord.h). | |
| // | |
| // Specific overloads (DrawRect, ClipRect, DrawImage, etc.) format command parameters | |
| // into text strings and pass them to visitingPrinter(name, ns). | |
| // Derived classes implement visitingPrinter to handle indentation, indexing, timing, | |
| // and writing to the desired output stream (fOutput). | |
| class SkRecordDumper { | |
| public: | |
| // Constructor for benchmarking / canvas playback (e.g. dump_record) | |
| SkRecordDumper(SkCanvas* canvas, | |
| int totalCommands, | |
| bool printTime = false, | |
| FILE* output = stdout) | |
| : fDigits(0) | |
| , fIndent(0) | |
| , fIndex(0) | |
| , fOutput(output ? output : stdout) | |
| , fPrintTime(printTime) { | |
| while (totalCommands > 0) { | |
| totalCommands /= 10; | |
| fDigits++; | |
| } | |
| if (fDigits < 2) fDigits = 2; | |
| if (canvas) { | |
| fDraw.emplace(canvas, nullptr, nullptr, 0, nullptr); | |
| } | |
| } | |
| // Convenience constructor for offline dumping without canvas playback (e.g. dump_capture) | |
| SkRecordDumper(int totalCommands, FILE* output = stdout) | |
| : SkRecordDumper(nullptr, totalCommands, false, output) {} | |
| virtual ~SkRecordDumper() = default; | |
| template <typename T> void operator()(const T& command) { | |
| auto start = SkTime::GetNSecs(); | |
| if (fDraw) { | |
| (*fDraw)(command); | |
| } | |
| print(command, SkTime::GetNSecs() - start); | |
| } | |
| void operator()(const SkRecords::NoOp&) {} | |
| template <typename T> void print(const T& command, double ns) { | |
| this->visitingPrinter(NameOf(command), ns); | |
| } | |
| void print(const SkRecords::Restore& command, double ns) { | |
| if (fIndent > 0) --fIndent; | |
| this->visitingPrinter(NameOf(command), ns); | |
| } | |
| void print(const SkRecords::Save& command, double ns) { | |
| this->visitingPrinter(NameOf(command), ns); | |
| ++fIndent; | |
| } | |
| void print(const SkRecords::SaveLayer& command, double ns) { | |
| this->visitingPrinter(NameOf(command), ns); | |
| ++fIndent; | |
| } | |
| void print(const SkRecords::DrawPicture& command, double ns) { | |
| this->visitingPrinter(NameOf(command), ns); | |
| ++fIndent; | |
| const SkRecord* record = SkPicturePriv::GetRecord(command.picture.get()); | |
| for (int i = 0; i < record->count(); i++) { | |
| record->visit(i, *this); | |
| } | |
| --fIndent; | |
| } | |
| void print(const SkRecords::DrawAnnotation& command, double ns) { | |
| char buf[256]; | |
| snprintf(buf, | |
| sizeof(buf), | |
| "DrawAnnotation [%g %g %g %g] %s", | |
| command.rect.left(), | |
| command.rect.top(), | |
| command.rect.right(), | |
| command.rect.bottom(), | |
| command.key.c_str()); | |
| visitingPrinter(buf, ns); | |
| } | |
| void print(const SkRecords::DrawRect& command, double ns) { | |
| char buf[128]; | |
| snprintf(buf, | |
| sizeof(buf), | |
| "DrawRect [%g, %g, %g, %g]", | |
| command.rect.left(), | |
| command.rect.top(), | |
| command.rect.right(), | |
| command.rect.bottom()); | |
| visitingPrinter(buf, ns); | |
| } | |
| void print(const SkRecords::DrawOval& command, double ns) { | |
| char buf[128]; | |
| snprintf(buf, | |
| sizeof(buf), | |
| "DrawOval [%g, %g, %g, %g]", | |
| command.oval.left(), | |
| command.oval.top(), | |
| command.oval.right(), | |
| command.oval.bottom()); | |
| visitingPrinter(buf, ns); | |
| } | |
| void print(const SkRecords::DrawImage& command, double ns) { | |
| char buf[128]; | |
| snprintf(buf, sizeof(buf), "DrawImage [%g, %g]", command.left, command.top); | |
| visitingPrinter(buf, ns); | |
| } | |
| void print(const SkRecords::DrawImageRect& command, double ns) { | |
| char buf[128]; | |
| snprintf(buf, | |
| sizeof(buf), | |
| "DrawImageRect dst=[%g, %g, %g, %g]", | |
| command.dst.left(), | |
| command.dst.top(), | |
| command.dst.right(), | |
| command.dst.bottom()); | |
| visitingPrinter(buf, ns); | |
| } | |
| void print(const SkRecords::DrawPaint& command, double ns) { | |
| visitingPrinter("DrawPaint (clear/paint)", ns); | |
| } | |
| void print(const SkRecords::ClipRect& command, double ns) { | |
| char buf[128]; | |
| snprintf(buf, | |
| sizeof(buf), | |
| "ClipRect [%g, %g, %g, %g]", | |
| command.rect.left(), | |
| command.rect.top(), | |
| command.rect.right(), | |
| command.rect.bottom()); | |
| visitingPrinter(buf, ns); | |
| } | |
| protected: | |
| void visitingPrinter(const char* name, double ns) { | |
| int us = (int)(ns * 1e-3); | |
| if (!fPrintTime) { | |
| fprintf(fOutput, "%6dus ", us); | |
| } | |
| fprintf(fOutput, "%*d ", fDigits, fIndex++); | |
| for (int i = 0; i < fIndent; i++) { | |
| fprintf(fOutput, " "); | |
| } | |
| if (fPrintTime) { | |
| fprintf(fOutput, "%6dus ", us); | |
| } | |
| fprintf(fOutput, "%s\n", name); | |
| } | |
| // NameOf returns the string name of an SkRecord command type. | |
| // It uses the X-macro pattern via SK_RECORD_TYPES (defined in src/core/SkRecord.h): | |
| // SK_RECORD_TYPES(M) expands macro M over all SkRecord command types. | |
| // CASE(U) generates a case label for SkRecords::U##_Type returning its stringified name "#U". | |
| template <typename T> static const char* NameOf(const T&) { | |
| #define CASE(U) \ | |
| case SkRecords::U##_Type: \ | |
| return #U; | |
| switch (T::kType) { SK_RECORD_TYPES(CASE) } | |
| #undef CASE | |
| SkDEBUGFAIL("Unknown T"); | |
| return "Unknown T"; | |
| } | |
| static const char* NameOf(const SkRecords::SaveLayer&) { | |
| return "\x1b[31;1mSaveLayer\x1b[0m"; // Bold red. | |
| } | |
| int fDigits; // Number of digits for formatting the command index column. | |
| int fIndent; // Current indentation level to represent Save/SaveLayer nesting depth. | |
| int fIndex; // Zero-based index of the current command being dumped. | |
| FILE* fOutput; // The file to write the output to. If null, print to stdout. | |
| bool fPrintTime; | |
| std::optional<SkRecords::Draw> fDraw; | |
| }; | |
| #endif // dump_base_DEFINED |