Sketch DM refactor.
BUG=skia:3255 I think this supports everything DM used to, but has completely refactored how it works to fit the design in the bug. Configs like "tiles-gpu" are automatically wired up. I wouldn't suggest looking at this as a diff. There's just a bunch of deleted files, a few new files, and one new file that shares a name with a deleted file (DM.cpp). NOTREECHECKS=true Committed: https://skia.googlesource.com/skia/+/709d2c3e5062c5b57f91273bfc11a751f5b2bb88 Review URL: https://codereview.chromium.org/788243008
This commit is contained in:
parent
0063a9b69a
commit
748ca3bf2d
@ -77,14 +77,7 @@ DEFINE_int32(flushEvery, 10, "Flush --outResultsFile every Nth run.");
|
||||
|
||||
static SkString humanize(double ms) {
|
||||
if (FLAGS_verbose) return SkStringPrintf("%llu", (uint64_t)(ms*1e6));
|
||||
if (ms > 1e+3) return SkStringPrintf("%.3gs", ms/1e3);
|
||||
if (ms < 1e-3) return SkStringPrintf("%.3gns", ms*1e6);
|
||||
#ifdef SK_BUILD_FOR_WIN
|
||||
if (ms < 1) return SkStringPrintf("%.3gus", ms*1e3);
|
||||
#else
|
||||
if (ms < 1) return SkStringPrintf("%.3gµs", ms*1e3);
|
||||
#endif
|
||||
return SkStringPrintf("%.3gms", ms);
|
||||
return HumanizeMs(ms);
|
||||
}
|
||||
#define HUMANIZE(ms) humanize(ms).c_str()
|
||||
|
||||
@ -548,7 +541,7 @@ public:
|
||||
static const int kFlags = SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag;
|
||||
pic->playback(recorder.beginRecording(pic->cullRect().width(),
|
||||
pic->cullRect().height(),
|
||||
&factory,
|
||||
&factory,
|
||||
fUseMPDs[fCurrentUseMPD] ? kFlags : 0));
|
||||
pic.reset(recorder.endRecording());
|
||||
}
|
||||
|
618
dm/DM.cpp
618
dm/DM.cpp
@ -1,293 +1,443 @@
|
||||
// Main binary for DM.
|
||||
// For a high-level overview, please see dm/README.
|
||||
|
||||
#include "CrashHandler.h"
|
||||
#include "LazyDecodeBitmap.h"
|
||||
#include "DMJsonWriter.h"
|
||||
#include "DMSrcSink.h"
|
||||
#include "OverwriteLine.h"
|
||||
#include "ProcStats.h"
|
||||
#include "SkBBHFactory.h"
|
||||
#include "SkCommonFlags.h"
|
||||
#include "SkForceLinking.h"
|
||||
#include "SkGraphics.h"
|
||||
#include "SkMD5.h"
|
||||
#include "SkOSFile.h"
|
||||
#include "SkPicture.h"
|
||||
#include "SkString.h"
|
||||
#include "SkTaskGroup.h"
|
||||
#include "Test.h"
|
||||
#include "gm.h"
|
||||
#include "sk_tool_utils.h"
|
||||
#include "sk_tool_utils_flags.h"
|
||||
#include "Timer.h"
|
||||
|
||||
#include "DMCpuGMTask.h"
|
||||
#include "DMGpuGMTask.h"
|
||||
#include "DMGpuSupport.h"
|
||||
#include "DMImageTask.h"
|
||||
#include "DMJsonWriter.h"
|
||||
#include "DMPDFTask.h"
|
||||
#include "DMPDFRasterizeTask.h"
|
||||
#include "DMReporter.h"
|
||||
#include "DMSKPTask.h"
|
||||
#include "DMTask.h"
|
||||
#include "DMTaskRunner.h"
|
||||
#include "DMTestTask.h"
|
||||
|
||||
#ifdef SK_BUILD_POPPLER
|
||||
# include "SkPDFRasterizer.h"
|
||||
# define RASTERIZE_PDF_PROC SkPopplerRasterizePDF
|
||||
#elif defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
|
||||
# include "SkCGUtils.h"
|
||||
# define RASTERIZE_PDF_PROC SkPDFDocumentToBitmap
|
||||
#else
|
||||
# define RASTERIZE_PDF_PROC NULL
|
||||
#endif
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
using skiagm::GM;
|
||||
using skiagm::GMRegistry;
|
||||
using skiatest::Test;
|
||||
using skiatest::TestRegistry;
|
||||
|
||||
static const char kGpuAPINameGL[] = "gl";
|
||||
static const char kGpuAPINameGLES[] = "gles";
|
||||
|
||||
DEFINE_bool(gms, true, "Run GMs?");
|
||||
DEFINE_bool(tests, true, "Run tests?");
|
||||
DEFINE_bool(reportUsedChars, false, "Output test font construction data to be pasted into"
|
||||
" create_test_font.cpp.");
|
||||
DEFINE_string(images, "resources", "Path to directory containing images to decode.");
|
||||
DEFINE_bool(rasterPDF, true, "Rasterize PDFs?");
|
||||
DEFINE_string(images, "resources", "Images to decode.");
|
||||
//DEFINE_string(src, "gm skp image subset", "Source types to test.");
|
||||
DEFINE_string(src, "gm skp", "Source types to test. TEMPORARILY DISABLED");
|
||||
DEFINE_bool(nameByHash, false,
|
||||
"If true, write to FLAGS_writePath[0]/<hash>.png instead of "
|
||||
"to FLAGS_writePath[0]/<config>/<sourceType>/<name>.png");
|
||||
DEFINE_bool2(pathOpsExtended, x, false, "Run extended pathOps tests.");
|
||||
DEFINE_string(matrix, "1 0 0 0 1 0 0 0 1",
|
||||
"Matrix to apply when using 'matrix' in config.");
|
||||
|
||||
__SK_FORCE_IMAGE_DECODER_LINKING;
|
||||
using namespace DM;
|
||||
|
||||
static DM::RasterizePdfProc get_pdf_rasterizer_proc() {
|
||||
return reinterpret_cast<DM::RasterizePdfProc>(
|
||||
FLAGS_rasterPDF ? RASTERIZE_PDF_PROC : NULL);
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
SK_DECLARE_STATIC_MUTEX(gFailuresMutex);
|
||||
static SkTArray<SkString> gFailures;
|
||||
|
||||
static void fail(ImplicitString err) {
|
||||
SkAutoMutexAcquire lock(gFailuresMutex);
|
||||
SkDebugf("\n\nFAILURE: %s\n\n", err.c_str());
|
||||
gFailures.push_back(err);
|
||||
}
|
||||
|
||||
// "FooBar" -> "foobar". Obviously, ASCII only.
|
||||
static SkString lowercase(SkString s) {
|
||||
for (size_t i = 0; i < s.size(); i++) {
|
||||
s[i] = tolower(s[i]);
|
||||
}
|
||||
return s;
|
||||
static int32_t gPending = 0; // Atomic.
|
||||
|
||||
static void done(double ms, ImplicitString config, ImplicitString src, ImplicitString name) {
|
||||
SkDebugf("%s(%4dMB %5d) %s\t%s %s %s ", FLAGS_verbose ? "\n" : kSkOverwriteLine
|
||||
, sk_tools::getMaxResidentSetSizeMB()
|
||||
, sk_atomic_dec(&gPending)-1
|
||||
, HumanizeMs(ms).c_str()
|
||||
, config.c_str()
|
||||
, src.c_str()
|
||||
, name.c_str());
|
||||
}
|
||||
|
||||
static const GrContextFactory::GLContextType native = GrContextFactory::kNative_GLContextType;
|
||||
static const GrContextFactory::GLContextType nvpr = GrContextFactory::kNVPR_GLContextType;
|
||||
static const GrContextFactory::GLContextType null = GrContextFactory::kNull_GLContextType;
|
||||
static const GrContextFactory::GLContextType debug = GrContextFactory::kDebug_GLContextType;
|
||||
#if SK_ANGLE
|
||||
static const GrContextFactory::GLContextType angle = GrContextFactory::kANGLE_GLContextType;
|
||||
#endif
|
||||
#if SK_MESA
|
||||
static const GrContextFactory::GLContextType mesa = GrContextFactory::kMESA_GLContextType;
|
||||
#endif
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
static void kick_off_gms(const SkTDArray<GMRegistry::Factory>& gms,
|
||||
const SkTArray<SkString>& configs,
|
||||
GrGLStandard gpuAPI,
|
||||
DM::Reporter* reporter,
|
||||
DM::TaskRunner* tasks) {
|
||||
#define START(name, type, ...) \
|
||||
if (lowercase(configs[j]).equals(name)) { \
|
||||
tasks->add(SkNEW_ARGS(DM::type, (name, reporter, tasks, gms[i], ## __VA_ARGS__))); \
|
||||
template <typename T>
|
||||
struct Tagged : public SkAutoTDelete<T> { const char* tag; };
|
||||
|
||||
static const bool kMemcpyOK = true;
|
||||
|
||||
static SkTArray<Tagged<Src>, kMemcpyOK> gSrcs;
|
||||
static SkTArray<Tagged<Sink>, kMemcpyOK> gSinks;
|
||||
|
||||
static void push_src(const char* tag, Src* s) {
|
||||
SkAutoTDelete<Src> src(s);
|
||||
if (FLAGS_src.contains(tag) &&
|
||||
!SkCommandLineFlags::ShouldSkip(FLAGS_match, src->name().c_str())) {
|
||||
Tagged<Src>& s = gSrcs.push_back();
|
||||
s.reset(src.detach());
|
||||
s.tag = tag;
|
||||
}
|
||||
for (int i = 0; i < gms.count(); i++) {
|
||||
for (int j = 0; j < configs.count(); j++) {
|
||||
}
|
||||
|
||||
START("565", CpuGMTask, kRGB_565_SkColorType);
|
||||
START("8888", CpuGMTask, kN32_SkColorType);
|
||||
START("gpu", GpuGMTask, native, gpuAPI, 0, false);
|
||||
START("msaa4", GpuGMTask, native, gpuAPI, 4, false);
|
||||
START("msaa16", GpuGMTask, native, gpuAPI, 16, false);
|
||||
START("nvprmsaa4", GpuGMTask, nvpr, gpuAPI, 4, false);
|
||||
START("nvprmsaa16", GpuGMTask, nvpr, gpuAPI, 16, false);
|
||||
START("gpudft", GpuGMTask, native, gpuAPI, 0, true);
|
||||
START("gpunull", GpuGMTask, null, gpuAPI, 0, false);
|
||||
START("gpudebug", GpuGMTask, debug, gpuAPI, 0, false);
|
||||
#if SK_ANGLE
|
||||
START("angle", GpuGMTask, angle, gpuAPI, 0, false);
|
||||
#endif
|
||||
#if SK_MESA
|
||||
START("mesa", GpuGMTask, mesa, gpuAPI, 0, false);
|
||||
#endif
|
||||
START("pdf", PDFTask, get_pdf_rasterizer_proc());
|
||||
static void gather_srcs() {
|
||||
for (const skiagm::GMRegistry* r = skiagm::GMRegistry::Head(); r; r = r->next()) {
|
||||
push_src("gm", new GMSrc(r->factory()));
|
||||
}
|
||||
if (!FLAGS_skps.isEmpty()) {
|
||||
SkOSFile::Iter it(FLAGS_skps[0], "skp");
|
||||
for (SkString file; it.next(&file); ) {
|
||||
push_src("skp", new SKPSrc(SkOSPath::Join(FLAGS_skps[0], file.c_str())));
|
||||
}
|
||||
}
|
||||
#undef START
|
||||
}
|
||||
|
||||
static void kick_off_tests(const SkTDArray<TestRegistry::Factory>& tests,
|
||||
DM::Reporter* reporter,
|
||||
DM::TaskRunner* tasks) {
|
||||
for (int i = 0; i < tests.count(); i++) {
|
||||
SkAutoTDelete<Test> test(tests[i](NULL));
|
||||
if (test->isGPUTest()) {
|
||||
tasks->add(SkNEW_ARGS(DM::GpuTestTask, (reporter, tasks, tests[i])));
|
||||
} else {
|
||||
tasks->add(SkNEW_ARGS(DM::CpuTestTask, (reporter, tasks, tests[i])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void find_files(const char* dir,
|
||||
const char* suffixes[],
|
||||
size_t num_suffixes,
|
||||
SkTArray<SkString>* files) {
|
||||
if (0 == strcmp(dir, "")) {
|
||||
return;
|
||||
}
|
||||
|
||||
SkString filename;
|
||||
for (size_t i = 0; i < num_suffixes; i++) {
|
||||
SkOSFile::Iter it(dir, suffixes[i]);
|
||||
while (it.next(&filename)) {
|
||||
if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, filename.c_str())) {
|
||||
files->push_back(SkOSPath::Join(dir, filename.c_str()));
|
||||
if (!FLAGS_images.isEmpty()) {
|
||||
const char* exts[] = {
|
||||
"bmp", "gif", "jpg", "jpeg", "png", "webp", "ktx", "astc", "wbmp", "ico",
|
||||
"BMP", "GIF", "JPG", "JPEG", "PNG", "WEBP", "KTX", "ASTC", "WBMP", "ICO",
|
||||
};
|
||||
for (size_t i = 0; i < SK_ARRAY_COUNT(exts); i++) {
|
||||
SkOSFile::Iter it(FLAGS_images[0], exts[i]);
|
||||
for (SkString file; it.next(&file); ) {
|
||||
SkString path = SkOSPath::Join(FLAGS_images[0], file.c_str());
|
||||
push_src("image", new ImageSrc(path)); // Decode entire image.
|
||||
push_src("subset", new ImageSrc(path, 5)); // Decode 5 random subsets.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void kick_off_skps(const SkTArray<SkString>& skps,
|
||||
DM::Reporter* reporter,
|
||||
DM::TaskRunner* tasks) {
|
||||
for (int i = 0; i < skps.count(); ++i) {
|
||||
SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(skps[i].c_str()));
|
||||
if (stream.get() == NULL) {
|
||||
SkDebugf("Could not read %s.\n", skps[i].c_str());
|
||||
exit(1);
|
||||
}
|
||||
SkAutoTUnref<SkPicture> pic(
|
||||
SkPicture::CreateFromStream(stream.get(), &sk_tools::LazyDecodeBitmap));
|
||||
if (pic.get() == NULL) {
|
||||
SkDebugf("Could not read %s as an SkPicture.\n", skps[i].c_str());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
SkString filename = SkOSPath::Basename(skps[i].c_str());
|
||||
tasks->add(SkNEW_ARGS(DM::SKPTask, (reporter, tasks, pic, filename)));
|
||||
tasks->add(SkNEW_ARGS(DM::PDFTask, (reporter, tasks, pic, filename,
|
||||
get_pdf_rasterizer_proc())));
|
||||
}
|
||||
static GrGLStandard get_gpu_api() {
|
||||
if (FLAGS_gpuAPI.contains("gl")) { return kGL_GrGLStandard; }
|
||||
if (FLAGS_gpuAPI.contains("gles")) { return kGLES_GrGLStandard; }
|
||||
return kNone_GrGLStandard;
|
||||
}
|
||||
|
||||
static void kick_off_images(const SkTArray<SkString>& images,
|
||||
DM::Reporter* reporter,
|
||||
DM::TaskRunner* tasks) {
|
||||
for (int i = 0; i < images.count(); i++) {
|
||||
SkAutoTUnref<SkData> image(SkData::NewFromFileName(images[i].c_str()));
|
||||
if (!image) {
|
||||
SkDebugf("Could not read %s.\n", images[i].c_str());
|
||||
exit(1);
|
||||
}
|
||||
SkString filename = SkOSPath::Basename(images[i].c_str());
|
||||
tasks->add(SkNEW_ARGS(DM::ImageTask, (reporter, tasks, image, filename)));
|
||||
tasks->add(SkNEW_ARGS(DM::ImageTask, (reporter, tasks, image, filename, 5/*subsets*/)));
|
||||
static void push_sink(const char* tag, Sink* s) {
|
||||
SkAutoTDelete<Sink> sink(s);
|
||||
if (!FLAGS_config.contains(tag)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Try a noop Src as a canary. If it fails, skip this sink.
|
||||
struct : public Src {
|
||||
Error draw(SkCanvas*) const SK_OVERRIDE { return ""; }
|
||||
SkISize size() const SK_OVERRIDE { return SkISize::Make(16, 16); }
|
||||
Name name() const SK_OVERRIDE { return "noop"; }
|
||||
} noop;
|
||||
|
||||
|
||||
static void report_failures(const SkTArray<SkString>& failures) {
|
||||
if (failures.count() == 0) {
|
||||
SkBitmap bitmap;
|
||||
SkDynamicMemoryWStream stream;
|
||||
Error err = sink->draw(noop, &bitmap, &stream);
|
||||
if (!err.isEmpty()) {
|
||||
SkDebugf("Skipping %s: %s\n", tag, err.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
SkDebugf("Failures:\n");
|
||||
for (int i = 0; i < failures.count(); i++) {
|
||||
SkDebugf(" %s\n", failures[i].c_str());
|
||||
Tagged<Sink>& ts = gSinks.push_back();
|
||||
ts.reset(sink.detach());
|
||||
ts.tag = tag;
|
||||
}
|
||||
|
||||
static bool gpu_supported() {
|
||||
#if SK_SUPPORT_GPU
|
||||
return FLAGS_gpu;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static Sink* create_sink(const char* tag) {
|
||||
#define SINK(t, sink, ...) if (0 == strcmp(t, tag)) { return new sink(__VA_ARGS__); }
|
||||
if (gpu_supported()) {
|
||||
const GrGLStandard api = get_gpu_api();
|
||||
SINK("gpunull", GPUSink, GrContextFactory::kNull_GLContextType, api, 0, false);
|
||||
SINK("gpudebug", GPUSink, GrContextFactory::kDebug_GLContextType, api, 0, false);
|
||||
SINK("gpu", GPUSink, GrContextFactory::kNative_GLContextType, api, 0, false);
|
||||
SINK("gpudft", GPUSink, GrContextFactory::kNative_GLContextType, api, 0, true);
|
||||
SINK("msaa4", GPUSink, GrContextFactory::kNative_GLContextType, api, 4, false);
|
||||
SINK("msaa16", GPUSink, GrContextFactory::kNative_GLContextType, api, 16, false);
|
||||
SINK("nvprmsaa4", GPUSink, GrContextFactory::kNVPR_GLContextType, api, 4, false);
|
||||
SINK("nvprmsaa16", GPUSink, GrContextFactory::kNVPR_GLContextType, api, 16, false);
|
||||
#if SK_ANGLE
|
||||
SINK("angle", GPUSink, GrContextFactory::kANGLE_GLContextType, api, 0, false);
|
||||
#endif
|
||||
#if SK_MESA
|
||||
SINK("mesa", GPUSink, GrContextFactory::kMESA_GLContextType, api, 0, false);
|
||||
#endif
|
||||
}
|
||||
SkDebugf("%d failures.\n", failures.count());
|
||||
|
||||
if (FLAGS_cpu) {
|
||||
SINK("565", RasterSink, kRGB_565_SkColorType);
|
||||
SINK("8888", RasterSink, kN32_SkColorType);
|
||||
// TODO(mtklein): reenable once skiagold can handle .pdf uploads.
|
||||
//SINK("pdf", PDFSink);
|
||||
}
|
||||
#undef SINK
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static GrGLStandard get_gl_standard() {
|
||||
if (FLAGS_gpuAPI.contains(kGpuAPINameGL)) {
|
||||
return kGL_GrGLStandard;
|
||||
}
|
||||
if (FLAGS_gpuAPI.contains(kGpuAPINameGLES)) {
|
||||
return kGLES_GrGLStandard;
|
||||
}
|
||||
return kNone_GrGLStandard;
|
||||
static Sink* create_via(const char* tag, Sink* wrapped) {
|
||||
#define VIA(t, via, ...) if (0 == strcmp(t, tag)) { return new via(__VA_ARGS__); }
|
||||
VIA("serialize", ViaSerialization, wrapped);
|
||||
|
||||
VIA("tiles", ViaTiles, 256, 256, NULL, wrapped);
|
||||
VIA("tiles_rt", ViaTiles, 256, 256, new SkRTreeFactory, wrapped);
|
||||
|
||||
const int xp = SkGPipeWriter::kCrossProcess_Flag,
|
||||
sa = xp | SkGPipeWriter::kSharedAddressSpace_Flag;
|
||||
VIA("pipe", ViaPipe, 0, wrapped);
|
||||
VIA("pipe_xp", ViaPipe, xp, wrapped);
|
||||
VIA("pipe_sa", ViaPipe, sa, wrapped);
|
||||
|
||||
if (FLAGS_matrix.count() == 9) {
|
||||
SkMatrix m;
|
||||
for (int i = 0; i < 9; i++) {
|
||||
m[i] = (SkScalar)atof(FLAGS_matrix[i]);
|
||||
}
|
||||
VIA("matrix", ViaMatrix, m, wrapped);
|
||||
}
|
||||
#undef VIA
|
||||
return NULL;
|
||||
}
|
||||
|
||||
template <typename T, typename Registry>
|
||||
static void append_matching_factories(Registry* head, SkTDArray<typename Registry::Factory>* out) {
|
||||
for (const Registry* reg = head; reg != NULL; reg = reg->next()) {
|
||||
SkAutoTDelete<T> forName(reg->factory()(NULL));
|
||||
if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, forName->getName())) {
|
||||
*out->append() = reg->factory();
|
||||
static void gather_sinks() {
|
||||
for (int i = 0; i < FLAGS_config.count(); i++) {
|
||||
const char* config = FLAGS_config[i];
|
||||
SkTArray<SkString> parts;
|
||||
SkStrSplit(config, "-", &parts);
|
||||
|
||||
Sink* sink = NULL;
|
||||
for (int i = parts.count(); i-- > 0;) {
|
||||
const char* part = parts[i].c_str();
|
||||
Sink* next = (sink == NULL) ? create_sink(part) : create_via(part, sink);
|
||||
if (next == NULL) {
|
||||
SkDebugf("Skipping %s: Don't understand '%s'.\n", config, part);
|
||||
delete sink;
|
||||
sink = NULL;
|
||||
break;
|
||||
}
|
||||
sink = next;
|
||||
}
|
||||
if (sink) {
|
||||
push_sink(config, sink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The finest-grained unit of work we can run: draw a single Src into a single Sink,
|
||||
// report any errors, and perhaps write out the output: a .png of the bitmap, or a raw stream.
|
||||
struct Task {
|
||||
Task(const Tagged<Src>& src, const Tagged<Sink>& sink) : src(src), sink(sink) {}
|
||||
const Tagged<Src>& src;
|
||||
const Tagged<Sink>& sink;
|
||||
|
||||
static void Run(Task* task) {
|
||||
WallTimer timer;
|
||||
timer.start();
|
||||
if (!FLAGS_dryRun) {
|
||||
SkBitmap bitmap;
|
||||
SkDynamicMemoryWStream stream;
|
||||
Error err = task->sink->draw(*task->src, &bitmap, &stream);
|
||||
if (!err.isEmpty()) {
|
||||
fail(SkStringPrintf("%s %s %s: %s",
|
||||
task->sink.tag,
|
||||
task->src.tag,
|
||||
task->src->name().c_str(),
|
||||
err.c_str()));
|
||||
}
|
||||
if (!FLAGS_writePath.isEmpty()) {
|
||||
const char* ext = task->sink->fileExtension();
|
||||
if (stream.bytesWritten() == 0) {
|
||||
SkMemoryStream pixels(bitmap.getPixels(), bitmap.getSize());
|
||||
WriteToDisk(*task, &pixels, bitmap.getSize(), &bitmap, ext);
|
||||
} else {
|
||||
SkAutoTDelete<SkStreamAsset> data(stream.detachAsStream());
|
||||
WriteToDisk(*task, data, data->getLength(), NULL, ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
timer.end();
|
||||
done(timer.fWall, task->sink.tag, task->src.tag, task->src->name());
|
||||
}
|
||||
|
||||
static void WriteToDisk(const Task& task,
|
||||
SkStream* data, size_t len,
|
||||
const SkBitmap* bitmap,
|
||||
const char* ext) {
|
||||
SkMD5 hash;
|
||||
hash.writeStream(data, len);
|
||||
SkMD5::Digest digest;
|
||||
hash.finish(digest);
|
||||
|
||||
JsonWriter::BitmapResult result;
|
||||
result.name = task.src->name();
|
||||
result.config = task.sink.tag;
|
||||
result.sourceType = task.src.tag;
|
||||
result.ext = ext;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
result.md5.appendf("%02x", digest.data[i]);
|
||||
}
|
||||
JsonWriter::AddBitmapResult(result);
|
||||
|
||||
const char* dir = FLAGS_writePath[0];
|
||||
if (0 == strcmp(dir, "@")) { // Needed for iOS.
|
||||
dir = FLAGS_resourcePath[0];
|
||||
}
|
||||
sk_mkdir(dir);
|
||||
|
||||
SkString path;
|
||||
if (FLAGS_nameByHash) {
|
||||
path = SkOSPath::Join(dir, result.md5.c_str());
|
||||
path.append(".");
|
||||
path.append(ext);
|
||||
if (sk_exists(path.c_str())) {
|
||||
return; // Content-addressed. If it exists already, we're done.
|
||||
}
|
||||
} else {
|
||||
path = SkOSPath::Join(dir, task.sink.tag);
|
||||
sk_mkdir(path.c_str());
|
||||
path = SkOSPath::Join(path.c_str(), task.src.tag);
|
||||
sk_mkdir(path.c_str());
|
||||
path = SkOSPath::Join(path.c_str(), task.src->name().c_str());
|
||||
path.append(".");
|
||||
path.append(ext);
|
||||
}
|
||||
|
||||
SkFILEWStream file(path.c_str());
|
||||
if (!file.isValid()) {
|
||||
fail(SkStringPrintf("Can't open %s for writing.\n", path.c_str()));
|
||||
return;
|
||||
}
|
||||
|
||||
data->rewind();
|
||||
if (bitmap) {
|
||||
// We can't encode A8 bitmaps as PNGs. Convert them to 8888 first.
|
||||
SkBitmap converted;
|
||||
if (bitmap->info().colorType() == kAlpha_8_SkColorType) {
|
||||
if (!bitmap->copyTo(&converted, kN32_SkColorType)) {
|
||||
fail("Can't convert A8 to 8888.\n");
|
||||
return;
|
||||
}
|
||||
bitmap = &converted;
|
||||
}
|
||||
if (!SkImageEncoder::EncodeStream(&file, *bitmap, SkImageEncoder::kPNG_Type, 100)) {
|
||||
fail(SkStringPrintf("Can't encode PNG to %s.\n", path.c_str()));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!file.writeStream(data, len)) {
|
||||
fail(SkStringPrintf("Can't write to %s.\n", path.c_str()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Run all tasks in the same enclave serially on the same thread.
|
||||
// They can't possibly run concurrently with each other.
|
||||
static void run_enclave(SkTArray<Task>* tasks) {
|
||||
for (int i = 0; i < tasks->count(); i++) {
|
||||
Task::Run(tasks->begin() + i);
|
||||
}
|
||||
}
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
// Unit tests don't fit so well into the Src/Sink model, so we give them special treatment.
|
||||
|
||||
static struct : public skiatest::Reporter {
|
||||
void onReportFailed(const skiatest::Failure& failure) SK_OVERRIDE {
|
||||
SkString s;
|
||||
failure.getFailureString(&s);
|
||||
fail(s);
|
||||
JsonWriter::AddTestFailure(failure);
|
||||
}
|
||||
bool allowExtendedTest() const SK_OVERRIDE { return FLAGS_pathOpsExtended; }
|
||||
bool verbose() const SK_OVERRIDE { return FLAGS_veryVerbose; }
|
||||
} gTestReporter;
|
||||
|
||||
static SkTArray<SkAutoTDelete<skiatest::Test>, kMemcpyOK> gTests;
|
||||
|
||||
static void gather_tests() {
|
||||
if (!FLAGS_tests) {
|
||||
return;
|
||||
}
|
||||
for (const skiatest::TestRegistry* r = skiatest::TestRegistry::Head(); r; r = r->next()) {
|
||||
SkAutoTDelete<skiatest::Test> test(r->factory()(NULL));
|
||||
if (SkCommandLineFlags::ShouldSkip(FLAGS_match, test->getName())) {
|
||||
continue;
|
||||
}
|
||||
if (test->isGPUTest() /*&& !gpu_supported()*/) { // TEMPORARILY DISABLED
|
||||
continue;
|
||||
}
|
||||
if (!test->isGPUTest() && !FLAGS_cpu) {
|
||||
continue;
|
||||
}
|
||||
test->setReporter(&gTestReporter);
|
||||
gTests.push_back().reset(test.detach());
|
||||
}
|
||||
}
|
||||
|
||||
static void run_test(SkAutoTDelete<skiatest::Test>* t) {
|
||||
WallTimer timer;
|
||||
timer.start();
|
||||
skiatest::Test* test = t->get();
|
||||
if (!FLAGS_dryRun) {
|
||||
GrContextFactory grFactory;
|
||||
test->setGrContextFactory(&grFactory);
|
||||
test->run();
|
||||
if (!test->passed()) {
|
||||
fail(SkStringPrintf("test %s failed", test->getName()));
|
||||
}
|
||||
}
|
||||
timer.end();
|
||||
done(timer.fWall, "unit", "test", test->getName());
|
||||
}
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
int dm_main();
|
||||
int dm_main() {
|
||||
SetupCrashHandler();
|
||||
SkAutoGraphics ag;
|
||||
SkTaskGroup::Enabler enabled(FLAGS_threads);
|
||||
|
||||
if (FLAGS_dryRun || FLAGS_veryVerbose) {
|
||||
FLAGS_verbose = true;
|
||||
}
|
||||
#if SK_ENABLE_INST_COUNT
|
||||
gPrintInstCount = FLAGS_leaks;
|
||||
#endif
|
||||
gather_srcs();
|
||||
gather_sinks();
|
||||
gather_tests();
|
||||
|
||||
SkTArray<SkString> configs;
|
||||
for (int i = 0; i < FLAGS_config.count(); i++) {
|
||||
SkStrSplit(FLAGS_config[i], ", ", &configs);
|
||||
gPending = gSrcs.count() * gSinks.count() + gTests.count();
|
||||
SkDebugf("%d srcs * %d sinks + %d tests == %d tasks\n",
|
||||
gSrcs.count(), gSinks.count(), gTests.count(), gPending);
|
||||
|
||||
// We try to exploit as much parallelism as is safe. Most Src/Sink pairs run on any thread,
|
||||
// but Sinks that identify as part of a particular enclave run serially on a single thread.
|
||||
// Tests run on any thread, with a separate GrContextFactory for each GPU test.
|
||||
SkTArray<Task> enclaves[kNumEnclaves];
|
||||
for (int j = 0; j < gSinks.count(); j++) {
|
||||
SkTArray<Task>& tasks = enclaves[gSinks[j]->enclave()];
|
||||
for (int i = 0; i < gSrcs.count(); i++) {
|
||||
tasks.push_back(Task(gSrcs[i], gSinks[j]));
|
||||
}
|
||||
}
|
||||
|
||||
GrGLStandard gpuAPI = get_gl_standard();
|
||||
SK_COMPILE_ASSERT(kAnyThread_Enclave == 0, AnyThreadZero);
|
||||
SkTaskGroup tg;
|
||||
tg.batch( Task::Run, enclaves[0].begin(), enclaves[0].count());
|
||||
tg.batch(run_enclave, enclaves+1, kNumEnclaves-1);
|
||||
tg.batch( run_test, gTests.begin(), gTests.count());
|
||||
tg.wait();
|
||||
|
||||
SkTDArray<GMRegistry::Factory> gms;
|
||||
if (FLAGS_gms) {
|
||||
append_matching_factories<GM>(GMRegistry::Head(), &gms);
|
||||
// At this point we're back in single-threaded land.
|
||||
|
||||
if (!FLAGS_verbose) {
|
||||
SkDebugf("\n");
|
||||
}
|
||||
|
||||
SkTDArray<TestRegistry::Factory> tests;
|
||||
if (FLAGS_tests) {
|
||||
append_matching_factories<Test>(TestRegistry::Head(), &tests);
|
||||
JsonWriter::DumpJson();
|
||||
|
||||
if (gFailures.count() > 0) {
|
||||
SkDebugf("Failures:\n");
|
||||
for (int i = 0; i < gFailures.count(); i++) {
|
||||
SkDebugf("\t%s", gFailures[i].c_str());
|
||||
}
|
||||
SkDebugf("%d failures\n", gFailures.count());
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
SkTArray<SkString> skps;
|
||||
if (!FLAGS_skps.isEmpty()) {
|
||||
const char* suffixes[] = { "skp" };
|
||||
find_files(FLAGS_skps[0], suffixes, SK_ARRAY_COUNT(suffixes), &skps);
|
||||
if (gPending > 0) {
|
||||
SkDebugf("Hrm, we didn't seem to run everything we intended to! Please file a bug.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
SkTArray<SkString> images;
|
||||
if (!FLAGS_images.isEmpty()) {
|
||||
const char* suffixes[] = {
|
||||
"bmp", "gif", "jpg", "jpeg", "png", "webp", "ktx", "astc", "wbmp", "ico",
|
||||
"BMP", "GIF", "JPG", "JPEG", "PNG", "WEBP", "KTX", "ASTC", "WBMP", "ICO",
|
||||
};
|
||||
find_files(FLAGS_images[0], suffixes, SK_ARRAY_COUNT(suffixes), &images);
|
||||
}
|
||||
|
||||
SkDebugf("%d GMs x %d configs, %d tests, %d pictures, %d images\n",
|
||||
gms.count(), configs.count(), tests.count(), skps.count(), images.count());
|
||||
DM::Reporter reporter;
|
||||
|
||||
DM::TaskRunner tasks;
|
||||
kick_off_tests(tests, &reporter, &tasks);
|
||||
kick_off_gms(gms, configs, gpuAPI, &reporter, &tasks);
|
||||
kick_off_skps(skps, &reporter, &tasks);
|
||||
kick_off_images(images, &reporter, &tasks);
|
||||
tasks.wait();
|
||||
|
||||
DM::JsonWriter::DumpJson();
|
||||
|
||||
SkDebugf("\n");
|
||||
#ifdef SK_DEBUG
|
||||
if (FLAGS_portableFonts && FLAGS_reportUsedChars) {
|
||||
sk_tool_utils::report_used_chars();
|
||||
}
|
||||
#endif
|
||||
|
||||
SkTArray<SkString> failures;
|
||||
reporter.getFailures(&failures);
|
||||
report_failures(failures);
|
||||
return failures.count() > 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL)
|
||||
|
@ -1,56 +0,0 @@
|
||||
#include "DMCpuGMTask.h"
|
||||
#include "DMPipeTask.h"
|
||||
#include "DMQuiltTask.h"
|
||||
#include "DMSerializeTask.h"
|
||||
#include "DMUtil.h"
|
||||
#include "DMWriteTask.h"
|
||||
|
||||
namespace DM {
|
||||
|
||||
CpuGMTask::CpuGMTask(const char* config,
|
||||
Reporter* reporter,
|
||||
TaskRunner* taskRunner,
|
||||
skiagm::GMRegistry::Factory gmFactory,
|
||||
SkColorType colorType)
|
||||
: CpuTask(reporter, taskRunner)
|
||||
, fGMFactory(gmFactory)
|
||||
, fGM(fGMFactory(NULL))
|
||||
, fName(UnderJoin(fGM->getName(), config))
|
||||
, fColorType(colorType)
|
||||
{}
|
||||
|
||||
void CpuGMTask::draw() {
|
||||
SkBitmap bm;
|
||||
AllocatePixels(fColorType, fGM->getISize().width(), fGM->getISize().height(), &bm);
|
||||
|
||||
SkCanvas canvas(bm);
|
||||
CanvasPreflight(&canvas);
|
||||
canvas.concat(fGM->getInitialTransform());
|
||||
fGM->draw(&canvas);
|
||||
canvas.flush();
|
||||
|
||||
#define SPAWN(ChildTask, ...) this->spawnChild(SkNEW_ARGS(ChildTask, (*this, __VA_ARGS__)))
|
||||
SPAWN(PipeTask, fGMFactory(NULL), bm, PipeTask::kInProcess_Mode);
|
||||
SPAWN(PipeTask, fGMFactory(NULL), bm, PipeTask::kCrossProcess_Mode);
|
||||
SPAWN(PipeTask, fGMFactory(NULL), bm, PipeTask::kSharedAddress_Mode);
|
||||
|
||||
SPAWN(QuiltTask, fGMFactory(NULL), bm, QuiltTask::kNone_BBH);
|
||||
SPAWN(QuiltTask, fGMFactory(NULL), bm, QuiltTask::kRTree_BBH);
|
||||
|
||||
SPAWN(SerializeTask, fGMFactory(NULL), bm);
|
||||
|
||||
SPAWN(WriteTask, "GM", bm);
|
||||
#undef SPAWN
|
||||
}
|
||||
|
||||
bool CpuGMTask::shouldSkip() const {
|
||||
if (kRGB_565_SkColorType == fColorType && (fGM->getFlags() & skiagm::GM::kSkip565_Flag)) {
|
||||
return true;
|
||||
}
|
||||
if (fGM->getFlags() & skiagm::GM::kGPUOnly_Flag) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace DM
|
@ -1,38 +0,0 @@
|
||||
#ifndef DMCpuGMTask_DEFINED
|
||||
#define DMCpuGMTask_DEFINED
|
||||
|
||||
#include "DMReporter.h"
|
||||
#include "DMTask.h"
|
||||
#include "DMTaskRunner.h"
|
||||
#include "SkBitmap.h"
|
||||
#include "SkString.h"
|
||||
#include "SkTemplates.h"
|
||||
#include "gm.h"
|
||||
|
||||
// This is the main entry point for drawing GMs with the CPU. Commandline
|
||||
// flags control whether this kicks off various comparison tasks when done.
|
||||
|
||||
namespace DM {
|
||||
|
||||
class CpuGMTask : public CpuTask {
|
||||
public:
|
||||
CpuGMTask(const char* config,
|
||||
Reporter*,
|
||||
TaskRunner*,
|
||||
skiagm::GMRegistry::Factory,
|
||||
SkColorType);
|
||||
|
||||
void draw() SK_OVERRIDE;
|
||||
bool shouldSkip() const SK_OVERRIDE;
|
||||
SkString name() const SK_OVERRIDE { return fName; }
|
||||
|
||||
private:
|
||||
skiagm::GMRegistry::Factory fGMFactory;
|
||||
SkAutoTDelete<skiagm::GM> fGM;
|
||||
const SkString fName;
|
||||
const SkColorType fColorType;
|
||||
};
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMCpuGMTask_DEFINED
|
@ -1,67 +0,0 @@
|
||||
#include "DMGpuGMTask.h"
|
||||
#include "DMUtil.h"
|
||||
#include "DMWriteTask.h"
|
||||
#include "SkCommonFlags.h"
|
||||
#include "SkSurface.h"
|
||||
#include "SkTLS.h"
|
||||
|
||||
namespace DM {
|
||||
|
||||
GpuGMTask::GpuGMTask(const char* config,
|
||||
Reporter* reporter,
|
||||
TaskRunner* taskRunner,
|
||||
skiagm::GMRegistry::Factory gmFactory,
|
||||
GrContextFactory::GLContextType contextType,
|
||||
GrGLStandard gpuAPI,
|
||||
int sampleCount,
|
||||
bool useDFText)
|
||||
: GpuTask(reporter, taskRunner)
|
||||
, fGM(gmFactory(NULL))
|
||||
, fName(UnderJoin(fGM->getName(), config))
|
||||
, fContextType(contextType)
|
||||
, fGpuAPI(gpuAPI)
|
||||
, fSampleCount(sampleCount)
|
||||
, fUseDFText(useDFText)
|
||||
{}
|
||||
|
||||
static bool gAlreadyWarned[GrContextFactory::kGLContextTypeCnt][kGrGLStandardCnt];
|
||||
|
||||
void GpuGMTask::draw(GrContextFactory* grFactory) {
|
||||
SkImageInfo info = SkImageInfo::Make(SkScalarCeilToInt(fGM->width()),
|
||||
SkScalarCeilToInt(fGM->height()),
|
||||
kN32_SkColorType,
|
||||
kPremul_SkAlphaType);
|
||||
SkAutoTUnref<SkSurface> surface(NewGpuSurface(grFactory, fContextType, fGpuAPI, info,
|
||||
fSampleCount, fUseDFText));
|
||||
if (!surface) {
|
||||
if (!gAlreadyWarned[fContextType][fGpuAPI]) {
|
||||
SkDebugf("FYI: couldn't create GPU context, type %d API %d. Will skip.\n",
|
||||
fContextType, fGpuAPI);
|
||||
gAlreadyWarned[fContextType][fGpuAPI] = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
SkCanvas* canvas = surface->getCanvas();
|
||||
CanvasPreflight(canvas);
|
||||
|
||||
canvas->concat(fGM->getInitialTransform());
|
||||
fGM->draw(canvas);
|
||||
canvas->flush();
|
||||
#if GR_CACHE_STATS && SK_SUPPORT_GPU
|
||||
if (FLAGS_veryVerbose) {
|
||||
grFactory->get(fContextType)->printCacheStats();
|
||||
}
|
||||
#endif
|
||||
|
||||
SkBitmap bitmap;
|
||||
bitmap.setInfo(info);
|
||||
canvas->readPixels(&bitmap, 0, 0);
|
||||
|
||||
this->spawnChild(SkNEW_ARGS(WriteTask, (*this, "GM", bitmap)));
|
||||
}
|
||||
|
||||
bool GpuGMTask::shouldSkip() const {
|
||||
return kGPUDisabled || SkToBool(fGM->getFlags() & skiagm::GM::kSkipGPU_Flag);
|
||||
}
|
||||
|
||||
} // namespace DM
|
@ -1,43 +0,0 @@
|
||||
#ifndef DMGpuGMTask_DEFINED
|
||||
#define DMGpuGMTask_DEFINED
|
||||
|
||||
#include "DMGpuSupport.h"
|
||||
#include "DMReporter.h"
|
||||
#include "DMTask.h"
|
||||
#include "DMTaskRunner.h"
|
||||
#include "SkBitmap.h"
|
||||
#include "SkString.h"
|
||||
#include "SkTemplates.h"
|
||||
#include "gm.h"
|
||||
|
||||
// This is the main entry point for drawing GMs with the GPU.
|
||||
|
||||
namespace DM {
|
||||
|
||||
class GpuGMTask : public GpuTask {
|
||||
public:
|
||||
GpuGMTask(const char* config,
|
||||
Reporter*,
|
||||
TaskRunner*,
|
||||
skiagm::GMRegistry::Factory,
|
||||
GrContextFactory::GLContextType,
|
||||
GrGLStandard gpuAPI,
|
||||
int sampleCount,
|
||||
bool useDFText);
|
||||
|
||||
void draw(GrContextFactory*) SK_OVERRIDE;
|
||||
bool shouldSkip() const SK_OVERRIDE;
|
||||
SkString name() const SK_OVERRIDE { return fName; }
|
||||
|
||||
private:
|
||||
SkAutoTDelete<skiagm::GM> fGM;
|
||||
const SkString fName;
|
||||
const GrContextFactory::GLContextType fContextType;
|
||||
GrGLStandard fGpuAPI;
|
||||
const int fSampleCount;
|
||||
const bool fUseDFText;
|
||||
};
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMGpuGMTask_DEFINED
|
@ -1,78 +0,0 @@
|
||||
#include "DMImageTask.h"
|
||||
#include "DMUtil.h"
|
||||
#include "DMWriteTask.h"
|
||||
#include "SkImageDecoder.h"
|
||||
#include "SkRandom.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
namespace DM {
|
||||
|
||||
// This converts file names like "mandrill_128.r11.ktx" into
|
||||
// "mandrill-128-r11_ktx" or "mandrill-128-r11-5-subsets_ktx".
|
||||
static SkString task_name(SkString filename, int subsets) {
|
||||
const char* ext = strrchr(filename.c_str(), '.');
|
||||
SkString name(filename.c_str(), ext - filename.c_str());
|
||||
if (subsets > 0) {
|
||||
name.appendf("_%d_subsets", subsets);
|
||||
}
|
||||
name = FileToTaskName(name); // Promote any stray '.' in the filename to '_'.
|
||||
name.append(ext); // Tack on the extension, including the '.'.
|
||||
return FileToTaskName(name); // Promote that last '.' to '_', other '_' to '-'.
|
||||
}
|
||||
|
||||
ImageTask::ImageTask(Reporter* r, TaskRunner* t, const SkData* encoded, SkString name, int subsets)
|
||||
: CpuTask(r, t)
|
||||
, fEncoded(SkRef(encoded))
|
||||
, fName(task_name(name, subsets))
|
||||
, fSubsets(subsets) {}
|
||||
|
||||
void ImageTask::draw() {
|
||||
if (fSubsets == 0) {
|
||||
// Decoding the whole image is considerably simpler than decoding subsets!
|
||||
SkBitmap bitmap;
|
||||
if (!SkImageDecoder::DecodeMemory(fEncoded->data(), fEncoded->size(), &bitmap)) {
|
||||
return this->fail("Can't DecodeMemory");
|
||||
}
|
||||
this->spawnChild(SkNEW_ARGS(WriteTask, (*this, "image", bitmap)));
|
||||
return;
|
||||
}
|
||||
|
||||
SkMemoryStream stream(fEncoded->data(), fEncoded->size());
|
||||
SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(&stream));
|
||||
if (!decoder) {
|
||||
return this->fail("Can't find good decoder.");
|
||||
}
|
||||
|
||||
int w,h;
|
||||
if (!decoder->buildTileIndex(&stream, &w, &h) || w*h == 1) {
|
||||
return; // Subset decoding is not always supported.
|
||||
}
|
||||
|
||||
SkBitmap composite;
|
||||
composite.allocN32Pixels(w,h); // We're lazy here and just always use native 8888.
|
||||
composite.eraseColor(SK_ColorTRANSPARENT);
|
||||
SkCanvas canvas(composite);
|
||||
|
||||
SkRandom rand;
|
||||
for (int i = 0; i < fSubsets; i++) {
|
||||
SkIRect rect;
|
||||
do {
|
||||
rect.fLeft = rand.nextULessThan(w);
|
||||
rect.fTop = rand.nextULessThan(h);
|
||||
rect.fRight = rand.nextULessThan(w);
|
||||
rect.fBottom = rand.nextULessThan(h);
|
||||
rect.sort();
|
||||
} while (rect.isEmpty());
|
||||
|
||||
SkBitmap subset;
|
||||
if (!decoder->decodeSubset(&subset, rect, kN32_SkColorType)) {
|
||||
return this->fail("Could not decode subset.");
|
||||
}
|
||||
canvas.drawBitmap(subset, SkIntToScalar(rect.fLeft), SkIntToScalar(rect.fTop));
|
||||
}
|
||||
canvas.flush();
|
||||
this->spawnChild(SkNEW_ARGS(WriteTask, (*this, "image", composite)));
|
||||
}
|
||||
|
||||
} // namespace DM
|
@ -1,30 +0,0 @@
|
||||
#ifndef DMImageTask_DEFINED
|
||||
#define DMImageTask_DEFINED
|
||||
|
||||
#include "DMReporter.h"
|
||||
#include "DMTask.h"
|
||||
#include "DMTaskRunner.h"
|
||||
#include "SkData.h"
|
||||
#include "SkString.h"
|
||||
|
||||
// Decode an image into its natural bitmap, perhaps decoding random subsets.
|
||||
|
||||
namespace DM {
|
||||
|
||||
class ImageTask : public CpuTask {
|
||||
public:
|
||||
ImageTask(Reporter*, TaskRunner*, const SkData*, SkString name, int subsets = 0);
|
||||
|
||||
void draw() SK_OVERRIDE;
|
||||
bool shouldSkip() const SK_OVERRIDE { return false; }
|
||||
SkString name() const SK_OVERRIDE { return fName; }
|
||||
|
||||
private:
|
||||
SkAutoTUnref<const SkData> fEncoded;
|
||||
const SkString fName;
|
||||
int fSubsets;
|
||||
};
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMImageTask_DEFINED
|
@ -52,8 +52,8 @@ void JsonWriter::DumpJson() {
|
||||
Json::Value result;
|
||||
result["key"]["name"] = gBitmapResults[i].name.c_str();
|
||||
result["key"]["config"] = gBitmapResults[i].config.c_str();
|
||||
result["key"]["mode"] = gBitmapResults[i].mode.c_str();
|
||||
result["key"]["source_type"] = gBitmapResults[i].sourceType.c_str();
|
||||
result["ext"] = gBitmapResults[i].ext.c_str();
|
||||
result["md5"] = gBitmapResults[i].md5.c_str();
|
||||
|
||||
root["results"].append(result);
|
||||
|
@ -23,11 +23,11 @@ public:
|
||||
* Info describing a single run.
|
||||
*/
|
||||
struct BitmapResult {
|
||||
SkString name; // E.g. "ninepatch-stretch", "desk-gws_skp"
|
||||
SkString config; // "gpu", "8888"
|
||||
SkString mode; // "direct", "default-tilegrid", "pipe"
|
||||
SkString sourceType; // "GM", "SKP"
|
||||
SkString name; // E.g. "ninepatch-stretch", "desk_gws.skp"
|
||||
SkString config; // "gpu", "8888", "serialize", "pipe"
|
||||
SkString sourceType; // "gm", "skp", "image"
|
||||
SkString md5; // In ASCII, so 32 bytes long.
|
||||
SkString ext; // Extension of file we wrote: "png", "pdf", ...
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "DMPDFRasterizeTask.h"
|
||||
#include "DMUtil.h"
|
||||
#include "DMWriteTask.h"
|
||||
#include "SkBitmap.h"
|
||||
#include "SkCanvas.h"
|
||||
#include "SkStream.h"
|
||||
|
||||
namespace DM {
|
||||
|
||||
PDFRasterizeTask::PDFRasterizeTask(const Task& parent,
|
||||
SkStreamAsset* pdf,
|
||||
RasterizePdfProc proc)
|
||||
: CpuTask(parent)
|
||||
, fName(UnderJoin(parent.name().c_str(), "rasterize"))
|
||||
, fPdf(pdf)
|
||||
, fRasterize(proc) {
|
||||
SkASSERT(fPdf.get());
|
||||
SkASSERT(fPdf->unique());
|
||||
}
|
||||
|
||||
void PDFRasterizeTask::draw() {
|
||||
SkBitmap bitmap;
|
||||
|
||||
if (fRasterize(fPdf.get(), &bitmap)) {
|
||||
this->spawnChild(SkNEW_ARGS(WriteTask, (*this, "PDF", bitmap)));
|
||||
} else {
|
||||
this->fail();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace DM
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#ifndef DMPDFRasterizeTask_DEFINED
|
||||
#define DMPDFRasterizeTask_DEFINED
|
||||
|
||||
#include "DMTask.h"
|
||||
#include "SkBitmap.h"
|
||||
#include "SkData.h"
|
||||
#include "SkStream.h"
|
||||
#include "SkString.h"
|
||||
#include "SkTemplates.h"
|
||||
|
||||
namespace DM {
|
||||
|
||||
typedef bool (*RasterizePdfProc)(SkStream* pdf, SkBitmap* output);
|
||||
|
||||
class PDFRasterizeTask : public CpuTask {
|
||||
public:
|
||||
// takes ownership of SkStreamAsset.
|
||||
PDFRasterizeTask(const Task& parent,
|
||||
SkStreamAsset* pdf,
|
||||
RasterizePdfProc);
|
||||
|
||||
void draw() SK_OVERRIDE;
|
||||
bool shouldSkip() const SK_OVERRIDE { return NULL == fRasterize; }
|
||||
SkString name() const SK_OVERRIDE { return fName; }
|
||||
|
||||
private:
|
||||
const SkString fName;
|
||||
SkAutoTDelete<SkStreamAsset> fPdf;
|
||||
RasterizePdfProc fRasterize;
|
||||
};
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMPDFRasterizeTask_DEFINED
|
106
dm/DMPDFTask.cpp
106
dm/DMPDFTask.cpp
@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "DMPDFTask.h"
|
||||
#include "DMPDFRasterizeTask.h"
|
||||
#include "DMUtil.h"
|
||||
#include "DMWriteTask.h"
|
||||
#include "SkCommandLineFlags.h"
|
||||
#include "SkDocument.h"
|
||||
|
||||
// The PDF backend is not threadsafe. If you run dm with --pdf repeatedly, you
|
||||
// will quickly find yourself crashed. (while catchsegv out/Release/dm;; end).
|
||||
//
|
||||
// TODO(mtklein): re-enable by default, maybe moving to its own single thread.
|
||||
DEFINE_bool(pdf, false, "PDF backend master switch.");
|
||||
|
||||
namespace DM {
|
||||
|
||||
PDFTask::PDFTask(const char* config,
|
||||
Reporter* reporter,
|
||||
TaskRunner* taskRunner,
|
||||
skiagm::GMRegistry::Factory factory,
|
||||
RasterizePdfProc rasterizePdfProc)
|
||||
: CpuTask(reporter, taskRunner)
|
||||
, fGM(factory(NULL))
|
||||
, fName(UnderJoin(fGM->getName(), config))
|
||||
, fRasterize(rasterizePdfProc) {}
|
||||
|
||||
PDFTask::PDFTask(Reporter* reporter,
|
||||
TaskRunner* taskRunner,
|
||||
const SkPicture* picture,
|
||||
SkString filename,
|
||||
RasterizePdfProc rasterizePdfProc)
|
||||
: CpuTask(reporter, taskRunner)
|
||||
, fPicture(SkRef(picture))
|
||||
, fName(UnderJoin(FileToTaskName(filename).c_str(), "pdf"))
|
||||
, fRasterize(rasterizePdfProc) {}
|
||||
|
||||
namespace {
|
||||
|
||||
class SinglePagePDF {
|
||||
public:
|
||||
SinglePagePDF(SkScalar width, SkScalar height)
|
||||
: fDocument(SkDocument::CreatePDF(&fWriteStream))
|
||||
, fCanvas(fDocument->beginPage(width, height)) {}
|
||||
|
||||
SkCanvas* canvas() { return fCanvas; }
|
||||
|
||||
SkStreamAsset* end() {
|
||||
fCanvas->flush();
|
||||
fDocument->endPage();
|
||||
fDocument->close();
|
||||
return fWriteStream.detachAsStream();
|
||||
}
|
||||
|
||||
private:
|
||||
SkDynamicMemoryWStream fWriteStream;
|
||||
SkAutoTUnref<SkDocument> fDocument;
|
||||
SkCanvas* fCanvas;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void PDFTask::draw() {
|
||||
SkAutoTDelete<SkStreamAsset> pdfData;
|
||||
bool rasterize = true;
|
||||
if (fGM.get()) {
|
||||
rasterize = 0 == (fGM->getFlags() & skiagm::GM::kSkipPDFRasterization_Flag);
|
||||
SinglePagePDF pdf(fGM->width(), fGM->height());
|
||||
CanvasPreflight(pdf.canvas());
|
||||
//TODO(mtklein): GM doesn't do this. Why not?
|
||||
//pdf.canvas()->concat(fGM->getInitialTransform());
|
||||
fGM->draw(pdf.canvas());
|
||||
pdfData.reset(pdf.end());
|
||||
} else {
|
||||
SinglePagePDF pdf(fPicture->cullRect().width(), fPicture->cullRect().height());
|
||||
CanvasPreflight(pdf.canvas());
|
||||
fPicture->playback(pdf.canvas());
|
||||
pdfData.reset(pdf.end());
|
||||
}
|
||||
|
||||
SkASSERT(pdfData.get());
|
||||
if (rasterize) {
|
||||
this->spawnChild(SkNEW_ARGS(PDFRasterizeTask,
|
||||
(*this, pdfData->duplicate(), fRasterize)));
|
||||
}
|
||||
const char* sourceType = fGM.get() ? "GM" : "SKP";
|
||||
this->spawnChild(SkNEW_ARGS(WriteTask,
|
||||
(*this, sourceType, pdfData->duplicate(), ".pdf")));
|
||||
}
|
||||
|
||||
bool PDFTask::shouldSkip() const {
|
||||
if (!FLAGS_pdf) {
|
||||
return true;
|
||||
}
|
||||
if (fGM.get() && 0 != (fGM->getFlags() & skiagm::GM::kSkipPDF_Flag)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace DM
|
@ -1,47 +0,0 @@
|
||||
#ifndef DMPDFTask_DEFINED
|
||||
#define DMPDFTask_DEFINED
|
||||
|
||||
#include "DMPDFRasterizeTask.h"
|
||||
#include "DMTask.h"
|
||||
#include "SkBitmap.h"
|
||||
#include "SkPicture.h"
|
||||
#include "SkString.h"
|
||||
#include "SkTemplates.h"
|
||||
#include "gm.h"
|
||||
|
||||
namespace DM {
|
||||
|
||||
// This task renders a GM or SKP using Skia's PDF backend.
|
||||
// If rasterizePdfProc is non-NULL, it will spawn a PDFRasterizeTask.
|
||||
class PDFTask : public CpuTask {
|
||||
public:
|
||||
PDFTask(const char*,
|
||||
Reporter*,
|
||||
TaskRunner*,
|
||||
skiagm::GMRegistry::Factory,
|
||||
RasterizePdfProc);
|
||||
|
||||
PDFTask(Reporter*,
|
||||
TaskRunner*,
|
||||
const SkPicture*,
|
||||
SkString name,
|
||||
RasterizePdfProc);
|
||||
|
||||
void draw() SK_OVERRIDE;
|
||||
|
||||
bool shouldSkip() const SK_OVERRIDE;
|
||||
|
||||
SkString name() const SK_OVERRIDE { return fName; }
|
||||
|
||||
private:
|
||||
// One of these two will be set.
|
||||
SkAutoTDelete<skiagm::GM> fGM;
|
||||
SkAutoTUnref<const SkPicture> fPicture;
|
||||
|
||||
const SkString fName;
|
||||
RasterizePdfProc fRasterize;
|
||||
};
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMPDFTask_DEFINED
|
@ -1,83 +0,0 @@
|
||||
#include "DMPipeTask.h"
|
||||
#include "DMUtil.h"
|
||||
#include "DMWriteTask.h"
|
||||
|
||||
#include "SamplePipeControllers.h"
|
||||
#include "SkCommandLineFlags.h"
|
||||
#include "SkGPipe.h"
|
||||
|
||||
DEFINE_bool(pipe, true, "If true, check several pipe variants against the reference bitmap.");
|
||||
|
||||
namespace DM {
|
||||
|
||||
static uint32_t get_flags(PipeTask::Mode mode) {
|
||||
uint32_t flags = 0;
|
||||
if (mode != PipeTask::kInProcess_Mode) {
|
||||
flags |= SkGPipeWriter::kCrossProcess_Flag;
|
||||
}
|
||||
if (mode == PipeTask::kSharedAddress_Mode) {
|
||||
flags |= SkGPipeWriter::kSharedAddressSpace_Flag;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
static const char* get_name(const uint32_t flags) {
|
||||
if (flags & SkGPipeWriter::kCrossProcess_Flag &&
|
||||
flags & SkGPipeWriter::kSharedAddressSpace_Flag) {
|
||||
return "shared-address-space-pipe";
|
||||
} else if (flags & SkGPipeWriter::kCrossProcess_Flag) {
|
||||
return "cross-process-pipe";
|
||||
} else {
|
||||
return "pipe";
|
||||
}
|
||||
}
|
||||
|
||||
PipeTask::PipeTask(const Task& parent,
|
||||
skiagm::GM* gm,
|
||||
SkBitmap reference,
|
||||
Mode mode)
|
||||
: CpuTask(parent)
|
||||
, fFlags(get_flags(mode))
|
||||
, fName(UnderJoin(parent.name().c_str(), get_name(fFlags)))
|
||||
, fGM(gm)
|
||||
, fReference(reference)
|
||||
{}
|
||||
|
||||
void PipeTask::draw() {
|
||||
SkBitmap bitmap;
|
||||
AllocatePixels(fReference, &bitmap);
|
||||
|
||||
SkCanvas canvas(bitmap);
|
||||
PipeController pipeController(&canvas, &SkImageDecoder::DecodeMemory);
|
||||
SkGPipeWriter writer;
|
||||
|
||||
SkCanvas* pipeCanvas = writer.startRecording(&pipeController,
|
||||
fFlags,
|
||||
bitmap.width(),
|
||||
bitmap.height());
|
||||
CanvasPreflight(pipeCanvas);
|
||||
pipeCanvas->concat(fGM->getInitialTransform());
|
||||
fGM->draw(pipeCanvas);
|
||||
writer.endRecording();
|
||||
|
||||
if (!BitmapsEqual(bitmap, fReference)) {
|
||||
this->fail();
|
||||
this->spawnChild(SkNEW_ARGS(WriteTask, (*this, "GM", bitmap)));
|
||||
}
|
||||
}
|
||||
|
||||
bool PipeTask::shouldSkip() const {
|
||||
if (!FLAGS_pipe) {
|
||||
return true;
|
||||
}
|
||||
if (fGM->getFlags() & skiagm::GM::kSkipPipe_Flag) {
|
||||
return true;
|
||||
}
|
||||
if (fFlags == SkGPipeWriter::kCrossProcess_Flag &&
|
||||
fGM->getFlags() & skiagm::GM::kSkipPipeCrossProcess_Flag) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace DM
|
@ -1,41 +0,0 @@
|
||||
#ifndef DMPipeTask_DEFINED
|
||||
#define DMPipeTask_DEFINED
|
||||
|
||||
#include "DMTask.h"
|
||||
#include "SkBitmap.h"
|
||||
#include "SkString.h"
|
||||
#include "SkTemplates.h"
|
||||
#include "gm.h"
|
||||
|
||||
// Sends a GM through a pipe, draws it, and compares against the reference bitmap.
|
||||
|
||||
namespace DM {
|
||||
|
||||
class PipeTask : public CpuTask {
|
||||
|
||||
public:
|
||||
enum Mode {
|
||||
kInProcess_Mode,
|
||||
kCrossProcess_Mode,
|
||||
kSharedAddress_Mode,
|
||||
};
|
||||
|
||||
PipeTask(const Task& parent, // PipeTask must be a child task. Pass its parent here.
|
||||
skiagm::GM*, // GM to run through a pipe. Takes ownership.
|
||||
SkBitmap reference, // Bitmap to compare pipe results to.
|
||||
Mode);
|
||||
|
||||
void draw() SK_OVERRIDE;
|
||||
bool shouldSkip() const SK_OVERRIDE;
|
||||
SkString name() const SK_OVERRIDE { return fName; }
|
||||
|
||||
private:
|
||||
const uint32_t fFlags;
|
||||
const SkString fName;
|
||||
SkAutoTDelete<skiagm::GM> fGM;
|
||||
const SkBitmap fReference;
|
||||
};
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMPipeTask_DEFINED
|
@ -1,98 +0,0 @@
|
||||
#include "DMQuiltTask.h"
|
||||
#include "DMUtil.h"
|
||||
#include "DMWriteTask.h"
|
||||
|
||||
#include "SkBBHFactory.h"
|
||||
#include "SkCommandLineFlags.h"
|
||||
#include "SkPicture.h"
|
||||
#include "SkTaskGroup.h"
|
||||
|
||||
DEFINE_bool(quilt, true, "If true, draw GM via a picture into a quilt of small tiles and compare.");
|
||||
DEFINE_int32(quiltTile, 256, "Dimension of (square) quilt tile.");
|
||||
|
||||
namespace DM {
|
||||
|
||||
static const char* kBBHs[] = { "nobbh", "rtree", "tilegrid" };
|
||||
QuiltTask::QuiltTask(const Task& parent, skiagm::GM* gm, SkBitmap reference, QuiltTask::BBH bbh)
|
||||
: CpuTask(parent)
|
||||
, fBBH(bbh)
|
||||
, fName(UnderJoin(parent.name().c_str(), kBBHs[bbh]))
|
||||
, fGM(gm)
|
||||
, fReference(reference)
|
||||
{}
|
||||
|
||||
static int tiles_needed(int fullDimension, int tileDimension) {
|
||||
return (fullDimension + tileDimension - 1) / tileDimension;
|
||||
}
|
||||
|
||||
struct DrawTileArgs {
|
||||
int x, y;
|
||||
const SkPicture* picture;
|
||||
SkBitmap* quilt;
|
||||
};
|
||||
|
||||
static void draw_tile(DrawTileArgs* arg) {
|
||||
const DrawTileArgs& a = *arg;
|
||||
SkBitmap tile;
|
||||
a.quilt->extractSubset(&tile, SkIRect::MakeXYWH(a.x, a.y, FLAGS_quiltTile, FLAGS_quiltTile));
|
||||
SkCanvas tileCanvas(tile);
|
||||
tileCanvas.translate(SkIntToScalar(-a.x), SkIntToScalar(-a.y));
|
||||
a.picture->playback(&tileCanvas);
|
||||
tileCanvas.flush();
|
||||
}
|
||||
|
||||
void QuiltTask::draw() {
|
||||
SkAutoTDelete<SkBBHFactory> factory;
|
||||
switch (fBBH) {
|
||||
case kNone_BBH: break;
|
||||
case kRTree_BBH:
|
||||
factory.reset(SkNEW(SkRTreeFactory));
|
||||
break;
|
||||
}
|
||||
|
||||
// A couple GMs draw wrong when using a bounding box hierarchy.
|
||||
// This almost certainly means we have a bug to fix, but for now
|
||||
// just draw without a bounding box hierarchy.
|
||||
if (fGM->getFlags() & skiagm::GM::kNoBBH_Flag) {
|
||||
factory.reset(NULL);
|
||||
}
|
||||
|
||||
SkAutoTUnref<const SkPicture> recorded(RecordPicture(fGM.get(), factory.get()));
|
||||
|
||||
SkBitmap full;
|
||||
AllocatePixels(fReference, &full);
|
||||
|
||||
if (fGM->getFlags() & skiagm::GM::kSkipTiled_Flag) {
|
||||
// Some GMs don't draw exactly the same when tiled. Draw them in one go.
|
||||
SkCanvas canvas(full);
|
||||
recorded->playback(&canvas);
|
||||
canvas.flush();
|
||||
} else {
|
||||
// Draw tiles in parallel into the same bitmap, simulating aggressive impl-side painting.
|
||||
int xTiles = tiles_needed(full.width(), FLAGS_quiltTile),
|
||||
yTiles = tiles_needed(full.height(), FLAGS_quiltTile);
|
||||
SkTDArray<DrawTileArgs> args;
|
||||
args.setCount(xTiles*yTiles);
|
||||
for (int y = 0; y < yTiles; y++) {
|
||||
for (int x = 0; x < xTiles; x++) {
|
||||
DrawTileArgs arg = { x*FLAGS_quiltTile, y*FLAGS_quiltTile, recorded, &full };
|
||||
args[y*xTiles + x] = arg;
|
||||
}
|
||||
}
|
||||
SkTaskGroup().batch(draw_tile, args.begin(), args.count());
|
||||
}
|
||||
|
||||
if (!BitmapsEqual(full, fReference)) {
|
||||
this->fail();
|
||||
this->spawnChild(SkNEW_ARGS(WriteTask, (*this, "GM", full)));
|
||||
}
|
||||
}
|
||||
|
||||
bool QuiltTask::shouldSkip() const {
|
||||
if (fGM->getFlags() & skiagm::GM::kSkipPicture_Flag) {
|
||||
return true;
|
||||
}
|
||||
return !FLAGS_quilt;
|
||||
}
|
||||
|
||||
} // namespace DM
|
@ -1,39 +0,0 @@
|
||||
#ifndef DMQuiltTask_DEFINED
|
||||
#define DMQuiltTask_DEFINED
|
||||
|
||||
#include "DMTask.h"
|
||||
#include "SkBitmap.h"
|
||||
#include "SkString.h"
|
||||
#include "SkTemplates.h"
|
||||
#include "gm.h"
|
||||
|
||||
// Records a GM through an SkPicture, draws it in tiles, and compares against the reference bitmap.
|
||||
|
||||
namespace DM {
|
||||
|
||||
class QuiltTask : public CpuTask {
|
||||
public:
|
||||
enum BBH {
|
||||
kNone_BBH,
|
||||
kRTree_BBH,
|
||||
};
|
||||
|
||||
QuiltTask(const Task& parent, // QuiltTask must be a child task. Pass its parent here.
|
||||
skiagm::GM*, // GM to run through a picture. Takes ownership.
|
||||
SkBitmap reference, // Bitmap to compare picture replay results to.
|
||||
BBH);
|
||||
|
||||
void draw() SK_OVERRIDE;
|
||||
bool shouldSkip() const SK_OVERRIDE;
|
||||
SkString name() const SK_OVERRIDE { return fName; }
|
||||
|
||||
private:
|
||||
const BBH fBBH;
|
||||
const SkString fName;
|
||||
SkAutoTDelete<skiagm::GM> fGM;
|
||||
const SkBitmap fReference;
|
||||
};
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMReplayTask_DEFINED
|
@ -1,46 +0,0 @@
|
||||
#include "DMReporter.h"
|
||||
|
||||
#include "SkDynamicAnnotations.h"
|
||||
#include "SkCommonFlags.h"
|
||||
#include "OverwriteLine.h"
|
||||
#include "ProcStats.h"
|
||||
|
||||
namespace DM {
|
||||
|
||||
void Reporter::printStatus(SkString name, SkMSec timeMs) const {
|
||||
if (FLAGS_quiet) {
|
||||
return;
|
||||
}
|
||||
|
||||
// It's okay if these are a little off---they're just for show---so we can read unprotectedly.
|
||||
const int32_t failed = SK_ANNOTATE_UNPROTECTED_READ(fFailed);
|
||||
const int32_t pending = SK_ANNOTATE_UNPROTECTED_READ(fPending) - 1;
|
||||
|
||||
SkString status;
|
||||
status.printf("%s%d tasks left", FLAGS_verbose ? "\n" : kSkOverwriteLine, pending);
|
||||
if (failed > 0) {
|
||||
status.appendf(", %d failed", failed);
|
||||
}
|
||||
if (FLAGS_verbose) {
|
||||
int max_rss_mb = sk_tools::getMaxResidentSetSizeMB();
|
||||
if (max_rss_mb >= 0) {
|
||||
status.appendf("\t%4dM peak", max_rss_mb);
|
||||
}
|
||||
status.appendf("\t%5dms\t%s", timeMs, name.c_str());
|
||||
}
|
||||
SkDebugf("%s", status.c_str());
|
||||
}
|
||||
|
||||
void Reporter::fail(SkString msg) {
|
||||
sk_atomic_inc(&fFailed);
|
||||
|
||||
SkAutoMutexAcquire writer(&fMutex);
|
||||
fFailures.push_back(msg);
|
||||
}
|
||||
|
||||
void Reporter::getFailures(SkTArray<SkString>* failures) const {
|
||||
SkAutoMutexAcquire reader(&fMutex);
|
||||
*failures = fFailures;
|
||||
}
|
||||
|
||||
} // namespace DM
|
@ -1,36 +0,0 @@
|
||||
#ifndef DMReporter_DEFINED
|
||||
#define DMReporter_DEFINED
|
||||
|
||||
#include "SkString.h"
|
||||
#include "SkTArray.h"
|
||||
#include "SkThread.h"
|
||||
#include "SkTime.h"
|
||||
#include "SkTypes.h"
|
||||
|
||||
// Used to report status changes including failures. All public methods are threadsafe.
|
||||
namespace DM {
|
||||
|
||||
class Reporter : SkNoncopyable {
|
||||
public:
|
||||
Reporter() : fPending(0), fFailed(0) {}
|
||||
|
||||
void taskCreated() { sk_atomic_inc(&fPending); }
|
||||
void taskDestroyed() { sk_atomic_dec(&fPending); }
|
||||
void fail(SkString msg);
|
||||
|
||||
void printStatus(SkString name, SkMSec timeMs) const;
|
||||
|
||||
void getFailures(SkTArray<SkString>*) const;
|
||||
|
||||
private:
|
||||
int32_t fPending; // atomic
|
||||
int32_t fFailed; // atomic, == fFailures.count().
|
||||
|
||||
mutable SkMutex fMutex; // Guards fFailures.
|
||||
SkTArray<SkString> fFailures;
|
||||
};
|
||||
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMReporter_DEFINED
|
@ -1,31 +0,0 @@
|
||||
#include "DMSKPTask.h"
|
||||
#include "DMUtil.h"
|
||||
#include "DMWriteTask.h"
|
||||
|
||||
#include "SkCommandLineFlags.h"
|
||||
#include "SkPictureRecorder.h"
|
||||
|
||||
DEFINE_int32(skpMaxWidth, 1000, "Max SKPTask viewport width.");
|
||||
DEFINE_int32(skpMaxHeight, 1000, "Max SKPTask viewport height.");
|
||||
|
||||
namespace DM {
|
||||
|
||||
SKPTask::SKPTask(Reporter* r,
|
||||
TaskRunner* tr,
|
||||
const SkPicture* pic,
|
||||
SkString filename)
|
||||
: CpuTask(r, tr)
|
||||
, fPicture(SkRef(pic))
|
||||
, fName(FileToTaskName(filename)) {}
|
||||
|
||||
void SKPTask::draw() {
|
||||
const int width = SkTMin(SkScalarCeilToInt(fPicture->cullRect().width()), FLAGS_skpMaxWidth),
|
||||
height = SkTMin(SkScalarCeilToInt(fPicture->cullRect().height()), FLAGS_skpMaxHeight);
|
||||
SkBitmap bitmap;
|
||||
AllocatePixels(kN32_SkColorType, width, height, &bitmap);
|
||||
DrawPicture(*fPicture, &bitmap);
|
||||
|
||||
this->spawnChild(SkNEW_ARGS(WriteTask, (*this, "SKP", bitmap)));
|
||||
}
|
||||
|
||||
} // namespace DM
|
@ -1,30 +0,0 @@
|
||||
#ifndef DMSKPTask_DEFINED
|
||||
#define DMSKPTask_DEFINED
|
||||
|
||||
#include "DMReporter.h"
|
||||
#include "DMTask.h"
|
||||
#include "DMTaskRunner.h"
|
||||
#include "SkPicture.h"
|
||||
#include "SkString.h"
|
||||
#include "SkTemplates.h"
|
||||
|
||||
// Draws an SKP to a raster canvas, then compares it with some other modes.
|
||||
|
||||
namespace DM {
|
||||
|
||||
class SKPTask : public CpuTask {
|
||||
public:
|
||||
SKPTask(Reporter*, TaskRunner*, const SkPicture*, SkString name);
|
||||
|
||||
void draw() SK_OVERRIDE;
|
||||
bool shouldSkip() const SK_OVERRIDE { return false; }
|
||||
SkString name() const SK_OVERRIDE { return fName; }
|
||||
|
||||
private:
|
||||
SkAutoTUnref<const SkPicture> fPicture;
|
||||
const SkString fName;
|
||||
};
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMSKPTask_DEFINED
|
@ -1,44 +0,0 @@
|
||||
#include "DMSerializeTask.h"
|
||||
#include "DMUtil.h"
|
||||
#include "DMWriteTask.h"
|
||||
|
||||
#include "SkCommandLineFlags.h"
|
||||
#include "SkPicture.h"
|
||||
#include "SkPixelRef.h"
|
||||
|
||||
DEFINE_bool(serialize, true, "If true, run picture serialization tests via SkPictureData.");
|
||||
|
||||
namespace DM {
|
||||
|
||||
SerializeTask::SerializeTask(const Task& parent, skiagm::GM* gm, SkBitmap reference)
|
||||
: CpuTask(parent)
|
||||
, fName(UnderJoin(parent.name().c_str(), "serialize"))
|
||||
, fGM(gm)
|
||||
, fReference(reference)
|
||||
{}
|
||||
|
||||
void SerializeTask::draw() {
|
||||
SkAutoTUnref<SkPicture> recorded(RecordPicture(fGM.get(), NULL/*no BBH*/));
|
||||
|
||||
SkDynamicMemoryWStream wStream;
|
||||
recorded->serialize(&wStream);
|
||||
SkAutoTUnref<SkStream> rStream(wStream.detachAsStream());
|
||||
SkAutoTUnref<SkPicture> reconstructed(SkPicture::CreateFromStream(rStream));
|
||||
|
||||
SkBitmap bitmap;
|
||||
AllocatePixels(fReference, &bitmap);
|
||||
DrawPicture(*reconstructed, &bitmap);
|
||||
if (!BitmapsEqual(bitmap, fReference)) {
|
||||
this->fail();
|
||||
this->spawnChild(SkNEW_ARGS(WriteTask, (*this, "GM", bitmap)));
|
||||
}
|
||||
}
|
||||
|
||||
bool SerializeTask::shouldSkip() const {
|
||||
if (fGM->getFlags() & skiagm::GM::kSkipPicture_Flag) {
|
||||
return true;
|
||||
}
|
||||
return !FLAGS_serialize;
|
||||
}
|
||||
|
||||
} // namespace DM
|
@ -1,31 +0,0 @@
|
||||
#ifndef DMSerializeTask_DEFINED
|
||||
#define DMSerializeTask_DEFINED
|
||||
|
||||
#include "DMTask.h"
|
||||
#include "SkBitmap.h"
|
||||
#include "SkString.h"
|
||||
#include "SkTemplates.h"
|
||||
#include "gm.h"
|
||||
|
||||
// Record a picture, serialize it, deserialize it, then draw it and compare to reference bitmap.
|
||||
|
||||
namespace DM {
|
||||
|
||||
class SerializeTask : public CpuTask {
|
||||
|
||||
public:
|
||||
SerializeTask(const Task& parent, skiagm::GM*, SkBitmap reference);
|
||||
|
||||
void draw() SK_OVERRIDE;
|
||||
bool shouldSkip() const SK_OVERRIDE;
|
||||
SkString name() const SK_OVERRIDE { return fName; }
|
||||
|
||||
private:
|
||||
const SkString fName;
|
||||
SkAutoTDelete<skiagm::GM> fGM;
|
||||
const SkBitmap fReference;
|
||||
};
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMSerializeTask_DEFINED
|
389
dm/DMSrcSink.cpp
Normal file
389
dm/DMSrcSink.cpp
Normal file
@ -0,0 +1,389 @@
|
||||
#include "DMSrcSink.h"
|
||||
#include "SamplePipeControllers.h"
|
||||
#include "SkCommonFlags.h"
|
||||
#include "SkDocument.h"
|
||||
#include "SkMultiPictureDraw.h"
|
||||
#include "SkOSFile.h"
|
||||
#include "SkPictureRecorder.h"
|
||||
#include "SkRandom.h"
|
||||
#include "SkTLS.h"
|
||||
|
||||
namespace DM {
|
||||
|
||||
void SafeUnref(SkPicture* p) { SkSafeUnref(p); }
|
||||
void SafeUnref(SkData* d) { SkSafeUnref(d); }
|
||||
|
||||
// FIXME: the GM objects themselves are not threadsafe, so we create and destroy them as needed.
|
||||
|
||||
GMSrc::GMSrc(skiagm::GMRegistry::Factory factory) : fFactory(factory) {}
|
||||
|
||||
Error GMSrc::draw(SkCanvas* canvas) const {
|
||||
SkAutoTDelete<skiagm::GM> gm(fFactory(NULL));
|
||||
canvas->concat(gm->getInitialTransform());
|
||||
gm->draw(canvas);
|
||||
return "";
|
||||
}
|
||||
|
||||
SkISize GMSrc::size() const {
|
||||
SkAutoTDelete<skiagm::GM> gm(fFactory(NULL));
|
||||
return gm->getISize();
|
||||
}
|
||||
|
||||
Name GMSrc::name() const {
|
||||
SkAutoTDelete<skiagm::GM> gm(fFactory(NULL));
|
||||
return gm->getName();
|
||||
}
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
// The first call to draw() or size() will mmap the file to an SkData. ~ImageSrc unrefs it.
|
||||
struct LazyLoadImage {
|
||||
LazyLoadImage(const char* path) : path(path) {}
|
||||
const char* path;
|
||||
|
||||
SkData* operator()() const { return SkData::NewFromFileName(path); }
|
||||
};
|
||||
|
||||
ImageSrc::ImageSrc(SkString path, int subsets) : fPath(path), fSubsets(subsets) {}
|
||||
|
||||
Error ImageSrc::draw(SkCanvas* canvas) const {
|
||||
const SkData* encoded = fEncoded.get(LazyLoadImage(fPath.c_str()));
|
||||
if (!encoded) {
|
||||
return SkStringPrintf("Couldn't read %s.", fPath.c_str());
|
||||
}
|
||||
if (fSubsets == 0) {
|
||||
// Decode the full image.
|
||||
SkBitmap bitmap;
|
||||
if (!SkImageDecoder::DecodeMemory(encoded->data(), encoded->size(), &bitmap)) {
|
||||
return SkStringPrintf("Couldn't decode %s.", fPath.c_str());
|
||||
}
|
||||
canvas->drawBitmap(bitmap, 0,0);
|
||||
return "";
|
||||
}
|
||||
// Decode random subsets. This is a little involved.
|
||||
SkMemoryStream stream(encoded->data(), encoded->size());
|
||||
SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(&stream));
|
||||
if (!decoder) {
|
||||
return SkStringPrintf("Can't find a good decoder for %s.", fPath.c_str());
|
||||
}
|
||||
int w,h;
|
||||
if (!decoder->buildTileIndex(&stream, &w, &h) || w*h == 1) {
|
||||
return ""; // Not an error. Subset decoding is not always supported.
|
||||
}
|
||||
SkRandom rand;
|
||||
for (int i = 0; i < fSubsets; i++) {
|
||||
SkIRect rect;
|
||||
do {
|
||||
rect.fLeft = rand.nextULessThan(w);
|
||||
rect.fTop = rand.nextULessThan(h);
|
||||
rect.fRight = rand.nextULessThan(w);
|
||||
rect.fBottom = rand.nextULessThan(h);
|
||||
rect.sort();
|
||||
} while (rect.isEmpty());
|
||||
SkBitmap subset;
|
||||
if (!decoder->decodeSubset(&subset, rect, kUnknown_SkColorType/*use best fit*/)) {
|
||||
return SkStringPrintf("Could not decode subset %d.\n", i);
|
||||
}
|
||||
canvas->drawBitmap(subset, SkIntToScalar(rect.fLeft), SkIntToScalar(rect.fTop));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
SkISize ImageSrc::size() const {
|
||||
const SkData* encoded = fEncoded.get(LazyLoadImage(fPath.c_str()));
|
||||
SkBitmap bitmap;
|
||||
if (!encoded || !SkImageDecoder::DecodeMemory(encoded->data(),
|
||||
encoded->size(),
|
||||
&bitmap,
|
||||
kUnknown_SkColorType,
|
||||
SkImageDecoder::kDecodeBounds_Mode)) {
|
||||
return SkISize::Make(0,0);
|
||||
}
|
||||
return bitmap.dimensions();
|
||||
}
|
||||
|
||||
Name ImageSrc::name() const { return SkOSPath::Basename(fPath.c_str()); }
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
static const SkRect kSKPViewport = {0,0, 1000,1000};
|
||||
|
||||
// The first call to draw() or size() will read the file into an SkPicture. ~SKPSrc unrefs it.
|
||||
struct LazyLoadPicture {
|
||||
LazyLoadPicture(const char* path) : path(path) {}
|
||||
const char* path;
|
||||
|
||||
SkPicture* operator()() const {
|
||||
SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path));
|
||||
if (!stream) {
|
||||
return NULL;
|
||||
}
|
||||
return SkPicture::CreateFromStream(stream);
|
||||
}
|
||||
};
|
||||
|
||||
SKPSrc::SKPSrc(SkString path) : fPath(path) {}
|
||||
|
||||
Error SKPSrc::draw(SkCanvas* canvas) const {
|
||||
const SkPicture* pic = fPic.get(LazyLoadPicture(fPath.c_str()));
|
||||
if (!pic) {
|
||||
return SkStringPrintf("Couldn't read %s.", fPath.c_str());
|
||||
}
|
||||
canvas->clipRect(kSKPViewport);
|
||||
canvas->drawPicture(pic);
|
||||
return "";
|
||||
}
|
||||
|
||||
SkISize SKPSrc::size() const {
|
||||
const SkPicture* pic = fPic.get(LazyLoadPicture(fPath.c_str()));
|
||||
if (!pic) {
|
||||
return SkISize::Make(0,0);
|
||||
}
|
||||
SkRect cull = pic->cullRect();
|
||||
if (!cull.intersect(kSKPViewport)) {
|
||||
sk_throw();
|
||||
}
|
||||
SkIRect bounds;
|
||||
cull.roundOut(&bounds);
|
||||
SkISize size = { bounds.width(), bounds.height() };
|
||||
return size;
|
||||
}
|
||||
|
||||
Name SKPSrc::name() const { return SkOSPath::Basename(fPath.c_str()); }
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
DEFINE_string(gpu_threading, "none",
|
||||
"none: single thread,\n"
|
||||
"tls: any thread, GrContextFactory in TLS (crashy),\n"
|
||||
"stack: any thread, GrContextFactory on stack (less crashy, differently so)");
|
||||
|
||||
GPUSink::GPUSink(GrContextFactory::GLContextType ct, GrGLStandard api, int samples, bool dfText)
|
||||
: fContextType(ct)
|
||||
, fGpuAPI(api)
|
||||
, fSampleCount(samples)
|
||||
, fUseDFText(dfText) {}
|
||||
|
||||
int GPUSink::enclave() const {
|
||||
return FLAGS_gpu_threading.contains("none") ? kGPUSink_Enclave : kAnyThread_Enclave;
|
||||
}
|
||||
|
||||
static void* CreateGrFactory() { return new GrContextFactory; }
|
||||
static void DeleteGrFactory(void* p) { delete (GrContextFactory*)p; }
|
||||
|
||||
Error GPUSink::draw(const Src& src, SkBitmap* dst, SkWStream*) const {
|
||||
GrContextFactory local, *factory = &local;
|
||||
if (!FLAGS_gpu_threading.contains("stack")) {
|
||||
factory = (GrContextFactory*)SkTLS::Get(CreateGrFactory, DeleteGrFactory);
|
||||
}
|
||||
// Does abandoning / resetting contexts make any sense if we have stack-scoped factories?
|
||||
if (FLAGS_abandonGpuContext) {
|
||||
factory->abandonContexts();
|
||||
}
|
||||
if (FLAGS_resetGpuContext || FLAGS_abandonGpuContext) {
|
||||
factory->destroyContexts();
|
||||
}
|
||||
const SkISize size = src.size();
|
||||
const SkImageInfo info =
|
||||
SkImageInfo::Make(size.width(), size.height(), kN32_SkColorType, kPremul_SkAlphaType);
|
||||
SkAutoTUnref<SkSurface> surface(
|
||||
NewGpuSurface(factory, fContextType, fGpuAPI, info, fSampleCount, fUseDFText));
|
||||
if (!surface) {
|
||||
return "Could not create a surface.";
|
||||
}
|
||||
SkCanvas* canvas = surface->getCanvas();
|
||||
Error err = src.draw(canvas);
|
||||
if (!err.isEmpty()) {
|
||||
return err;
|
||||
}
|
||||
canvas->flush();
|
||||
dst->allocPixels(info);
|
||||
canvas->readPixels(dst, 0,0);
|
||||
return "";
|
||||
}
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
PDFSink::PDFSink() {}
|
||||
|
||||
Error PDFSink::draw(const Src& src, SkBitmap*, SkWStream* dst) const {
|
||||
SkSize size;
|
||||
size = src.size();
|
||||
SkAutoTUnref<SkDocument> doc(SkDocument::CreatePDF(dst));
|
||||
SkCanvas* canvas = doc->beginPage(size.width(), size.height());
|
||||
|
||||
Error err = src.draw(canvas);
|
||||
if (!err.isEmpty()) {
|
||||
return err;
|
||||
}
|
||||
canvas->flush();
|
||||
doc->endPage();
|
||||
doc->close();
|
||||
return "";
|
||||
}
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
RasterSink::RasterSink(SkColorType colorType) : fColorType(colorType) {}
|
||||
|
||||
Error RasterSink::draw(const Src& src, SkBitmap* dst, SkWStream*) const {
|
||||
const SkISize size = src.size();
|
||||
// If there's an appropriate alpha type for this color type, use it, otherwise use premul.
|
||||
SkAlphaType alphaType = kPremul_SkAlphaType;
|
||||
(void)SkColorTypeValidateAlphaType(fColorType, alphaType, &alphaType);
|
||||
|
||||
dst->allocPixels(SkImageInfo::Make(size.width(), size.height(), fColorType, alphaType));
|
||||
dst->eraseColor(SK_ColorTRANSPARENT);
|
||||
SkCanvas canvas(*dst);
|
||||
return src.draw(&canvas);
|
||||
}
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
ViaMatrix::ViaMatrix(SkMatrix matrix, Sink* sink) : fMatrix(matrix), fSink(sink) {}
|
||||
|
||||
Error ViaMatrix::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream) const {
|
||||
// We turn our arguments into a Src, then draw that Src into our Sink to fill bitmap or stream.
|
||||
struct ProxySrc : public Src {
|
||||
const Src& fSrc;
|
||||
SkMatrix fMatrix;
|
||||
ProxySrc(const Src& src, SkMatrix matrix) : fSrc(src), fMatrix(matrix) {}
|
||||
|
||||
Error draw(SkCanvas* canvas) const SK_OVERRIDE {
|
||||
canvas->concat(fMatrix);
|
||||
return fSrc.draw(canvas);
|
||||
}
|
||||
SkISize size() const SK_OVERRIDE { return fSrc.size(); }
|
||||
Name name() const SK_OVERRIDE { sk_throw(); return ""; } // No one should be calling this.
|
||||
} proxy(src, fMatrix);
|
||||
return fSink->draw(proxy, bitmap, stream);
|
||||
}
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
ViaPipe::ViaPipe(int flags, Sink* sink) : fFlags((SkGPipeWriter::Flags)flags), fSink(sink) {}
|
||||
|
||||
Error ViaPipe::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream) const {
|
||||
// We turn our arguments into a Src, then draw that Src into our Sink to fill bitmap or stream.
|
||||
struct ProxySrc : public Src {
|
||||
const Src& fSrc;
|
||||
SkGPipeWriter::Flags fFlags;
|
||||
ProxySrc(const Src& src, SkGPipeWriter::Flags flags) : fSrc(src), fFlags(flags) {}
|
||||
|
||||
Error draw(SkCanvas* canvas) const SK_OVERRIDE {
|
||||
SkISize size = this->size();
|
||||
// TODO: is DecodeMemory really required? Might help RAM usage to be lazy if we can.
|
||||
PipeController controller(canvas, &SkImageDecoder::DecodeMemory);
|
||||
SkGPipeWriter pipe;
|
||||
return fSrc.draw(pipe.startRecording(&controller, fFlags, size.width(), size.height()));
|
||||
}
|
||||
SkISize size() const SK_OVERRIDE { return fSrc.size(); }
|
||||
Name name() const SK_OVERRIDE { sk_throw(); return ""; } // No one should be calling this.
|
||||
} proxy(src, fFlags);
|
||||
return fSink->draw(proxy, bitmap, stream);
|
||||
}
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
ViaSerialization::ViaSerialization(Sink* sink) : fSink(sink) {}
|
||||
|
||||
Error ViaSerialization::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream) const {
|
||||
// Record our Src into a picture.
|
||||
SkSize size;
|
||||
size = src.size();
|
||||
SkPictureRecorder recorder;
|
||||
Error err = src.draw(recorder.beginRecording(size.width(), size.height()));
|
||||
if (!err.isEmpty()) {
|
||||
return err;
|
||||
}
|
||||
SkAutoTUnref<SkPicture> pic(recorder.endRecording());
|
||||
|
||||
// Serialize it and then deserialize it.
|
||||
SkDynamicMemoryWStream wStream;
|
||||
pic->serialize(&wStream);
|
||||
SkAutoTUnref<SkStream> rStream(wStream.detachAsStream());
|
||||
SkAutoTUnref<SkPicture> deserialized(SkPicture::CreateFromStream(rStream));
|
||||
|
||||
// Turn that deserialized picture into a Src, draw it into our Sink to fill bitmap or stream.
|
||||
struct ProxySrc : public Src {
|
||||
const SkPicture* fPic;
|
||||
const SkISize fSize;
|
||||
ProxySrc(const SkPicture* pic, SkISize size) : fPic(pic), fSize(size) {}
|
||||
|
||||
Error draw(SkCanvas* canvas) const SK_OVERRIDE {
|
||||
canvas->drawPicture(fPic);
|
||||
return "";
|
||||
}
|
||||
SkISize size() const SK_OVERRIDE { return fSize; }
|
||||
Name name() const SK_OVERRIDE { sk_throw(); return ""; } // No one should be calling this.
|
||||
} proxy(deserialized, src.size());
|
||||
return fSink->draw(proxy, bitmap, stream);
|
||||
}
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
ViaTiles::ViaTiles(int w, int h, SkBBHFactory* factory, Sink* sink)
|
||||
: fW(w)
|
||||
, fH(h)
|
||||
, fFactory(factory)
|
||||
, fSink(sink) {}
|
||||
|
||||
Error ViaTiles::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream) const {
|
||||
// Record our Src into a picture.
|
||||
SkSize size;
|
||||
size = src.size();
|
||||
SkPictureRecorder recorder;
|
||||
Error err = src.draw(recorder.beginRecording(size.width(), size.height(), fFactory.get()));
|
||||
if (!err.isEmpty()) {
|
||||
return err;
|
||||
}
|
||||
SkAutoTUnref<SkPicture> pic(recorder.endRecording());
|
||||
|
||||
// Turn that picture into a Src that draws into our Sink via tiles + MPD.
|
||||
struct ProxySrc : public Src {
|
||||
const int fW, fH;
|
||||
const SkPicture* fPic;
|
||||
const SkISize fSize;
|
||||
ProxySrc(int w, int h, const SkPicture* pic, SkISize size)
|
||||
: fW(w), fH(h), fPic(pic), fSize(size) {}
|
||||
|
||||
Error draw(SkCanvas* canvas) const SK_OVERRIDE {
|
||||
const int xTiles = (fSize.width() + fW - 1) / fW,
|
||||
yTiles = (fSize.height() + fH - 1) / fH;
|
||||
SkMultiPictureDraw mpd(xTiles*yTiles);
|
||||
SkTDArray<SkSurface*> surfaces;
|
||||
surfaces.setReserve(xTiles*yTiles);
|
||||
|
||||
SkImageInfo info = canvas->imageInfo().makeWH(fW, fH);
|
||||
for (int j = 0; j < yTiles; j++) {
|
||||
for (int i = 0; i < xTiles; i++) {
|
||||
// This lets our ultimate Sink determine the best kind of surface.
|
||||
// E.g., if it's a GpuSink, the surfaces and images are textures.
|
||||
SkSurface* s = canvas->newSurface(info);
|
||||
if (!s) {
|
||||
s = SkSurface::NewRaster(info); // Some canvases can't create surfaces.
|
||||
}
|
||||
surfaces.push(s);
|
||||
SkCanvas* c = s->getCanvas();
|
||||
c->translate(SkIntToScalar(-i * fW),
|
||||
SkIntToScalar(-j * fH)); // Line up the canvas with this tile.
|
||||
mpd.add(c, fPic);
|
||||
}
|
||||
}
|
||||
mpd.draw();
|
||||
for (int j = 0; j < yTiles; j++) {
|
||||
for (int i = 0; i < xTiles; i++) {
|
||||
SkAutoTUnref<SkImage> image(surfaces[i+xTiles*j]->newImageSnapshot());
|
||||
canvas->drawImage(image, SkIntToScalar(i*fW), SkIntToScalar(j*fH));
|
||||
}
|
||||
}
|
||||
surfaces.unrefAll();
|
||||
return "";
|
||||
}
|
||||
SkISize size() const SK_OVERRIDE { return fSize; }
|
||||
Name name() const SK_OVERRIDE { sk_throw(); return ""; } // No one should be calling this.
|
||||
} proxy(fW, fH, pic, src.size());
|
||||
return fSink->draw(proxy, bitmap, stream);
|
||||
}
|
||||
|
||||
} // namespace DM
|
177
dm/DMSrcSink.h
Normal file
177
dm/DMSrcSink.h
Normal file
@ -0,0 +1,177 @@
|
||||
#ifndef DMSrcSink_DEFINED
|
||||
#define DMSrcSink_DEFINED
|
||||
|
||||
#include "DMGpuSupport.h"
|
||||
#include "SkBBHFactory.h"
|
||||
#include "SkBBoxHierarchy.h"
|
||||
#include "SkBitmap.h"
|
||||
#include "SkCanvas.h"
|
||||
#include "SkData.h"
|
||||
#include "SkGPipe.h"
|
||||
#include "SkPicture.h"
|
||||
#include "SkStream.h"
|
||||
#include "gm.h"
|
||||
|
||||
namespace DM {
|
||||
|
||||
// This is just convenience. It lets you use either return "foo" or return SkStringPrintf(...).
|
||||
struct ImplicitString : public SkString {
|
||||
template <typename T>
|
||||
ImplicitString(const T& s) : SkString(s) {}
|
||||
};
|
||||
typedef ImplicitString Error;
|
||||
typedef ImplicitString Name;
|
||||
|
||||
struct Src {
|
||||
// All Srcs must be thread safe.
|
||||
virtual ~Src() {}
|
||||
virtual Error SK_WARN_UNUSED_RESULT draw(SkCanvas*) const = 0;
|
||||
virtual SkISize size() const = 0;
|
||||
virtual Name name() const = 0;
|
||||
};
|
||||
|
||||
struct Sink {
|
||||
virtual ~Sink() {}
|
||||
// You may write to either the bitmap or stream.
|
||||
virtual Error SK_WARN_UNUSED_RESULT draw(const Src&, SkBitmap*, SkWStream*) const
|
||||
= 0;
|
||||
// Sinks in the same enclave (except kAnyThread_Enclave) will run serially on the same thread.
|
||||
virtual int enclave() const = 0;
|
||||
|
||||
// File extension for the content draw() outputs, e.g. "png", "pdf".
|
||||
virtual const char* fileExtension() const = 0;
|
||||
};
|
||||
|
||||
enum { kAnyThread_Enclave, kGPUSink_Enclave, kPDFSink_Enclave };
|
||||
static const int kNumEnclaves = kPDFSink_Enclave + 1;
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
void SafeUnref(SkPicture*); // These need external linkage (and specific types).
|
||||
void SafeUnref(SkData*);
|
||||
|
||||
class GMSrc : public Src {
|
||||
public:
|
||||
explicit GMSrc(skiagm::GMRegistry::Factory);
|
||||
|
||||
Error draw(SkCanvas*) const SK_OVERRIDE;
|
||||
SkISize size() const SK_OVERRIDE;
|
||||
Name name() const SK_OVERRIDE;
|
||||
private:
|
||||
skiagm::GMRegistry::Factory fFactory;
|
||||
};
|
||||
|
||||
class ImageSrc : public Src {
|
||||
public:
|
||||
explicit ImageSrc(SkString path, int subsets = 0);
|
||||
|
||||
Error draw(SkCanvas*) const SK_OVERRIDE;
|
||||
SkISize size() const SK_OVERRIDE;
|
||||
Name name() const SK_OVERRIDE;
|
||||
private:
|
||||
SkString fPath;
|
||||
int fSubsets;
|
||||
SkLazyPtr<SkData, SafeUnref> fEncoded;
|
||||
};
|
||||
|
||||
class SKPSrc : public Src {
|
||||
public:
|
||||
explicit SKPSrc(SkString path);
|
||||
|
||||
Error draw(SkCanvas*) const SK_OVERRIDE;
|
||||
SkISize size() const SK_OVERRIDE;
|
||||
Name name() const SK_OVERRIDE;
|
||||
private:
|
||||
SkString fPath;
|
||||
SkLazyPtr<SkPicture, SafeUnref> fPic;
|
||||
};
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
class GPUSink : public Sink {
|
||||
public:
|
||||
GPUSink(GrContextFactory::GLContextType, GrGLStandard, int samples, bool dfText);
|
||||
|
||||
Error draw(const Src&, SkBitmap*, SkWStream*) const SK_OVERRIDE;
|
||||
int enclave() const SK_OVERRIDE;
|
||||
const char* fileExtension() const SK_OVERRIDE { return "png"; }
|
||||
private:
|
||||
GrContextFactory::GLContextType fContextType;
|
||||
GrGLStandard fGpuAPI;
|
||||
int fSampleCount;
|
||||
bool fUseDFText;
|
||||
};
|
||||
|
||||
class PDFSink : public Sink {
|
||||
public:
|
||||
PDFSink();
|
||||
|
||||
Error draw(const Src&, SkBitmap*, SkWStream*) const SK_OVERRIDE;
|
||||
int enclave() const SK_OVERRIDE { return kPDFSink_Enclave; }
|
||||
const char* fileExtension() const SK_OVERRIDE { return "pdf"; }
|
||||
};
|
||||
|
||||
class RasterSink : public Sink {
|
||||
public:
|
||||
explicit RasterSink(SkColorType);
|
||||
|
||||
Error draw(const Src&, SkBitmap*, SkWStream*) const SK_OVERRIDE;
|
||||
int enclave() const SK_OVERRIDE { return kAnyThread_Enclave; }
|
||||
const char* fileExtension() const SK_OVERRIDE { return "png"; }
|
||||
private:
|
||||
SkColorType fColorType;
|
||||
};
|
||||
|
||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||
|
||||
class ViaMatrix : public Sink {
|
||||
public:
|
||||
ViaMatrix(SkMatrix, Sink*);
|
||||
|
||||
Error draw(const Src&, SkBitmap*, SkWStream*) const SK_OVERRIDE;
|
||||
int enclave() const SK_OVERRIDE { return fSink->enclave(); }
|
||||
const char* fileExtension() const SK_OVERRIDE { return fSink->fileExtension(); }
|
||||
private:
|
||||
SkMatrix fMatrix;
|
||||
SkAutoTDelete<Sink> fSink;
|
||||
};
|
||||
|
||||
class ViaPipe : public Sink {
|
||||
public:
|
||||
ViaPipe(int flags, Sink*);
|
||||
|
||||
Error draw(const Src&, SkBitmap*, SkWStream*) const SK_OVERRIDE;
|
||||
int enclave() const SK_OVERRIDE { return fSink->enclave(); }
|
||||
const char* fileExtension() const SK_OVERRIDE { return fSink->fileExtension(); }
|
||||
private:
|
||||
SkGPipeWriter::Flags fFlags;
|
||||
SkAutoTDelete<Sink> fSink;
|
||||
};
|
||||
|
||||
class ViaSerialization : public Sink {
|
||||
public:
|
||||
explicit ViaSerialization(Sink*);
|
||||
|
||||
Error draw(const Src&, SkBitmap*, SkWStream*) const SK_OVERRIDE;
|
||||
int enclave() const SK_OVERRIDE { return fSink->enclave(); }
|
||||
const char* fileExtension() const SK_OVERRIDE { return fSink->fileExtension(); }
|
||||
private:
|
||||
SkAutoTDelete<Sink> fSink;
|
||||
};
|
||||
|
||||
class ViaTiles : public Sink {
|
||||
public:
|
||||
ViaTiles(int w, int h, SkBBHFactory*, Sink*);
|
||||
|
||||
Error draw(const Src&, SkBitmap*, SkWStream*) const SK_OVERRIDE;
|
||||
int enclave() const SK_OVERRIDE { return fSink->enclave(); }
|
||||
const char* fileExtension() const SK_OVERRIDE { return fSink->fileExtension(); }
|
||||
private:
|
||||
const int fW, fH;
|
||||
SkAutoTDelete<SkBBHFactory> fFactory;
|
||||
SkAutoTDelete<Sink> fSink;
|
||||
};
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif//DMSrcSink_DEFINED
|
@ -1,91 +0,0 @@
|
||||
#include "DMTask.h"
|
||||
#include "DMTaskRunner.h"
|
||||
#include "SkCommonFlags.h"
|
||||
|
||||
namespace DM {
|
||||
|
||||
Task::Task(Reporter* reporter, TaskRunner* taskRunner)
|
||||
: fReporter(reporter)
|
||||
, fTaskRunner(taskRunner)
|
||||
, fDepth(0) {
|
||||
fReporter->taskCreated();
|
||||
}
|
||||
|
||||
Task::Task(const Task& parent)
|
||||
: fReporter(parent.fReporter)
|
||||
, fTaskRunner(parent.fTaskRunner)
|
||||
, fDepth(parent.depth() + 1) {
|
||||
fReporter->taskCreated();
|
||||
}
|
||||
|
||||
Task::~Task() {
|
||||
fReporter->taskDestroyed();
|
||||
}
|
||||
|
||||
void Task::fail(const char* msg) {
|
||||
SkString failure(this->name());
|
||||
if (msg) {
|
||||
failure.appendf(": %s", msg);
|
||||
}
|
||||
fReporter->fail(failure);
|
||||
}
|
||||
|
||||
void Task::start() {
|
||||
fStart = SkTime::GetMSecs();
|
||||
}
|
||||
|
||||
void Task::finish() {
|
||||
fReporter->printStatus(this->name(), SkTime::GetMSecs() - fStart);
|
||||
}
|
||||
|
||||
void Task::reallySpawnChild(CpuTask* task) {
|
||||
fTaskRunner->add(task);
|
||||
}
|
||||
|
||||
CpuTask::CpuTask(Reporter* reporter, TaskRunner* taskRunner) : Task(reporter, taskRunner) {}
|
||||
CpuTask::CpuTask(const Task& parent) : Task(parent) {}
|
||||
|
||||
void CpuTask::run() {
|
||||
// If the task says skip, or if we're starting a top-level CPU task and we don't want to, skip.
|
||||
const bool skip = this->shouldSkip() || (this->depth() == 0 && !FLAGS_cpu);
|
||||
if (!skip) {
|
||||
this->start();
|
||||
if (!FLAGS_dryRun) this->draw();
|
||||
this->finish();
|
||||
}
|
||||
SkDELETE(this);
|
||||
}
|
||||
|
||||
void CpuTask::spawnChild(CpuTask* task) {
|
||||
// Run children serially on this (CPU) thread. This tends to save RAM and is usually no slower.
|
||||
// Calling reallySpawnChild() is nearly equivalent, but it'd pointlessly contend on the
|
||||
// threadpool; reallySpawnChild() is most useful when you want to change threadpools.
|
||||
task->run();
|
||||
}
|
||||
|
||||
GpuTask::GpuTask(Reporter* reporter, TaskRunner* taskRunner) : Task(reporter, taskRunner) {}
|
||||
|
||||
void GpuTask::run(GrContextFactory* factory) {
|
||||
// If the task says skip, or if we're starting a top-level GPU task and we don't want to, skip.
|
||||
const bool skip = this->shouldSkip() || (this->depth() == 0 && !FLAGS_gpu);
|
||||
if (!skip) {
|
||||
this->start();
|
||||
if (!FLAGS_dryRun) this->draw(factory);
|
||||
this->finish();
|
||||
if (FLAGS_abandonGpuContext) {
|
||||
factory->abandonContexts();
|
||||
}
|
||||
if (FLAGS_resetGpuContext || FLAGS_abandonGpuContext) {
|
||||
factory->destroyContexts();
|
||||
}
|
||||
}
|
||||
SkDELETE(this);
|
||||
}
|
||||
|
||||
void GpuTask::spawnChild(CpuTask* task) {
|
||||
// Spawn a new task so it runs on the CPU threadpool instead of the GPU one we're on now.
|
||||
// It goes on the front of the queue to minimize the time we must hold reference bitmaps in RAM.
|
||||
this->reallySpawnChild(task);
|
||||
}
|
||||
|
||||
} // namespace DM
|
74
dm/DMTask.h
74
dm/DMTask.h
@ -1,74 +0,0 @@
|
||||
#ifndef DMTask_DEFINED
|
||||
#define DMTask_DEFINED
|
||||
|
||||
#include "DMGpuSupport.h"
|
||||
#include "DMReporter.h"
|
||||
#include "SkRunnable.h"
|
||||
#include "SkTaskGroup.h"
|
||||
#include "SkTime.h"
|
||||
|
||||
// DM will run() these tasks on one of two threadpools.
|
||||
// Subclasses can call fail() to mark this task as failed, or make any number of spawnChild() calls
|
||||
// to kick off dependent tasks.
|
||||
//
|
||||
// Tasks delete themselves when run.
|
||||
|
||||
namespace DM {
|
||||
|
||||
class TaskRunner;
|
||||
|
||||
class CpuTask;
|
||||
|
||||
class Task {
|
||||
public:
|
||||
virtual bool shouldSkip() const = 0;
|
||||
virtual SkString name() const = 0;
|
||||
|
||||
// Returns the number of parents above this task.
|
||||
// Top-level tasks return 0, their children 1, and so on.
|
||||
int depth() const { return fDepth; }
|
||||
|
||||
protected:
|
||||
Task(Reporter* reporter, TaskRunner* taskRunner);
|
||||
Task(const Task& parent);
|
||||
virtual ~Task();
|
||||
|
||||
void start();
|
||||
void fail(const char* msg = NULL);
|
||||
void finish();
|
||||
|
||||
void reallySpawnChild(CpuTask* task); // For now we don't allow GPU child tasks.
|
||||
|
||||
private:
|
||||
Reporter* fReporter; // Unowned.
|
||||
TaskRunner* fTaskRunner; // Unowned.
|
||||
int fDepth;
|
||||
SkMSec fStart;
|
||||
};
|
||||
|
||||
class CpuTask : public Task, public SkRunnable {
|
||||
public:
|
||||
CpuTask(Reporter* reporter, TaskRunner* taskRunner);
|
||||
CpuTask(const Task& parent);
|
||||
virtual ~CpuTask() {}
|
||||
|
||||
void run() SK_OVERRIDE;
|
||||
virtual void draw() = 0;
|
||||
|
||||
void spawnChild(CpuTask* task);
|
||||
};
|
||||
|
||||
class GpuTask : public Task {
|
||||
public:
|
||||
GpuTask(Reporter* reporter, TaskRunner* taskRunner);
|
||||
virtual ~GpuTask() {}
|
||||
|
||||
void run(GrContextFactory*);
|
||||
virtual void draw(GrContextFactory*) = 0;
|
||||
|
||||
void spawnChild(CpuTask* task);
|
||||
};
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMTask_DEFINED
|
@ -1,17 +0,0 @@
|
||||
#include "DMTaskRunner.h"
|
||||
#include "DMTask.h"
|
||||
|
||||
namespace DM {
|
||||
|
||||
void TaskRunner::add(CpuTask* task) { fCpuWork.add(task); }
|
||||
void TaskRunner::add(GpuTask* task) { fGpuWork.push(task); }
|
||||
|
||||
void TaskRunner::wait() {
|
||||
GrContextFactory factory;
|
||||
for (int i = 0; i < fGpuWork.count(); i++) {
|
||||
fGpuWork[i]->run(&factory);
|
||||
}
|
||||
fCpuWork.wait();
|
||||
}
|
||||
|
||||
} // namespace DM
|
@ -1,29 +0,0 @@
|
||||
#ifndef DMTaskRunner_DEFINED
|
||||
#define DMTaskRunner_DEFINED
|
||||
|
||||
#include "DMGpuSupport.h"
|
||||
#include "SkTDArray.h"
|
||||
#include "SkTaskGroup.h"
|
||||
#include "SkTypes.h"
|
||||
|
||||
namespace DM {
|
||||
|
||||
class CpuTask;
|
||||
class GpuTask;
|
||||
|
||||
class TaskRunner : SkNoncopyable {
|
||||
public:
|
||||
TaskRunner() {}
|
||||
|
||||
void add(CpuTask* task);
|
||||
void add(GpuTask* task);
|
||||
void wait();
|
||||
|
||||
private:
|
||||
SkTaskGroup fCpuWork;
|
||||
SkTDArray<GpuTask*> fGpuWork;
|
||||
};
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMTaskRunner_DEFINED
|
@ -1,61 +0,0 @@
|
||||
#include "DMTestTask.h"
|
||||
#include "DMUtil.h"
|
||||
#include "SkCommandLineFlags.h"
|
||||
#include "SkCommonFlags.h"
|
||||
|
||||
DEFINE_bool2(pathOpsExtended, x, false, "Run extended pathOps tests.");
|
||||
|
||||
namespace DM {
|
||||
|
||||
bool TestReporter::allowExtendedTest() const { return FLAGS_pathOpsExtended; }
|
||||
bool TestReporter::verbose() const { return FLAGS_veryVerbose; }
|
||||
|
||||
static SkString test_name(const char* name) {
|
||||
SkString result("test ");
|
||||
result.append(name);
|
||||
return result;
|
||||
}
|
||||
|
||||
CpuTestTask::CpuTestTask(Reporter* reporter,
|
||||
TaskRunner* taskRunner,
|
||||
skiatest::TestRegistry::Factory factory)
|
||||
: CpuTask(reporter, taskRunner)
|
||||
, fTest(factory(NULL))
|
||||
, fName(test_name(fTest->getName())) {}
|
||||
|
||||
GpuTestTask::GpuTestTask(Reporter* reporter,
|
||||
TaskRunner* taskRunner,
|
||||
skiatest::TestRegistry::Factory factory)
|
||||
: GpuTask(reporter, taskRunner)
|
||||
, fTest(factory(NULL))
|
||||
, fName(test_name(fTest->getName())) {}
|
||||
|
||||
|
||||
void CpuTestTask::draw() {
|
||||
fTest->setReporter(&fTestReporter);
|
||||
fTest->run();
|
||||
if (!fTest->passed()) {
|
||||
const SkTArray<SkString>& failures = fTestReporter.failures();
|
||||
for (int i = 0; i < failures.count(); i++) {
|
||||
this->fail(failures[i].c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GpuTestTask::draw(GrContextFactory* grFactory) {
|
||||
fTest->setGrContextFactory(grFactory);
|
||||
fTest->setReporter(&fTestReporter);
|
||||
fTest->run();
|
||||
if (!fTest->passed()) {
|
||||
const SkTArray<SkString>& failures = fTestReporter.failures();
|
||||
for (int i = 0; i < failures.count(); i++) {
|
||||
this->fail(failures[i].c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GpuTestTask::shouldSkip() const {
|
||||
return kGPUDisabled;
|
||||
}
|
||||
|
||||
} // namespace DM
|
@ -1,66 +0,0 @@
|
||||
#ifndef DMTestTask_DEFINED
|
||||
#define DMTestTask_DEFINED
|
||||
|
||||
#include "DMReporter.h"
|
||||
#include "DMJsonWriter.h"
|
||||
#include "DMTask.h"
|
||||
#include "DMTaskRunner.h"
|
||||
#include "SkString.h"
|
||||
#include "SkTemplates.h"
|
||||
#include "Test.h"
|
||||
|
||||
// Runs a unit test.
|
||||
namespace DM {
|
||||
|
||||
class TestReporter : public skiatest::Reporter {
|
||||
public:
|
||||
TestReporter() {}
|
||||
|
||||
const SkTArray<SkString>& failures() const { return fFailures; }
|
||||
|
||||
private:
|
||||
bool allowExtendedTest() const SK_OVERRIDE;
|
||||
bool verbose() const SK_OVERRIDE;
|
||||
|
||||
void onReportFailed(const skiatest::Failure& failure) SK_OVERRIDE {
|
||||
JsonWriter::AddTestFailure(failure);
|
||||
|
||||
SkString newFailure;
|
||||
failure.getFailureString(&newFailure);
|
||||
fFailures.push_back(newFailure);
|
||||
}
|
||||
|
||||
SkTArray<SkString> fFailures;
|
||||
};
|
||||
|
||||
class CpuTestTask : public CpuTask {
|
||||
public:
|
||||
CpuTestTask(Reporter*, TaskRunner*, skiatest::TestRegistry::Factory);
|
||||
|
||||
void draw() SK_OVERRIDE;
|
||||
bool shouldSkip() const SK_OVERRIDE { return false; }
|
||||
SkString name() const SK_OVERRIDE { return fName; }
|
||||
|
||||
private:
|
||||
TestReporter fTestReporter;
|
||||
SkAutoTDelete<skiatest::Test> fTest;
|
||||
const SkString fName;
|
||||
};
|
||||
|
||||
class GpuTestTask : public GpuTask {
|
||||
public:
|
||||
GpuTestTask(Reporter*, TaskRunner*, skiatest::TestRegistry::Factory);
|
||||
|
||||
void draw(GrContextFactory*) SK_OVERRIDE;
|
||||
bool shouldSkip() const SK_OVERRIDE;
|
||||
SkString name() const SK_OVERRIDE { return fName; }
|
||||
|
||||
private:
|
||||
TestReporter fTestReporter;
|
||||
SkAutoTDelete<skiatest::Test> fTest;
|
||||
const SkString fName;
|
||||
};
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMTestTask_DEFINED
|
118
dm/DMUtil.cpp
118
dm/DMUtil.cpp
@ -1,118 +0,0 @@
|
||||
#include "DMUtil.h"
|
||||
|
||||
#include "SkColorPriv.h"
|
||||
#include "SkCommandLineFlags.h"
|
||||
#include "SkPicture.h"
|
||||
#include "SkPictureRecorder.h"
|
||||
|
||||
DEFINE_string(matrix, "1 0 0 0 1 0 0 0 1",
|
||||
"Matrix to apply to the canvas before drawing.");
|
||||
|
||||
namespace DM {
|
||||
|
||||
void CanvasPreflight(SkCanvas* canvas) {
|
||||
if (FLAGS_matrix.count() == 9) {
|
||||
SkMatrix m;
|
||||
for (int i = 0; i < 9; i++) {
|
||||
m[i] = (SkScalar)atof(FLAGS_matrix[i]);
|
||||
}
|
||||
canvas->concat(m);
|
||||
}
|
||||
}
|
||||
|
||||
SkString UnderJoin(const char* a, const char* b) {
|
||||
SkString s;
|
||||
s.appendf("%s_%s", a, b);
|
||||
return s;
|
||||
}
|
||||
|
||||
SkString FileToTaskName(SkString filename) {
|
||||
for (size_t i = 0; i < filename.size(); i++) {
|
||||
if ('_' == filename[i]) { filename[i] = '-'; }
|
||||
if ('.' == filename[i]) { filename[i] = '_'; }
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
SkPicture* RecordPicture(skiagm::GM* gm, SkBBHFactory* factory) {
|
||||
const SkScalar w = SkIntToScalar(gm->getISize().width()),
|
||||
h = SkIntToScalar(gm->getISize().height());
|
||||
SkPictureRecorder recorder;
|
||||
|
||||
SkCanvas* canvas = recorder.beginRecording(w, h, factory);
|
||||
CanvasPreflight(canvas);
|
||||
canvas->concat(gm->getInitialTransform());
|
||||
gm->draw(canvas);
|
||||
canvas->flush();
|
||||
return recorder.endRecording();
|
||||
}
|
||||
|
||||
void AllocatePixels(SkColorType ct, int width, int height, SkBitmap* bitmap) {
|
||||
bitmap->allocPixels(SkImageInfo::Make(width, height, ct, kPremul_SkAlphaType));
|
||||
bitmap->eraseColor(0x00000000);
|
||||
}
|
||||
|
||||
void AllocatePixels(const SkBitmap& reference, SkBitmap* bitmap) {
|
||||
AllocatePixels(reference.colorType(), reference.width(), reference.height(), bitmap);
|
||||
}
|
||||
|
||||
void DrawPicture(const SkPicture& picture, SkBitmap* bitmap) {
|
||||
SkASSERT(bitmap != NULL);
|
||||
SkCanvas canvas(*bitmap);
|
||||
canvas.drawPicture(&picture);
|
||||
canvas.flush();
|
||||
}
|
||||
|
||||
static void unpack_565(uint16_t pixel, unsigned* r, unsigned* g, unsigned* b) {
|
||||
*r = SkGetPackedR16(pixel);
|
||||
*g = SkGetPackedG16(pixel);
|
||||
*b = SkGetPackedB16(pixel);
|
||||
}
|
||||
|
||||
// Returns |a-b|.
|
||||
static unsigned abs_diff(unsigned a, unsigned b) {
|
||||
return a > b ? a - b : b - a;
|
||||
}
|
||||
|
||||
unsigned MaxComponentDifference(const SkBitmap& a, const SkBitmap& b) {
|
||||
if (a.info() != b.info()) {
|
||||
SkFAIL("Can't compare bitmaps of different shapes.");
|
||||
}
|
||||
|
||||
unsigned max = 0;
|
||||
|
||||
const SkAutoLockPixels lockA(a), lockB(b);
|
||||
if (a.info().colorType() == kRGB_565_SkColorType) {
|
||||
// 565 is special/annoying because its 3 components straddle 2 bytes.
|
||||
const uint16_t* aPixels = (const uint16_t*)a.getPixels();
|
||||
const uint16_t* bPixels = (const uint16_t*)b.getPixels();
|
||||
for (size_t i = 0; i < a.getSize() / 2; i++) {
|
||||
unsigned ar, ag, ab,
|
||||
br, bg, bb;
|
||||
unpack_565(aPixels[i], &ar, &ag, &ab);
|
||||
unpack_565(bPixels[i], &br, &bg, &bb);
|
||||
max = SkTMax(max, abs_diff(ar, br));
|
||||
max = SkTMax(max, abs_diff(ag, bg));
|
||||
max = SkTMax(max, abs_diff(ab, bb));
|
||||
}
|
||||
} else {
|
||||
// Everything else we produce is byte aligned, so max component diff == max byte diff.
|
||||
const uint8_t* aBytes = (const uint8_t*)a.getPixels();
|
||||
const uint8_t* bBytes = (const uint8_t*)b.getPixels();
|
||||
for (size_t i = 0; i < a.getSize(); i++) {
|
||||
max = SkTMax(max, abs_diff(aBytes[i], bBytes[i]));
|
||||
}
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
bool BitmapsEqual(const SkBitmap& a, const SkBitmap& b) {
|
||||
if (a.info() != b.info()) {
|
||||
return false;
|
||||
}
|
||||
const SkAutoLockPixels lockA(a), lockB(b);
|
||||
return 0 == memcmp(a.getPixels(), b.getPixels(), a.getSize());
|
||||
}
|
||||
|
||||
} // namespace DM
|
43
dm/DMUtil.h
43
dm/DMUtil.h
@ -1,43 +0,0 @@
|
||||
#ifndef DMUtil_DEFINED
|
||||
#define DMUtil_DEFINED
|
||||
|
||||
#include "SkBitmap.h"
|
||||
#include "SkCanvas.h"
|
||||
#include "SkString.h"
|
||||
#include "gm.h"
|
||||
|
||||
class SkBBHFactory;
|
||||
|
||||
// Small free functions used in more than one place in DM.
|
||||
|
||||
namespace DM {
|
||||
|
||||
// UnderJoin("a", "b") -> "a_b"
|
||||
SkString UnderJoin(const char* a, const char* b);
|
||||
|
||||
// "foo_bar.skp" -> "foo-bar_skp"
|
||||
SkString FileToTaskName(SkString);
|
||||
|
||||
// Draw gm to picture.
|
||||
SkPicture* RecordPicture(skiagm::GM* gm, SkBBHFactory* factory = NULL);
|
||||
|
||||
// Allocate an empty bitmap with this size and depth.
|
||||
void AllocatePixels(SkColorType, int w, int h, SkBitmap* bitmap);
|
||||
// Allocate an empty bitmap the same size and depth as reference.
|
||||
void AllocatePixels(const SkBitmap& reference, SkBitmap* bitmap);
|
||||
|
||||
// Draw picture to bitmap.
|
||||
void DrawPicture(const SkPicture& picture, SkBitmap* bitmap);
|
||||
|
||||
// What is the maximum component difference in these bitmaps?
|
||||
unsigned MaxComponentDifference(const SkBitmap& a, const SkBitmap& b);
|
||||
|
||||
// Are these identical bitmaps?
|
||||
bool BitmapsEqual(const SkBitmap& a, const SkBitmap& b);
|
||||
|
||||
// Hook to modify canvas using global flag values (e.g. --matrix).
|
||||
void CanvasPreflight(SkCanvas*);
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMUtil_DEFINED
|
@ -1,189 +0,0 @@
|
||||
#include "DMWriteTask.h"
|
||||
|
||||
#include "DMJsonWriter.h"
|
||||
#include "DMUtil.h"
|
||||
#include "SkColorPriv.h"
|
||||
#include "SkCommonFlags.h"
|
||||
#include "SkData.h"
|
||||
#include "SkImageEncoder.h"
|
||||
#include "SkMD5.h"
|
||||
#include "SkMallocPixelRef.h"
|
||||
#include "SkOSFile.h"
|
||||
#include "SkStream.h"
|
||||
#include "SkString.h"
|
||||
|
||||
DEFINE_bool(nameByHash, false, "If true, write .../hash.png instead of .../mode/config/name.png");
|
||||
|
||||
namespace DM {
|
||||
|
||||
// Splits off the last N suffixes of name (splitting on _) and appends them to out.
|
||||
// Returns the total number of characters consumed.
|
||||
static int split_suffixes(int N, const char* name, SkTArray<SkString>* out) {
|
||||
SkTArray<SkString> split;
|
||||
SkStrSplit(name, "_", &split);
|
||||
int consumed = 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
// We're splitting off suffixes from the back to front.
|
||||
out->push_back(split[split.count()-i-1]);
|
||||
consumed += SkToInt(out->back().size() + 1); // Add one for the _.
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
inline static SkString find_base_name(const Task& parent, SkTArray<SkString>* suffixList) {
|
||||
const int suffixes = parent.depth() + 1;
|
||||
const SkString& name = parent.name();
|
||||
const int totalSuffixLength = split_suffixes(suffixes, name.c_str(), suffixList);
|
||||
return SkString(name.c_str(), name.size() - totalSuffixLength);
|
||||
}
|
||||
|
||||
WriteTask::WriteTask(const Task& parent, const char* sourceType, SkBitmap bitmap)
|
||||
: CpuTask(parent)
|
||||
, fBaseName(find_base_name(parent, &fSuffixes))
|
||||
, fSourceType(sourceType)
|
||||
, fBitmap(bitmap)
|
||||
, fData(NULL)
|
||||
, fExtension(".png") {
|
||||
}
|
||||
|
||||
WriteTask::WriteTask(const Task& parent,
|
||||
const char* sourceType,
|
||||
SkStreamAsset *data,
|
||||
const char* ext)
|
||||
: CpuTask(parent)
|
||||
, fBaseName(find_base_name(parent, &fSuffixes))
|
||||
, fSourceType(sourceType)
|
||||
, fData(data)
|
||||
, fExtension(ext) {
|
||||
SkASSERT(fData.get());
|
||||
SkASSERT(fData->unique());
|
||||
}
|
||||
|
||||
void WriteTask::makeDirOrFail(SkString dir) {
|
||||
// This can be a little racy, so if it fails check to see if someone else succeeded.
|
||||
if (!sk_mkdir(dir.c_str()) && !sk_isdir(dir.c_str())) {
|
||||
this->fail("Can't make directory.");
|
||||
}
|
||||
}
|
||||
|
||||
static SkString get_md5_string(SkMD5* hasher) {
|
||||
SkMD5::Digest digest;
|
||||
hasher->finish(digest);
|
||||
|
||||
SkString md5;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
md5.appendf("%02x", digest.data[i]);
|
||||
}
|
||||
return md5;
|
||||
}
|
||||
|
||||
static SkString get_md5(const void* ptr, size_t len) {
|
||||
SkMD5 hasher;
|
||||
hasher.write(ptr, len);
|
||||
return get_md5_string(&hasher);
|
||||
}
|
||||
|
||||
static bool write_asset(SkStreamAsset* input, SkWStream* output) {
|
||||
return input->rewind() && output->writeStream(input, input->getLength());
|
||||
}
|
||||
|
||||
static SkString get_md5(SkStreamAsset* stream) {
|
||||
SkMD5 hasher;
|
||||
write_asset(stream, &hasher);
|
||||
return get_md5_string(&hasher);
|
||||
}
|
||||
|
||||
static bool encode_png(const SkBitmap& src, SkFILEWStream* file) {
|
||||
SkBitmap bm;
|
||||
// We can't encode A8 bitmaps as PNGs. Convert them to 8888 first.
|
||||
if (src.info().colorType() == kAlpha_8_SkColorType) {
|
||||
if (!src.copyTo(&bm, kN32_SkColorType)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
bm = src;
|
||||
}
|
||||
return SkImageEncoder::EncodeStream(file, bm, SkImageEncoder::kPNG_Type, 100);
|
||||
}
|
||||
|
||||
void WriteTask::draw() {
|
||||
SkString md5;
|
||||
{
|
||||
SkAutoLockPixels lock(fBitmap);
|
||||
md5 = fData ? get_md5(fData)
|
||||
: get_md5(fBitmap.getPixels(), fBitmap.getSize());
|
||||
}
|
||||
|
||||
SkASSERT(fSuffixes.count() > 0);
|
||||
SkString config = fSuffixes.back();
|
||||
SkString mode("direct");
|
||||
if (fSuffixes.count() > 1) {
|
||||
mode = fSuffixes.fromBack(1);
|
||||
}
|
||||
|
||||
{
|
||||
const JsonWriter::BitmapResult entry = { fBaseName,
|
||||
config,
|
||||
mode,
|
||||
fSourceType,
|
||||
md5 };
|
||||
JsonWriter::AddBitmapResult(entry);
|
||||
}
|
||||
|
||||
SkString dir(FLAGS_writePath[0]);
|
||||
#if defined(SK_BUILD_FOR_IOS)
|
||||
if (dir.equals("@")) {
|
||||
dir.set(FLAGS_resourcePath[0]);
|
||||
}
|
||||
#endif
|
||||
this->makeDirOrFail(dir);
|
||||
|
||||
SkString path;
|
||||
if (FLAGS_nameByHash) {
|
||||
// Flat directory of hash-named files.
|
||||
path = SkOSPath::Join(dir.c_str(), md5.c_str());
|
||||
path.append(fExtension);
|
||||
// We're content-addressed, so it's possible two threads race to write
|
||||
// this file. We let the first one win. This also means we won't
|
||||
// overwrite identical files from previous runs.
|
||||
if (sk_exists(path.c_str())) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Nested by mode, config, etc.
|
||||
for (int i = 0; i < fSuffixes.count(); i++) {
|
||||
dir = SkOSPath::Join(dir.c_str(), fSuffixes[i].c_str());
|
||||
this->makeDirOrFail(dir);
|
||||
}
|
||||
path = SkOSPath::Join(dir.c_str(), fBaseName.c_str());
|
||||
path.append(fExtension);
|
||||
// The path is unique, so two threads can't both write to the same file.
|
||||
// If already present we overwrite here, since the content may have changed.
|
||||
}
|
||||
|
||||
SkFILEWStream file(path.c_str());
|
||||
if (!file.isValid()) {
|
||||
return this->fail("Can't open file.");
|
||||
}
|
||||
|
||||
bool ok = fData ? write_asset(fData, &file)
|
||||
: encode_png(fBitmap, &file);
|
||||
if (!ok) {
|
||||
return this->fail("Can't write to file.");
|
||||
}
|
||||
}
|
||||
|
||||
SkString WriteTask::name() const {
|
||||
SkString name("writing ");
|
||||
for (int i = 0; i < fSuffixes.count(); i++) {
|
||||
name.appendf("%s/", fSuffixes[i].c_str());
|
||||
}
|
||||
name.append(fBaseName.c_str());
|
||||
return name;
|
||||
}
|
||||
|
||||
bool WriteTask::shouldSkip() const {
|
||||
return FLAGS_writePath.isEmpty();
|
||||
}
|
||||
|
||||
} // namespace DM
|
@ -1,45 +0,0 @@
|
||||
#ifndef DMWriteTask_DEFINED
|
||||
#define DMWriteTask_DEFINED
|
||||
|
||||
#include "DMTask.h"
|
||||
#include "SkBitmap.h"
|
||||
#include "SkStream.h"
|
||||
#include "SkString.h"
|
||||
#include "SkTArray.h"
|
||||
|
||||
|
||||
// Writes a bitmap to a file.
|
||||
|
||||
namespace DM {
|
||||
|
||||
class WriteTask : public CpuTask {
|
||||
|
||||
public:
|
||||
WriteTask(const Task& parent, // WriteTask must be a child task.
|
||||
const char* sourceType, // E.g. "GM", "SKP". For humans.
|
||||
SkBitmap bitmap); // Bitmap to encode to PNG and write to disk.
|
||||
|
||||
// Takes ownership of SkStreamAsset
|
||||
WriteTask(const Task& parent, // WriteTask must be a child task.
|
||||
const char* sourceType, // E.g. "GM", "SKP". For humans.
|
||||
SkStreamAsset* data, // Pre-encoded data to write to disk.
|
||||
const char* ext); // File extension.
|
||||
|
||||
void draw() SK_OVERRIDE;
|
||||
bool shouldSkip() const SK_OVERRIDE;
|
||||
SkString name() const SK_OVERRIDE;
|
||||
|
||||
private:
|
||||
SkTArray<SkString> fSuffixes;
|
||||
const SkString fBaseName;
|
||||
const SkString fSourceType;
|
||||
const SkBitmap fBitmap;
|
||||
SkAutoTDelete<SkStreamAsset> fData;
|
||||
const char* fExtension;
|
||||
|
||||
void makeDirOrFail(SkString dir);
|
||||
};
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMWriteTask_DEFINED
|
27
dm/README
27
dm/README
@ -1,27 +0,0 @@
|
||||
DM (Diamond Master, a.k.a Dungeon master, a.k.a GM 2).
|
||||
|
||||
DM is like GM, but multithreaded. It doesn't do everything GM does.
|
||||
|
||||
DM's design is based around Tasks and a TaskRunner.
|
||||
|
||||
A Task represents an independent unit of work that might fail. We make a task
|
||||
for each GM/configuration pair we want to run. Tasks can kick off new tasks
|
||||
themselves. For example, a CpuTask can kick off a ReplayTask to make sure
|
||||
recording and playing back an SkPicture gives the same result as direct
|
||||
rendering.
|
||||
|
||||
The TaskRunner runs all tasks on one of two threadpools, whose sizes are
|
||||
configurable by --cpuThreads and --gpuThreads. Ideally we'd run these on a
|
||||
single threadpool but it can swamp the GPU if we shove too much work into it at
|
||||
once. --cpuThreads defaults to the number of cores on the machine.
|
||||
--gpuThreads defaults to 1, but you may find 2 or 4 runs a little faster.
|
||||
|
||||
So the main flow of DM is:
|
||||
|
||||
for each GM:
|
||||
for each configuration:
|
||||
kick off a new task
|
||||
< tasks run, maybe fail, and maybe kick off new tasks >
|
||||
wait for all tasks to finish
|
||||
report failures
|
||||
|
17
gyp/dm.gypi
17
gyp/dm.gypi
@ -21,6 +21,7 @@
|
||||
'tools.gyp:crash_handler',
|
||||
'tools.gyp:proc_stats',
|
||||
'tools.gyp:sk_tool_utils',
|
||||
'tools.gyp:timer',
|
||||
],
|
||||
'includes': [
|
||||
'gmslides.gypi',
|
||||
@ -29,22 +30,8 @@
|
||||
],
|
||||
'sources': [
|
||||
'../dm/DM.cpp',
|
||||
'../dm/DMCpuGMTask.cpp',
|
||||
'../dm/DMGpuGMTask.cpp',
|
||||
'../dm/DMPDFRasterizeTask.cpp',
|
||||
'../dm/DMImageTask.cpp',
|
||||
'../dm/DMSrcSink.cpp',
|
||||
'../dm/DMJsonWriter.cpp',
|
||||
'../dm/DMPDFTask.cpp',
|
||||
'../dm/DMPipeTask.cpp',
|
||||
'../dm/DMQuiltTask.cpp',
|
||||
'../dm/DMReporter.cpp',
|
||||
'../dm/DMSKPTask.cpp',
|
||||
'../dm/DMSerializeTask.cpp',
|
||||
'../dm/DMTask.cpp',
|
||||
'../dm/DMTaskRunner.cpp',
|
||||
'../dm/DMTestTask.cpp',
|
||||
'../dm/DMUtil.cpp',
|
||||
'../dm/DMWriteTask.cpp',
|
||||
'../gm/gm.cpp',
|
||||
|
||||
'../src/pipe/utils/SamplePipeControllers.cpp',
|
||||
|
@ -7,9 +7,9 @@
|
||||
|
||||
#include "SkCommonFlags.h"
|
||||
|
||||
DEFINE_string(config, "565 8888 pdf gpu nonrendering angle nvprmsaa4",
|
||||
DEFINE_string(config, "565 8888 gpu nonrendering angle nvprmsaa4 ",
|
||||
"Options: 565 8888 pdf gpu nonrendering msaa4 msaa16 nvprmsaa4 nvprmsaa16 "
|
||||
"gpudft gpunull gpudebug angle mesa");
|
||||
"gpudft gpunull gpudebug angle mesa (and many more)");
|
||||
|
||||
DEFINE_bool(cpu, true, "master switch for running CPU-bound work.");
|
||||
|
||||
|
@ -51,3 +51,14 @@ void WallTimer::start() {
|
||||
void WallTimer::end() {
|
||||
fWall = fSysTimer.endWall();
|
||||
}
|
||||
|
||||
SkString HumanizeMs(double ms) {
|
||||
if (ms > 1e+3) return SkStringPrintf("%.3gs", ms/1e3);
|
||||
if (ms < 1e-3) return SkStringPrintf("%.3gns", ms*1e6);
|
||||
#ifdef SK_BUILD_FOR_WIN
|
||||
if (ms < 1) return SkStringPrintf("%.3gus", ms*1e3);
|
||||
#else
|
||||
if (ms < 1) return SkStringPrintf("%.3gµs", ms*1e3);
|
||||
#endif
|
||||
return SkStringPrintf("%.3gms", ms);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
#define Timer_DEFINED
|
||||
|
||||
#include "SkTypes.h"
|
||||
#include "SkString.h"
|
||||
|
||||
#if defined(SK_BUILD_FOR_WIN32)
|
||||
#include "SysTimer_windows.h"
|
||||
@ -70,4 +71,6 @@ private:
|
||||
SysTimer fSysTimer;
|
||||
};
|
||||
|
||||
SkString HumanizeMs(double);
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user