aboutsummaryrefslogtreecommitdiffhomepage
path: root/dm
diff options
context:
space:
mode:
authorGravatar mtklein <mtklein@google.com>2015-01-15 10:15:02 -0800
committerGravatar Commit bot <commit-bot@chromium.org>2015-01-15 10:15:02 -0800
commit114c3cd0543d77aa0ac08d8af436ac7f9d32714d (patch)
tree4735fb4a5cdcfcfecfc48ff8e12ad185ceb3926d /dm
parentc563172f530b5872d725d084d941f14c130d8116 (diff)
Revert of Sketch DM refactor. (patchset #45 id:850001 of https://codereview.chromium.org/788243008/)
Reason for revert: plenty of data Original issue's description: > 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 TBR=bsalomon@google.com,mtklein@chromium.org NOTREECHECKS=true NOTRY=true BUG=skia:3255 Review URL: https://codereview.chromium.org/853883004
Diffstat (limited to 'dm')
-rw-r--r--dm/DM.cpp580
-rw-r--r--dm/DMCpuGMTask.cpp56
-rw-r--r--dm/DMCpuGMTask.h38
-rw-r--r--dm/DMGpuGMTask.cpp67
-rw-r--r--dm/DMGpuGMTask.h43
-rw-r--r--dm/DMImageTask.cpp78
-rw-r--r--dm/DMImageTask.h30
-rw-r--r--dm/DMJsonWriter.cpp2
-rw-r--r--dm/DMJsonWriter.h8
-rw-r--r--dm/DMPDFRasterizeTask.cpp38
-rw-r--r--dm/DMPDFRasterizeTask.h41
-rw-r--r--dm/DMPDFTask.cpp106
-rw-r--r--dm/DMPDFTask.h47
-rw-r--r--dm/DMPipeTask.cpp83
-rw-r--r--dm/DMPipeTask.h41
-rw-r--r--dm/DMQuiltTask.cpp98
-rw-r--r--dm/DMQuiltTask.h39
-rw-r--r--dm/DMReporter.cpp46
-rw-r--r--dm/DMReporter.h36
-rw-r--r--dm/DMSKPTask.cpp31
-rw-r--r--dm/DMSKPTask.h30
-rw-r--r--dm/DMSerializeTask.cpp44
-rw-r--r--dm/DMSerializeTask.h31
-rw-r--r--dm/DMSrcSink.cpp389
-rw-r--r--dm/DMSrcSink.h177
-rw-r--r--dm/DMTask.cpp91
-rw-r--r--dm/DMTask.h74
-rw-r--r--dm/DMTaskRunner.cpp17
-rw-r--r--dm/DMTaskRunner.h29
-rw-r--r--dm/DMTestTask.cpp61
-rw-r--r--dm/DMTestTask.h66
-rw-r--r--dm/DMUtil.cpp118
-rw-r--r--dm/DMUtil.h43
-rw-r--r--dm/DMWriteTask.cpp189
-rw-r--r--dm/DMWriteTask.h45
-rw-r--r--dm/README27
36 files changed, 2013 insertions, 926 deletions
diff --git a/dm/DM.cpp b/dm/DM.cpp
index d8efc58448..7113ad839a 100644
--- a/dm/DM.cpp
+++ b/dm/DM.cpp
@@ -1,423 +1,293 @@
+// Main binary for DM.
+// For a high-level overview, please see dm/README.
+
#include "CrashHandler.h"
-#include "DMJsonWriter.h"
-#include "DMSrcSink.h"
-#include "OverwriteLine.h"
-#include "ProcStats.h"
-#include "SkBBHFactory.h"
+#include "LazyDecodeBitmap.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 "Timer.h"
+#include "gm.h"
+#include "sk_tool_utils.h"
+#include "sk_tool_utils_flags.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
-DEFINE_bool(tests, true, "Run tests?");
-DEFINE_string(images, "resources", "Images to decode.");
-DEFINE_string(src, "gm skp image subset", "Source types to test.");
-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.");
+#include <ctype.h>
-__SK_FORCE_IMAGE_DECODER_LINKING;
-using namespace DM;
+using skiagm::GM;
+using skiagm::GMRegistry;
+using skiatest::Test;
+using skiatest::TestRegistry;
-/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+static const char kGpuAPINameGL[] = "gl";
+static const char kGpuAPINameGLES[] = "gles";
-static int gPending = 0, gFailures = 0;
+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?");
-static void fail(ImplicitString err) {
- SkDebugf("\n\nERROR: %s\n\n", err.c_str());
- sk_atomic_inc(&gFailures);
-}
+__SK_FORCE_IMAGE_DECODER_LINKING;
-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 DM::RasterizePdfProc get_pdf_rasterizer_proc() {
+ return reinterpret_cast<DM::RasterizePdfProc>(
+ FLAGS_rasterPDF ? RASTERIZE_PDF_PROC : NULL);
}
-/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
-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;
+// "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 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())));
- }
+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__))); \
}
- 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.
- }
+ 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());
}
}
+#undef START
}
-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_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 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;
-
- SkBitmap bitmap;
- SkDynamicMemoryWStream stream;
- Error err = sink->draw(noop, &bitmap, &stream);
- if (!err.isEmpty()) {
- SkDebugf("Skipping %s: %s\n", tag, err.c_str());
+static void find_files(const char* dir,
+ const char* suffixes[],
+ size_t num_suffixes,
+ SkTArray<SkString>* files) {
+ if (0 == strcmp(dir, "")) {
return;
}
- Tagged<Sink>& ts = gSinks.push_back();
- ts.reset(sink.detach());
- ts.tag = tag;
+ 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()));
+ }
+ }
+ }
}
-static bool gpu_supported() {
-#if SK_SUPPORT_GPU
- return FLAGS_gpu;
-#else
- return false;
-#endif
-}
+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);
+ }
-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
+ 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())));
}
+}
- 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);
+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*/)));
}
-#undef SINK
- return NULL;
}
-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);
+static void report_failures(const SkTArray<SkString>& failures) {
+ if (failures.count() == 0) {
+ return;
+ }
- 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);
+ SkDebugf("Failures:\n");
+ for (int i = 0; i < failures.count(); i++) {
+ SkDebugf(" %s\n", failures[i].c_str());
}
-#undef VIA
- return NULL;
+ SkDebugf("%d failures.\n", failures.count());
}
-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);
- }
- }
+static GrGLStandard get_gl_standard() {
+ if (FLAGS_gpuAPI.contains(kGpuAPINameGL)) {
+ return kGL_GrGLStandard;
+ }
+ if (FLAGS_gpuAPI.contains(kGpuAPINameGLES)) {
+ return kGLES_GrGLStandard;
+ }
+ return kNone_GrGLStandard;
}
-// 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);
- }
- }
+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();
}
- 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;
- }
+int dm_main();
+int dm_main() {
+ SetupCrashHandler();
+ SkAutoGraphics ag;
+ SkTaskGroup::Enabler enabled(FLAGS_threads);
- 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;
- }
- }
+ if (FLAGS_dryRun || FLAGS_veryVerbose) {
+ FLAGS_verbose = true;
}
-};
+#if SK_ENABLE_INST_COUNT
+ gPrintInstCount = FLAGS_leaks;
+#endif
-// 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);
+ SkTArray<SkString> configs;
+ for (int i = 0; i < FLAGS_config.count(); i++) {
+ SkStrSplit(FLAGS_config[i], ", ", &configs);
}
-}
-/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+ GrGLStandard gpuAPI = get_gl_standard();
-// Unit tests don't fit so well into the Src/Sink model, so we give them special treatment.
+ SkTDArray<GMRegistry::Factory> gms;
+ if (FLAGS_gms) {
+ append_matching_factories<GM>(GMRegistry::Head(), &gms);
+ }
-static struct : public skiatest::Reporter {
- void onReportFailed(const skiatest::Failure& failure) SK_OVERRIDE {
- SkString s;
- failure.getFailureString(&s);
- fail(s);
- JsonWriter::AddTestFailure(failure);
+ SkTDArray<TestRegistry::Factory> tests;
+ if (FLAGS_tests) {
+ append_matching_factories<Test>(TestRegistry::Head(), &tests);
}
- 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()) {
- continue;
- }
- if (!test->isGPUTest() && !FLAGS_cpu) {
- continue;
- }
- test->setReporter(&gTestReporter);
- gTests.push_back().reset(test.detach());
+ SkTArray<SkString> skps;
+ if (!FLAGS_skps.isEmpty()) {
+ const char* suffixes[] = { "skp" };
+ find_files(FLAGS_skps[0], suffixes, SK_ARRAY_COUNT(suffixes), &skps);
}
-}
-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()));
- }
+ 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);
}
- timer.end();
- done(timer.fWall, "test", "", test->getName());
-}
-/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
-int dm_main();
-int dm_main() {
- SetupCrashHandler();
- SkAutoGraphics ag;
- SkTaskGroup::Enabler enabled(FLAGS_threads);
+ 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;
- gather_srcs();
- gather_sinks();
- gather_tests();
-
- 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]));
- }
- }
+ 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();
- 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();
+ DM::JsonWriter::DumpJson();
- if (!FLAGS_verbose) {
- SkDebugf("\n");
+ SkDebugf("\n");
+#ifdef SK_DEBUG
+ if (FLAGS_portableFonts && FLAGS_reportUsedChars) {
+ sk_tool_utils::report_used_chars();
}
+#endif
- JsonWriter::DumpJson();
- return gPending == 0 && gFailures == 0 ? 0 : 1;
+ SkTArray<SkString> failures;
+ reporter.getFailures(&failures);
+ report_failures(failures);
+ return failures.count() > 0;
}
#if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL)
diff --git a/dm/DMCpuGMTask.cpp b/dm/DMCpuGMTask.cpp
new file mode 100644
index 0000000000..1a2c00d1d7
--- /dev/null
+++ b/dm/DMCpuGMTask.cpp
@@ -0,0 +1,56 @@
+#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
diff --git a/dm/DMCpuGMTask.h b/dm/DMCpuGMTask.h
new file mode 100644
index 0000000000..8258f69e2e
--- /dev/null
+++ b/dm/DMCpuGMTask.h
@@ -0,0 +1,38 @@
+#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
diff --git a/dm/DMGpuGMTask.cpp b/dm/DMGpuGMTask.cpp
new file mode 100644
index 0000000000..9347ebdfe9
--- /dev/null
+++ b/dm/DMGpuGMTask.cpp
@@ -0,0 +1,67 @@
+#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
diff --git a/dm/DMGpuGMTask.h b/dm/DMGpuGMTask.h
new file mode 100644
index 0000000000..87436d39f5
--- /dev/null
+++ b/dm/DMGpuGMTask.h
@@ -0,0 +1,43 @@
+#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
diff --git a/dm/DMImageTask.cpp b/dm/DMImageTask.cpp
new file mode 100644
index 0000000000..1657984f1e
--- /dev/null
+++ b/dm/DMImageTask.cpp
@@ -0,0 +1,78 @@
+#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
diff --git a/dm/DMImageTask.h b/dm/DMImageTask.h
new file mode 100644
index 0000000000..eb059d7625
--- /dev/null
+++ b/dm/DMImageTask.h
@@ -0,0 +1,30 @@
+#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
diff --git a/dm/DMJsonWriter.cpp b/dm/DMJsonWriter.cpp
index 8aea814aed..71290a33fb 100644
--- a/dm/DMJsonWriter.cpp
+++ b/dm/DMJsonWriter.cpp
@@ -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);
diff --git a/dm/DMJsonWriter.h b/dm/DMJsonWriter.h
index 58d85d358d..66ce530624 100644
--- a/dm/DMJsonWriter.h
+++ b/dm/DMJsonWriter.h
@@ -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", "serialize", "pipe"
- SkString sourceType; // "gm", "skp", "image"
+ SkString name; // E.g. "ninepatch-stretch", "desk-gws_skp"
+ SkString config; // "gpu", "8888"
+ SkString mode; // "direct", "default-tilegrid", "pipe"
+ SkString sourceType; // "GM", "SKP"
SkString md5; // In ASCII, so 32 bytes long.
- SkString ext; // Extension of file we wrote: "png", "pdf", ...
};
/**
diff --git a/dm/DMPDFRasterizeTask.cpp b/dm/DMPDFRasterizeTask.cpp
new file mode 100644
index 0000000000..d32178b78b
--- /dev/null
+++ b/dm/DMPDFRasterizeTask.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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
diff --git a/dm/DMPDFRasterizeTask.h b/dm/DMPDFRasterizeTask.h
new file mode 100644
index 0000000000..8148499061
--- /dev/null
+++ b/dm/DMPDFRasterizeTask.h
@@ -0,0 +1,41 @@
+/*
+ * 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
diff --git a/dm/DMPDFTask.cpp b/dm/DMPDFTask.cpp
new file mode 100644
index 0000000000..3102478c2f
--- /dev/null
+++ b/dm/DMPDFTask.cpp
@@ -0,0 +1,106 @@
+/*
+ * 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
diff --git a/dm/DMPDFTask.h b/dm/DMPDFTask.h
new file mode 100644
index 0000000000..8c3ba1dd12
--- /dev/null
+++ b/dm/DMPDFTask.h
@@ -0,0 +1,47 @@
+#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
diff --git a/dm/DMPipeTask.cpp b/dm/DMPipeTask.cpp
new file mode 100644
index 0000000000..383b51a38d
--- /dev/null
+++ b/dm/DMPipeTask.cpp
@@ -0,0 +1,83 @@
+#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
diff --git a/dm/DMPipeTask.h b/dm/DMPipeTask.h
new file mode 100644
index 0000000000..b0c8ea716a
--- /dev/null
+++ b/dm/DMPipeTask.h
@@ -0,0 +1,41 @@
+#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
diff --git a/dm/DMQuiltTask.cpp b/dm/DMQuiltTask.cpp
new file mode 100644
index 0000000000..83a989da71
--- /dev/null
+++ b/dm/DMQuiltTask.cpp
@@ -0,0 +1,98 @@
+#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
diff --git a/dm/DMQuiltTask.h b/dm/DMQuiltTask.h
new file mode 100644
index 0000000000..ae45e5cd95
--- /dev/null
+++ b/dm/DMQuiltTask.h
@@ -0,0 +1,39 @@
+#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
diff --git a/dm/DMReporter.cpp b/dm/DMReporter.cpp
new file mode 100644
index 0000000000..7b2cea8136
--- /dev/null
+++ b/dm/DMReporter.cpp
@@ -0,0 +1,46 @@
+#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
diff --git a/dm/DMReporter.h b/dm/DMReporter.h
new file mode 100644
index 0000000000..f7803dc67f
--- /dev/null
+++ b/dm/DMReporter.h
@@ -0,0 +1,36 @@
+#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
diff --git a/dm/DMSKPTask.cpp b/dm/DMSKPTask.cpp
new file mode 100644
index 0000000000..d633594ffc
--- /dev/null
+++ b/dm/DMSKPTask.cpp
@@ -0,0 +1,31 @@
+#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
diff --git a/dm/DMSKPTask.h b/dm/DMSKPTask.h
new file mode 100644
index 0000000000..2d830e022b
--- /dev/null
+++ b/dm/DMSKPTask.h
@@ -0,0 +1,30 @@
+#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
diff --git a/dm/DMSerializeTask.cpp b/dm/DMSerializeTask.cpp
new file mode 100644
index 0000000000..a3e2503c6d
--- /dev/null
+++ b/dm/DMSerializeTask.cpp
@@ -0,0 +1,44 @@
+#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
diff --git a/dm/DMSerializeTask.h b/dm/DMSerializeTask.h
new file mode 100644
index 0000000000..16025c2c3e
--- /dev/null
+++ b/dm/DMSerializeTask.h
@@ -0,0 +1,31 @@
+#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
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
deleted file mode 100644
index 4f272f11f8..0000000000
--- a/dm/DMSrcSink.cpp
+++ /dev/null
@@ -1,389 +0,0 @@
-#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
diff --git a/dm/DMSrcSink.h b/dm/DMSrcSink.h
deleted file mode 100644
index 3f88f6350b..0000000000
--- a/dm/DMSrcSink.h
+++ /dev/null
@@ -1,177 +0,0 @@
-#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
diff --git a/dm/DMTask.cpp b/dm/DMTask.cpp
new file mode 100644
index 0000000000..3fa5c39718
--- /dev/null
+++ b/dm/DMTask.cpp
@@ -0,0 +1,91 @@
+#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
diff --git a/dm/DMTask.h b/dm/DMTask.h
new file mode 100644
index 0000000000..3f41b497ba
--- /dev/null
+++ b/dm/DMTask.h
@@ -0,0 +1,74 @@
+#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
diff --git a/dm/DMTaskRunner.cpp b/dm/DMTaskRunner.cpp
new file mode 100644
index 0000000000..92381a7a7e
--- /dev/null
+++ b/dm/DMTaskRunner.cpp
@@ -0,0 +1,17 @@
+#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
diff --git a/dm/DMTaskRunner.h b/dm/DMTaskRunner.h
new file mode 100644
index 0000000000..3d4e491b9c
--- /dev/null
+++ b/dm/DMTaskRunner.h
@@ -0,0 +1,29 @@
+#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
diff --git a/dm/DMTestTask.cpp b/dm/DMTestTask.cpp
new file mode 100644
index 0000000000..366087c351
--- /dev/null
+++ b/dm/DMTestTask.cpp
@@ -0,0 +1,61 @@
+#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
diff --git a/dm/DMTestTask.h b/dm/DMTestTask.h
new file mode 100644
index 0000000000..4be57a9f86
--- /dev/null
+++ b/dm/DMTestTask.h
@@ -0,0 +1,66 @@
+#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
diff --git a/dm/DMUtil.cpp b/dm/DMUtil.cpp
new file mode 100644
index 0000000000..c3363a627c
--- /dev/null
+++ b/dm/DMUtil.cpp
@@ -0,0 +1,118 @@
+#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
diff --git a/dm/DMUtil.h b/dm/DMUtil.h
new file mode 100644
index 0000000000..dfb7f92022
--- /dev/null
+++ b/dm/DMUtil.h
@@ -0,0 +1,43 @@
+#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
diff --git a/dm/DMWriteTask.cpp b/dm/DMWriteTask.cpp
new file mode 100644
index 0000000000..aee43a80b8
--- /dev/null
+++ b/dm/DMWriteTask.cpp
@@ -0,0 +1,189 @@
+#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
diff --git a/dm/DMWriteTask.h b/dm/DMWriteTask.h
new file mode 100644
index 0000000000..fb80058fd0
--- /dev/null
+++ b/dm/DMWriteTask.h
@@ -0,0 +1,45 @@
+#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
diff --git a/dm/README b/dm/README
new file mode 100644
index 0000000000..8b809b3488
--- /dev/null
+++ b/dm/README
@@ -0,0 +1,27 @@
+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
+