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

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