From d36522d12d3e71958e50683a7eef43dc2a47d96d Mon Sep 17 00:00:00 2001 From: "mtklein@google.com" Date: Wed, 16 Oct 2013 13:02:15 +0000 Subject: [PATCH] dm is like gm, but faster and with fewer features. This is sort of the near-minimal proof-of-concept skeleton. - It can run existing GMs. - It supports most configs (just not PDF). - --replay is the only "fancy" feature it currently supports Hopefully you will be disturbed by its speed. BUG= R=epoger@google.com Review URL: https://codereview.chromium.org/22839016 git-svn-id: http://skia.googlecode.com/svn/trunk@11802 2bbb7eff-a529-9590-31e7-b0007b416f81 --- dm/DM.cpp | 166 ++++++++++++++++++++++++++++++++++++++++++++++++ dm/DMComparisonTask.cpp | 22 +++++++ dm/DMComparisonTask.h | 31 +++++++++ dm/DMCpuTask.cpp | 57 +++++++++++++++++ dm/DMCpuTask.h | 44 +++++++++++++ dm/DMGpuTask.cpp | 63 ++++++++++++++++++ dm/DMGpuTask.h | 45 +++++++++++++ dm/DMReplayTask.cpp | 50 +++++++++++++++ dm/DMReplayTask.h | 40 ++++++++++++ dm/DMReporter.cpp | 24 +++++++ dm/DMReporter.h | 39 ++++++++++++ dm/DMTask.cpp | 42 ++++++++++++ dm/DMTask.h | 43 +++++++++++++ dm/DMTaskRunner.cpp | 28 ++++++++ dm/DMTaskRunner.h | 28 ++++++++ dm/DMUtil.cpp | 23 +++++++ dm/DMUtil.h | 23 +++++++ dm/README | 37 +++++++++++ gyp/dm.gyp | 43 +++++++++++++ gyp/everything.gyp | 5 +- 20 files changed, 852 insertions(+), 1 deletion(-) create mode 100644 dm/DM.cpp create mode 100644 dm/DMComparisonTask.cpp create mode 100644 dm/DMComparisonTask.h create mode 100644 dm/DMCpuTask.cpp create mode 100644 dm/DMCpuTask.h create mode 100644 dm/DMGpuTask.cpp create mode 100644 dm/DMGpuTask.h create mode 100644 dm/DMReplayTask.cpp create mode 100644 dm/DMReplayTask.h create mode 100644 dm/DMReporter.cpp create mode 100644 dm/DMReporter.h create mode 100644 dm/DMTask.cpp create mode 100644 dm/DMTask.h create mode 100644 dm/DMTaskRunner.cpp create mode 100644 dm/DMTaskRunner.h create mode 100644 dm/DMUtil.cpp create mode 100644 dm/DMUtil.h create mode 100644 dm/README create mode 100644 gyp/dm.gyp diff --git a/dm/DM.cpp b/dm/DM.cpp new file mode 100644 index 0000000..d149b48 --- /dev/null +++ b/dm/DM.cpp @@ -0,0 +1,166 @@ +// Main binary for DM. +// For a high-level overview, please see dm/README. + +#include "GrContext.h" +#include "GrContextFactory.h" +#include "SkCommandLineFlags.h" +#include "SkForceLinking.h" +#include "SkGraphics.h" +#include "gm.h" + +#include "DMReporter.h" +#include "DMTask.h" +#include "DMTaskRunner.h" +#include "DMCpuTask.h" +#include "DMGpuTask.h" + +#include + +using skiagm::GM; +using skiagm::GMRegistry; +using skiagm::Expectations; +using skiagm::ExpectationsSource; +using skiagm::JsonExpectationsSource; + +DEFINE_int32(cpuThreads, -1, "Threads for CPU work. Default NUM_CPUS."); +DEFINE_int32(gpuThreads, 1, "Threads for GPU work."); +DEFINE_string(expectations, "", "Compare generated images against JSON expectations at this path."); +DEFINE_string(resources, "resources", "Path to resources directory."); +DEFINE_string(match, "", "[~][^]substring[$] [...] of GM name to run.\n" + "Multiple matches may be separated by spaces.\n" + "~ causes a matching GM to always be skipped\n" + "^ requires the start of the GM to match\n" + "$ requires the end of the GM to match\n" + "^ and $ requires an exact match\n" + "If a GM does not match any list entry,\n" + "it is skipped unless some list entry starts with ~"); +DEFINE_string(config, "8888 gpu", + "Options: 565 8888 gpu msaa4 msaa16 gpunull gpudebug angle mesa"); // TODO(mtklein): pdf + +__SK_FORCE_IMAGE_DECODER_LINKING; + +// Split str on any characters in delimiters into out. (Think, strtok with a sane API.) +static void split(const char* str, const char* delimiters, SkTArray* out) { + const char* end = str + strlen(str); + while (str != end) { + // Find a token. + const size_t len = strcspn(str, delimiters); + out->push_back().set(str, len); + str += len; + // Skip any delimiters. + str += strspn(str, delimiters); + } +} + +// "FooBar" -> "foobar". Obviously, ASCII only. +static SkString lowercase(SkString s) { + for (size_t i = 0; i < s.size(); i++) { + s[i] = tolower(s[i]); + } + return s; +} + +static void kick_off_tasks(const SkTDArray& gms, + const SkTArray& configs, + const ExpectationsSource& expectations, + DM::Reporter* reporter, + DM::TaskRunner* tasks) { + const SkBitmap::Config _565 = SkBitmap::kRGB_565_Config; + const SkBitmap::Config _8888 = SkBitmap::kARGB_8888_Config; + const GrContextFactory::GLContextType native = GrContextFactory::kNative_GLContextType; + const GrContextFactory::GLContextType null = GrContextFactory::kNull_GLContextType; + const GrContextFactory::GLContextType debug = GrContextFactory::kDebug_GLContextType; + const GrContextFactory::GLContextType angle = + #if SK_ANGLE + GrContextFactory::kANGLE_GLContextType; + #else + native; + #endif + const GrContextFactory::GLContextType mesa = + #if SK_MESA + GLContextFactory::kMESA_GLContextType; + #else + native; + #endif + + for (int i = 0; i < gms.count(); i++) { + SkAutoTDelete gmForName(gms[i](NULL)); + if (SkCommandLineFlags::ShouldSkip(FLAGS_match, gmForName->shortName())) continue; + +#define START(name, type, ...) \ + if (lowercase(configs[j]).equals(name)) { \ + tasks->add(SkNEW_ARGS(DM::type, \ + (name, reporter, tasks, expectations, gms[i], __VA_ARGS__))); \ + } + for (int j = 0; j < configs.count(); j++) { + START("565", CpuTask, _565); + START("8888", CpuTask, _8888); + START("gpu", GpuTask, _8888, native, 0); + START("msaa4", GpuTask, _8888, native, 4); + START("msaa16", GpuTask, _8888, native, 16); + START("gpunull", GpuTask, _8888, null, 0); + START("gpudebug", GpuTask, _8888, debug, 0); + START("angle", GpuTask, _8888, angle, 0); + START("mesa", GpuTask, _8888, mesa, 0); + //START("pdf", PdfTask, _8888); + } + } +#undef START +} + +static void report_failures(const DM::Reporter& reporter) { + SkTArray failures; + reporter.getFailures(&failures); + + if (failures.count() == 0) { + return; + } + + SkDebugf("Failures:\n"); + for (int i = 0; i < failures.count(); i++) { + SkDebugf(" %s\n", failures[i].c_str()); + } +} + +class NoExpectations : public ExpectationsSource { +public: + Expectations get(const char* /*testName*/) const SK_OVERRIDE { + return Expectations(); + } +}; + + +int main(int argc, char** argv) { + SkGraphics::Init(); + + SkCommandLineFlags::Parse(argc, argv); + GM::SetResourcePath(FLAGS_resources[0]); + SkTArray configs; + for (int i = 0; i < FLAGS_config.count(); i++) { + split(FLAGS_config[i], ", ", &configs); + } + + SkTDArray gms; + for (const GMRegistry* reg = GMRegistry::Head(); reg != NULL; reg = reg->next()) { + *gms.append() = reg->factory(); + } + SkDebugf("%d GMs x %d configs\n", gms.count(), configs.count()); + + SkAutoTUnref expectations(SkNEW(NoExpectations)); + if (FLAGS_expectations.count() > 0) { + expectations.reset(SkNEW_ARGS(JsonExpectationsSource, (FLAGS_expectations[0]))); + } + + DM::Reporter reporter; + DM::TaskRunner tasks(FLAGS_cpuThreads, FLAGS_gpuThreads); + kick_off_tasks(gms, configs, *expectations, &reporter, &tasks); + tasks.wait(); + + reporter.updateStatusLine(); + SkDebugf("\n"); + report_failures(reporter); + + SkGraphics::Term(); + + return reporter.failed() > 0; +} diff --git a/dm/DMComparisonTask.cpp b/dm/DMComparisonTask.cpp new file mode 100644 index 0000000..f4f742c --- /dev/null +++ b/dm/DMComparisonTask.cpp @@ -0,0 +1,22 @@ +#include "DMComparisonTask.h" +#include "DMUtil.h" + +namespace DM { + +ComparisonTask::ComparisonTask(const Task& parent, + skiagm::Expectations expectations, + SkBitmap bitmap) + : Task(parent) + , fName(parent.name()) // Masquerade as parent so failures are attributed to it. + , fExpectations(expectations) + , fBitmap(bitmap) + {} + +void ComparisonTask::draw() { + const skiagm::GmResultDigest digest(fBitmap); + if (!meetsExpectations(fExpectations, digest)) { + this->fail(); + } +} + +} // namespace DM diff --git a/dm/DMComparisonTask.h b/dm/DMComparisonTask.h new file mode 100644 index 0000000..265a58c --- /dev/null +++ b/dm/DMComparisonTask.h @@ -0,0 +1,31 @@ +#ifndef DMComparisonTask_DEFINED +#define DMComparisonTask_DEFINED + +#include "DMTask.h" +#include "SkBitmap.h" +#include "SkString.h" +#include "gm_expectations.h" + +namespace DM { + +// We use ComparisonTask to move CPU-bound comparison work of GpuTasks back to +// the main thread pool, where we probably have more threads available. + +class ComparisonTask : public Task { +public: + ComparisonTask(const Task& parent, skiagm::Expectations, SkBitmap); + + virtual void draw() SK_OVERRIDE; + virtual bool usesGpu() const SK_OVERRIDE { return false; } + virtual bool shouldSkip() const SK_OVERRIDE { return false; } + virtual SkString name() const SK_OVERRIDE { return fName; } + +private: + const SkString fName; + const skiagm::Expectations fExpectations; + const SkBitmap fBitmap; +}; + +} // namespace DM + +#endif // DMComparisonTask_DEFINED diff --git a/dm/DMCpuTask.cpp b/dm/DMCpuTask.cpp new file mode 100644 index 0000000..5534ba4 --- /dev/null +++ b/dm/DMCpuTask.cpp @@ -0,0 +1,57 @@ +#include "DMCpuTask.h" +#include "DMReplayTask.h" +#include "DMUtil.h" +#include "SkCommandLineFlags.h" + +DEFINE_bool(replay, false, "If true, run replay tests for each CpuTask."); +// TODO(mtklein): add the other various options + +namespace DM { + +CpuTask::CpuTask(const char* name, + Reporter* reporter, + TaskRunner* taskRunner, + const skiagm::ExpectationsSource& expectations, + skiagm::GMRegistry::Factory gmFactory, + SkBitmap::Config config) + : Task(reporter, taskRunner) + , fGMFactory(gmFactory) + , fGM(fGMFactory(NULL)) + , fName(underJoin(fGM->shortName(), name)) + , fExpectations(expectations.get(png(fName).c_str())) + , fConfig(config) + {} + +void CpuTask::draw() { + SkBitmap bitmap; + bitmap.setConfig(fConfig, fGM->width(), fGM->height()); + bitmap.allocPixels(); + bitmap.eraseColor(0x00000000); + SkCanvas canvas(bitmap); + + canvas.concat(fGM->getInitialTransform()); + fGM->draw(&canvas); + canvas.flush(); + + const skiagm::GmResultDigest digest(bitmap); + if (!meetsExpectations(fExpectations, digest)) { + this->fail(); + } + + if (FLAGS_replay) { + this->spawnChild(SkNEW_ARGS(ReplayTask, + ("replay", *this, fGMFactory(NULL), digest, fConfig))); + } +} + +bool CpuTask::shouldSkip() const { + if (SkBitmap::kRGB_565_Config == fConfig && (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/DMCpuTask.h b/dm/DMCpuTask.h new file mode 100644 index 0000000..998ed7b --- /dev/null +++ b/dm/DMCpuTask.h @@ -0,0 +1,44 @@ +#ifndef DMCpuTask_DEFINED +#define DMCpuTask_DEFINED + +#include "DMReporter.h" +#include "DMTask.h" +#include "DMTaskRunner.h" +#include "SkBitmap.h" +#include "SkString.h" +#include "SkTemplates.h" +#include "gm.h" +#include "gm_expectations.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. +// Currently: +// --replay: spawn a DMReplayTask to record into a picture, draw the picture, and compare. + +namespace DM { + +class CpuTask : public Task { +public: + CpuTask(const char* name, + Reporter*, + TaskRunner*, + const skiagm::ExpectationsSource&, + skiagm::GMRegistry::Factory, + SkBitmap::Config); + + virtual void draw() SK_OVERRIDE; + virtual bool usesGpu() const SK_OVERRIDE { return false; } + virtual bool shouldSkip() const SK_OVERRIDE; + virtual SkString name() const SK_OVERRIDE { return fName; } + +private: + skiagm::GMRegistry::Factory fGMFactory; + SkAutoTDelete fGM; + const SkString fName; + const skiagm::Expectations fExpectations; + const SkBitmap::Config fConfig; +}; + +} // namespace DM + +#endif // DMCpuTask_DEFINED diff --git a/dm/DMGpuTask.cpp b/dm/DMGpuTask.cpp new file mode 100644 index 0000000..9205cb9 --- /dev/null +++ b/dm/DMGpuTask.cpp @@ -0,0 +1,63 @@ +#include "DMGpuTask.h" + +#include "DMComparisonTask.h" +#include "DMUtil.h" +#include "SkCommandLineFlags.h" +#include "SkGpuDevice.h" +#include "SkTLS.h" + +namespace DM { + +GpuTask::GpuTask(const char* name, + Reporter* reporter, + TaskRunner* taskRunner, + const skiagm::ExpectationsSource& expectations, + skiagm::GMRegistry::Factory gmFactory, + SkBitmap::Config config, + GrContextFactory::GLContextType contextType, + int sampleCount) + : Task(reporter, taskRunner) + , fGM(gmFactory(NULL)) + , fName(underJoin(fGM->shortName(), name)) + , fExpectations(expectations.get(png(fName).c_str())) + , fConfig(config) + , fContextType(contextType) + , fSampleCount(sampleCount) + {} + +static void* new_gr_context_factory() { + return SkNEW(GrContextFactory); +} + +static void delete_gr_context_factory(void* factory) { + return SkDELETE((GrContextFactory*) factory); +} + +static GrContextFactory* get_gr_factory() { + return reinterpret_cast(SkTLS::Get(&new_gr_context_factory, + &delete_gr_context_factory)); +} + +void GpuTask::draw() { + GrContext* gr = get_gr_factory()->get(fContextType); // Will be owned by device. + SkGpuDevice device(gr, fConfig, fGM->width(), fGM->height(), fSampleCount); + SkCanvas canvas(&device); + + canvas.concat(fGM->getInitialTransform()); + fGM->draw(&canvas); + canvas.flush(); + + SkBitmap bitmap; + bitmap.setConfig(fConfig, fGM->width(), fGM->height()); + canvas.readPixels(&bitmap, 0, 0); + + // We offload checksum comparison to the main CPU threadpool. + // This cuts run time by about 30%. + this->spawnChild(SkNEW_ARGS(ComparisonTask, (*this, fExpectations, bitmap))); +} + +bool GpuTask::shouldSkip() const { + return fGM->getFlags() & skiagm::GM::kSkipGPU_Flag; +} + +} // namespace DM diff --git a/dm/DMGpuTask.h b/dm/DMGpuTask.h new file mode 100644 index 0000000..87c530b --- /dev/null +++ b/dm/DMGpuTask.h @@ -0,0 +1,45 @@ +#ifndef DMGpuTask_DEFINED +#define DMGpuTask_DEFINED + +#include "DMReporter.h" +#include "DMTask.h" +#include "DMTaskRunner.h" +#include "GrContextFactory.h" +#include "SkBitmap.h" +#include "SkString.h" +#include "SkTemplates.h" +#include "gm.h" +#include "gm_expectations.h" + +// This is the main entry point for drawing GMs with the GPU. + +namespace DM { + +class GpuTask : public Task { +public: + GpuTask(const char* name, + Reporter*, + TaskRunner*, + const skiagm::ExpectationsSource&, + skiagm::GMRegistry::Factory, + SkBitmap::Config, + GrContextFactory::GLContextType, + int sampleCount); + + virtual void draw() SK_OVERRIDE; + virtual bool usesGpu() const SK_OVERRIDE { return true; } + virtual bool shouldSkip() const SK_OVERRIDE; + virtual SkString name() const SK_OVERRIDE { return fName; } + +private: + SkAutoTDelete fGM; + const SkString fName; + const skiagm::Expectations fExpectations; + const SkBitmap::Config fConfig; + const GrContextFactory::GLContextType fContextType; + const int fSampleCount; +}; + +} // namespace DM + +#endif // DMGpuTask_DEFINED diff --git a/dm/DMReplayTask.cpp b/dm/DMReplayTask.cpp new file mode 100644 index 0000000..bc94f73 --- /dev/null +++ b/dm/DMReplayTask.cpp @@ -0,0 +1,50 @@ +#include "DMReplayTask.h" +#include "DMUtil.h" + +#include "SkPicture.h" + +namespace DM { + +ReplayTask::ReplayTask(const char* suffix, + const Task& parent, + skiagm::GM* gm, + skiagm::GmResultDigest reference, + SkBitmap::Config config) + : Task(parent) + , fName(underJoin(parent.name().c_str(), suffix)) + , fGM(gm) + , fReference(reference) + , fConfig(config) + {} + +void ReplayTask::draw() { + SkPicture picture; + SkCanvas* canvas = picture.beginRecording(fGM->width(), fGM->height(), 0 /*flags*/); + + canvas->concat(fGM->getInitialTransform()); + fGM->draw(canvas); + canvas->flush(); + + picture.endRecording(); + + SkBitmap bitmap; + bitmap.setConfig(fConfig, fGM->width(), fGM->height()); + bitmap.allocPixels(); + bitmap.eraseColor(0x00000000); + + SkCanvas replay(bitmap); + replay.drawPicture(picture); + replay.flush(); + + const skiagm::GmResultDigest replayDigest(bitmap); + if (!replayDigest.equals(fReference)) { + this->fail(); + } +} + +bool ReplayTask::shouldSkip() const { + return fGM->getFlags() & skiagm::GM::kGPUOnly_Flag || + fGM->getFlags() & skiagm::GM::kSkipPicture_Flag; +} + +} // namespace diff --git a/dm/DMReplayTask.h b/dm/DMReplayTask.h new file mode 100644 index 0000000..0ed9351 --- /dev/null +++ b/dm/DMReplayTask.h @@ -0,0 +1,40 @@ +#ifndef DMReplayTask_DEFINED +#define DMReplayTask_DEFINED + +#include "DMReporter.h" +#include "DMTask.h" +#include "DMTaskRunner.h" +#include "SkBitmap.h" +#include "SkString.h" +#include "SkTemplates.h" +#include "gm.h" +#include "gm_expectations.h" + +// Records a GM through an SkPicture, draws it, and compares against the reference checksum. + +namespace DM { + +class ReplayTask : public Task { + +public: + ReplayTask(const char* name, + const Task& parent, + skiagm::GM*, + skiagm::GmResultDigest reference, + SkBitmap::Config); + + virtual void draw() SK_OVERRIDE; + virtual bool usesGpu() const SK_OVERRIDE { return false; } + virtual bool shouldSkip() const SK_OVERRIDE; + virtual SkString name() const SK_OVERRIDE { return fName; } + +private: + const SkString fName; + SkAutoTDelete fGM; + const skiagm::GmResultDigest fReference; + const SkBitmap::Config fConfig; +}; + +} // namespace DM + +#endif // DMReplayTask_DEFINED diff --git a/dm/DMReporter.cpp b/dm/DMReporter.cpp new file mode 100644 index 0000000..7a0c20e --- /dev/null +++ b/dm/DMReporter.cpp @@ -0,0 +1,24 @@ +#include "DMReporter.h" + +namespace DM { + +void Reporter::updateStatusLine() const { + SkDebugf("\r\033[K%d / %d, %d failed", this->finished(), this->started(), this->failed()); +} + +int32_t Reporter::failed() const { + SkAutoMutexAcquire reader(&fMutex); + return fFailures.count(); +} + +void Reporter::fail(SkString name) { + SkAutoMutexAcquire writer(&fMutex); + fFailures.push_back(name); +} + +void Reporter::getFailures(SkTArray* failures) const { + SkAutoMutexAcquire reader(&fMutex); + *failures = fFailures; +} + +} // namespace DM diff --git a/dm/DMReporter.h b/dm/DMReporter.h new file mode 100644 index 0000000..4f4ad43 --- /dev/null +++ b/dm/DMReporter.h @@ -0,0 +1,39 @@ +#ifndef DMReporter_DEFINED +#define DMReporter_DEFINED + +#include "SkString.h" +#include "SkTArray.h" +#include "SkThread.h" +#include "SkTypes.h" + +// Used to report status changes including failures. All public methods are threadsafe. + +namespace DM { + +class Reporter : SkNoncopyable { +public: + Reporter() : fStarted(0), fFinished(0) {} + + void start() { sk_atomic_inc(&fStarted); } + void finish() { sk_atomic_inc(&fFinished); } + void fail(SkString name); + + int32_t started() const { return fStarted; } + int32_t finished() const { return fFinished; } + int32_t failed() const; + + void updateStatusLine() const; + + void getFailures(SkTArray*) const; + +private: + int32_t fStarted, fFinished; + + mutable SkMutex fMutex; // Guards fFailures. + SkTArray fFailures; +}; + + +} // namespace DM + +#endif // DMReporter_DEFINED diff --git a/dm/DMTask.cpp b/dm/DMTask.cpp new file mode 100644 index 0000000..9b463f9 --- /dev/null +++ b/dm/DMTask.cpp @@ -0,0 +1,42 @@ +#include "DMTask.h" + +#include "DMTaskRunner.h" +#include "DMUtil.h" +#include "SkBitmap.h" +#include "SkCommandLineFlags.h" + +namespace DM { + +Task::Task(Reporter* reporter, TaskRunner* taskRunner) + : fReporter(reporter), fTaskRunner(taskRunner) { + fReporter->start(); +} + +Task::Task(const Task& that) : fReporter(that.fReporter), fTaskRunner(that.fTaskRunner) { + fReporter->start(); +} + +Task::~Task() {} + +void Task::run() { + if (!this->shouldSkip()) { + this->draw(); + } + fReporter->finish(); + fReporter->updateStatusLine(); + delete this; +} + +void Task::spawnChild(Task* task) { + if (!task->usesGpu()) { + fTaskRunner->add(task); + } else { + SkDEBUGFAIL("Sorry, we can't spawn GPU tasks. :( See comment in TaskRunner::wait()."); + } +} + +void Task::fail() { + fReporter->fail(this->name()); +} + +} // namespace DM diff --git a/dm/DMTask.h b/dm/DMTask.h new file mode 100644 index 0000000..744fd6b --- /dev/null +++ b/dm/DMTask.h @@ -0,0 +1,43 @@ +#ifndef DMTask_DEFINED +#define DMTask_DEFINED + +#include "DMReporter.h" +#include "SkRunnable.h" +#include "SkThreadPool.h" + +// DM will run() these tasks on one of two threadpools, depending on the result +// of usesGpu(). The subclasses can call fail() to mark this task as failed, +// or make any number of spawnChild() calls to kick off dependent tasks. +// +// Task deletes itself when run. + +namespace DM { + +class TaskRunner; + +class Task : public SkRunnable { +public: + Task(Reporter* reporter, TaskRunner* taskRunner); + Task(const Task& that); + virtual ~Task(); + + void run(); + + virtual void draw() = 0; + virtual bool usesGpu() const = 0; + virtual bool shouldSkip() const = 0; + virtual SkString name() const = 0; + +protected: + void spawnChild(Task* task); + void fail(); + +private: + // Both unowned. + Reporter* fReporter; + TaskRunner* fTaskRunner; +}; + +} // namespace DM + +#endif // DMTask_DEFINED diff --git a/dm/DMTaskRunner.cpp b/dm/DMTaskRunner.cpp new file mode 100644 index 0000000..22269a4 --- /dev/null +++ b/dm/DMTaskRunner.cpp @@ -0,0 +1,28 @@ +#include "DMTaskRunner.h" +#include "DMTask.h" + +namespace DM { + +TaskRunner::TaskRunner(int cputhreads, int gpuThreads) + : fMain(cputhreads) + , fGpu(gpuThreads) + {} + +void TaskRunner::add(Task* task) { + if (task->usesGpu()) { + fGpu.add(task); + } else { + fMain.add(task); + } +} + +void TaskRunner::wait() { + // These wait calls block until the threadpool is done. We don't allow + // children to spawn new GPU tasks so we can wait for that first knowing + // we'll never try to add to it later. Same can't be said of fMain: fGpu + // and fMain can both add tasks to fMain, so we have to wait for that last. + fGpu.wait(); + fMain.wait(); +} + +} // namespace DM diff --git a/dm/DMTaskRunner.h b/dm/DMTaskRunner.h new file mode 100644 index 0000000..5d7b320 --- /dev/null +++ b/dm/DMTaskRunner.h @@ -0,0 +1,28 @@ +#ifndef DMTaskRunner_DEFINED +#define DMTaskRunner_DEFINED + +#include "SkThreadPool.h" +#include "SkTypes.h" + +// TaskRunner runs Tasks on one of two threadpools depending on the Task's usesGpu() method. +// This lets us drive the GPU with a small number of threads (e.g. 2 or 4 can be faster than 1) +// while not swamping it with requests from the full fleet of threads that CPU-bound tasks run on. + +namespace DM { + +class Task; + +class TaskRunner : SkNoncopyable { +public: + TaskRunner(int cputhreads, int gpuThreads); + + void add(Task* task); + void wait(); + +private: + SkThreadPool fMain, fGpu; +}; + +} // namespace DM + +#endif // DMTaskRunner_DEFINED diff --git a/dm/DMUtil.cpp b/dm/DMUtil.cpp new file mode 100644 index 0000000..803c338 --- /dev/null +++ b/dm/DMUtil.cpp @@ -0,0 +1,23 @@ +#include "DMUtil.h" + +namespace DM { + +SkString underJoin(const char* a, const char* b) { + SkString s; + s.appendf("%s_%s", a, b); + return s; +} + +SkString png(SkString s) { + s.appendf(".png"); + return s; +} + +bool meetsExpectations(const skiagm::Expectations& expectations, + const skiagm::GmResultDigest& digest) { + return expectations.ignoreFailure() + || expectations.empty() + || expectations.match(digest); +} + +} // namespace DM diff --git a/dm/DMUtil.h b/dm/DMUtil.h new file mode 100644 index 0000000..e808ca0 --- /dev/null +++ b/dm/DMUtil.h @@ -0,0 +1,23 @@ +#ifndef DMUtil_DEFINED +#define DMUtil_DEFINED + +#include "SkString.h" +#include "gm_expectations.h" + +// 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); + +// png("a") -> "a.png" +SkString png(SkString s); + +// Roughly, expectations.match(digest), but only does it if we're not ignoring the result. +bool meetsExpectations(const skiagm::Expectations& expectations, + const skiagm::GmResultDigest& digest); + +} // namespace DM + +#endif // DMUtil_DEFINED diff --git a/dm/README b/dm/README new file mode 100644 index 0000000..bce9a7e --- /dev/null +++ b/dm/README @@ -0,0 +1,37 @@ +DM is like GM, but multithreaded. It doesn't do everything GM does yet. + +Current approximate list of missing features: + --mismatchPath + --missingExpectationsPath + --writePath + --writePicturePath + + --deferred / --pipe + --rtree + --serialize + --tiledGrid + + +DM's design is based around Tasks and a TaskRunner. + +A Task represents an independent unit of work that might fail. We make a task +for each GM/configuration pair we want to run. Tasks can kick off new tasks +themselves. For example, a CpuTask can kick off a ReplayTask to make sure +recording and playing back an SkPicture gives the same result as direct +rendering. + +The TaskRunner runs all tasks on one of two threadpools, whose sizes are +configurable by --cpuThreads and --gpuThreads. Ideally we'd run these on a +single threadpool but it can swamp the GPU if we shove too much work into it at +once. --cpuThreads defaults to the number of cores on the machine. +--gpuThreads defaults to 1, but you may find 2 or 4 runs a little faster. + +So the main flow of DM is: + + for each GM: + for each configuration: + kick off a new task + < tasks run, maybe fail, and maybe kick off new tasks > + wait for all tasks to finish + report failures + diff --git a/gyp/dm.gyp b/gyp/dm.gyp new file mode 100644 index 0000000..315f03b --- /dev/null +++ b/gyp/dm.gyp @@ -0,0 +1,43 @@ +# GYP for "dm" (Diamond Master, a.k.a Dungeon master, a.k.a GM 2). +# vim: set expandtab tabstop=4 shiftwidth=4 +{ + 'includes': [ 'apptype_console.gypi' ], + + 'targets': [{ + 'target_name': 'dm', + 'type': 'executable', + 'include_dirs': [ + '../dm', + '../gm', + '../src/core', + '../src/effects', + '../src/utils', + '../src/utils/debugger', + ], + 'includes': [ 'gmslides.gypi' ], + 'sources': [ + '../dm/DM.cpp', + '../dm/DMComparisonTask.cpp', + '../dm/DMCpuTask.cpp', + '../dm/DMGpuTask.cpp', + '../dm/DMReplayTask.cpp', + '../dm/DMReporter.cpp', + '../dm/DMTask.cpp', + '../dm/DMTaskRunner.cpp', + '../dm/DMUtil.cpp', + '../gm/gm.cpp', + '../gm/gm_expectations.cpp', + + # TODO: split these out as a library in src/utils/debugger. + '../src/utils/debugger/SkDebugCanvas.cpp', + '../src/utils/debugger/SkDrawCommand.cpp', + '../src/utils/debugger/SkObjectParser.cpp', + ], + 'dependencies': [ + 'skia_lib.gyp:skia_lib', + 'flags.gyp:flags', + 'jsoncpp.gyp:jsoncpp', + 'gputest.gyp:skgputest', + ], + }] +} diff --git a/gyp/everything.gyp b/gyp/everything.gyp index dc5d91c..09c9c3a 100644 --- a/gyp/everything.gyp +++ b/gyp/everything.gyp @@ -12,7 +12,10 @@ { 'target_name': 'everything', 'type': 'none', - 'dependencies': ['most.gyp:most'], + 'dependencies': [ + 'most.gyp:most', + 'dm.gyp:dm', + ], 'conditions': [ ['skia_os in ("ios", "android", "chromeos") or (skia_os == "mac" and skia_arch_width == 32)', { # debugger is not supported on this platform -- 2.7.4