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
This commit is contained in:
parent
beede90eae
commit
d36522d12d
166
dm/DM.cpp
Normal file
166
dm/DM.cpp
Normal file
@ -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 <string.h>
|
||||
|
||||
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<SkString>* 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<GMRegistry::Factory>& gms,
|
||||
const SkTArray<SkString>& 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<GM> 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<SkString> 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<SkString> configs;
|
||||
for (int i = 0; i < FLAGS_config.count(); i++) {
|
||||
split(FLAGS_config[i], ", ", &configs);
|
||||
}
|
||||
|
||||
SkTDArray<GMRegistry::Factory> 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<ExpectationsSource> 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;
|
||||
}
|
22
dm/DMComparisonTask.cpp
Normal file
22
dm/DMComparisonTask.cpp
Normal file
@ -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
|
31
dm/DMComparisonTask.h
Normal file
31
dm/DMComparisonTask.h
Normal file
@ -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
|
57
dm/DMCpuTask.cpp
Normal file
57
dm/DMCpuTask.cpp
Normal file
@ -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
|
44
dm/DMCpuTask.h
Normal file
44
dm/DMCpuTask.h
Normal file
@ -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<skiagm::GM> fGM;
|
||||
const SkString fName;
|
||||
const skiagm::Expectations fExpectations;
|
||||
const SkBitmap::Config fConfig;
|
||||
};
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMCpuTask_DEFINED
|
63
dm/DMGpuTask.cpp
Normal file
63
dm/DMGpuTask.cpp
Normal file
@ -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<GrContextFactory*>(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
|
45
dm/DMGpuTask.h
Normal file
45
dm/DMGpuTask.h
Normal file
@ -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<skiagm::GM> fGM;
|
||||
const SkString fName;
|
||||
const skiagm::Expectations fExpectations;
|
||||
const SkBitmap::Config fConfig;
|
||||
const GrContextFactory::GLContextType fContextType;
|
||||
const int fSampleCount;
|
||||
};
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMGpuTask_DEFINED
|
50
dm/DMReplayTask.cpp
Normal file
50
dm/DMReplayTask.cpp
Normal file
@ -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
|
40
dm/DMReplayTask.h
Normal file
40
dm/DMReplayTask.h
Normal file
@ -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<skiagm::GM> fGM;
|
||||
const skiagm::GmResultDigest fReference;
|
||||
const SkBitmap::Config fConfig;
|
||||
};
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMReplayTask_DEFINED
|
24
dm/DMReporter.cpp
Normal file
24
dm/DMReporter.cpp
Normal file
@ -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<SkString>* failures) const {
|
||||
SkAutoMutexAcquire reader(&fMutex);
|
||||
*failures = fFailures;
|
||||
}
|
||||
|
||||
} // namespace DM
|
39
dm/DMReporter.h
Normal file
39
dm/DMReporter.h
Normal file
@ -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<SkString>*) const;
|
||||
|
||||
private:
|
||||
int32_t fStarted, fFinished;
|
||||
|
||||
mutable SkMutex fMutex; // Guards fFailures.
|
||||
SkTArray<SkString> fFailures;
|
||||
};
|
||||
|
||||
|
||||
} // namespace DM
|
||||
|
||||
#endif // DMReporter_DEFINED
|
42
dm/DMTask.cpp
Normal file
42
dm/DMTask.cpp
Normal file
@ -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
|
43
dm/DMTask.h
Normal file
43
dm/DMTask.h
Normal file
@ -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
|
28
dm/DMTaskRunner.cpp
Normal file
28
dm/DMTaskRunner.cpp
Normal file
@ -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
|
28
dm/DMTaskRunner.h
Normal file
28
dm/DMTaskRunner.h
Normal file
@ -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
|
23
dm/DMUtil.cpp
Normal file
23
dm/DMUtil.cpp
Normal file
@ -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
|
23
dm/DMUtil.h
Normal file
23
dm/DMUtil.h
Normal file
@ -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
|
37
dm/README
Normal file
37
dm/README
Normal file
@ -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
|
||||
|
43
gyp/dm.gyp
Normal file
43
gyp/dm.gyp
Normal file
@ -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',
|
||||
],
|
||||
}]
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user