blob: cddbc828b9a3c178190ef674fa65508c93a2c366 [file]
/*
* 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