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