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