Sketch DM refactor.

BUG=skia:3255

I think this supports everything DM used to, but has completely refactored how
it works to fit the design in the bug.

Configs like "tiles-gpu" are automatically wired up.

I wouldn't suggest looking at this as a diff.  There's just a bunch of deleted
files, a few new files, and one new file that shares a name with a deleted file
(DM.cpp).

NOTREECHECKS=true

Committed: https://skia.googlesource.com/skia/+/709d2c3e5062c5b57f91273bfc11a751f5b2bb88

Review URL: https://codereview.chromium.org/788243008
This commit is contained in:
mtklein 2015-01-15 10:56:12 -08:00 committed by Commit bot
parent 0063a9b69a
commit 748ca3bf2d
41 changed files with 975 additions and 2048 deletions

View File

@ -77,14 +77,7 @@ DEFINE_int32(flushEvery, 10, "Flush --outResultsFile every Nth run.");
static SkString humanize(double ms) { static SkString humanize(double ms) {
if (FLAGS_verbose) return SkStringPrintf("%llu", (uint64_t)(ms*1e6)); if (FLAGS_verbose) return SkStringPrintf("%llu", (uint64_t)(ms*1e6));
if (ms > 1e+3) return SkStringPrintf("%.3gs", ms/1e3); return HumanizeMs(ms);
if (ms < 1e-3) return SkStringPrintf("%.3gns", ms*1e6);
#ifdef SK_BUILD_FOR_WIN
if (ms < 1) return SkStringPrintf("%.3gus", ms*1e3);
#else
if (ms < 1) return SkStringPrintf("%.3gµs", ms*1e3);
#endif
return SkStringPrintf("%.3gms", ms);
} }
#define HUMANIZE(ms) humanize(ms).c_str() #define HUMANIZE(ms) humanize(ms).c_str()

618
dm/DM.cpp
View File

@ -1,293 +1,443 @@
// Main binary for DM.
// For a high-level overview, please see dm/README.
#include "CrashHandler.h" #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 "SkCommonFlags.h"
#include "SkForceLinking.h" #include "SkForceLinking.h"
#include "SkGraphics.h" #include "SkGraphics.h"
#include "SkMD5.h"
#include "SkOSFile.h" #include "SkOSFile.h"
#include "SkPicture.h"
#include "SkString.h"
#include "SkTaskGroup.h" #include "SkTaskGroup.h"
#include "Test.h" #include "Test.h"
#include "gm.h" #include "Timer.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 <ctype.h>
using skiagm::GM;
using skiagm::GMRegistry;
using skiatest::Test;
using skiatest::TestRegistry;
static const char kGpuAPINameGL[] = "gl";
static const char kGpuAPINameGLES[] = "gles";
DEFINE_bool(gms, true, "Run GMs?");
DEFINE_bool(tests, true, "Run tests?"); DEFINE_bool(tests, true, "Run tests?");
DEFINE_bool(reportUsedChars, false, "Output test font construction data to be pasted into" DEFINE_string(images, "resources", "Images to decode.");
" create_test_font.cpp."); //DEFINE_string(src, "gm skp image subset", "Source types to test.");
DEFINE_string(images, "resources", "Path to directory containing images to decode."); DEFINE_string(src, "gm skp", "Source types to test. TEMPORARILY DISABLED");
DEFINE_bool(rasterPDF, true, "Rasterize PDFs?"); DEFINE_bool(nameByHash, false,
"If true, write to FLAGS_writePath[0]/<hash>.png instead of "
"to FLAGS_writePath[0]/<config>/<sourceType>/<name>.png");
DEFINE_bool2(pathOpsExtended, x, false, "Run extended pathOps tests.");
DEFINE_string(matrix, "1 0 0 0 1 0 0 0 1",
"Matrix to apply when using 'matrix' in config.");
__SK_FORCE_IMAGE_DECODER_LINKING; __SK_FORCE_IMAGE_DECODER_LINKING;
using namespace DM;
static DM::RasterizePdfProc get_pdf_rasterizer_proc() { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
return reinterpret_cast<DM::RasterizePdfProc>(
FLAGS_rasterPDF ? RASTERIZE_PDF_PROC : NULL); SK_DECLARE_STATIC_MUTEX(gFailuresMutex);
static SkTArray<SkString> gFailures;
static void fail(ImplicitString err) {
SkAutoMutexAcquire lock(gFailuresMutex);
SkDebugf("\n\nFAILURE: %s\n\n", err.c_str());
gFailures.push_back(err);
} }
// "FooBar" -> "foobar". Obviously, ASCII only. static int32_t gPending = 0; // Atomic.
static SkString lowercase(SkString s) {
for (size_t i = 0; i < s.size(); i++) { static void done(double ms, ImplicitString config, ImplicitString src, ImplicitString name) {
s[i] = tolower(s[i]); SkDebugf("%s(%4dMB %5d) %s\t%s %s %s ", FLAGS_verbose ? "\n" : kSkOverwriteLine
} , sk_tools::getMaxResidentSetSizeMB()
return s; , sk_atomic_dec(&gPending)-1
, HumanizeMs(ms).c_str()
, config.c_str()
, src.c_str()
, name.c_str());
} }
static const GrContextFactory::GLContextType native = GrContextFactory::kNative_GLContextType; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
static const GrContextFactory::GLContextType nvpr = GrContextFactory::kNVPR_GLContextType;
static const GrContextFactory::GLContextType null = GrContextFactory::kNull_GLContextType;
static const GrContextFactory::GLContextType debug = GrContextFactory::kDebug_GLContextType;
#if SK_ANGLE
static const GrContextFactory::GLContextType angle = GrContextFactory::kANGLE_GLContextType;
#endif
#if SK_MESA
static const GrContextFactory::GLContextType mesa = GrContextFactory::kMESA_GLContextType;
#endif
static void kick_off_gms(const SkTDArray<GMRegistry::Factory>& gms, template <typename T>
const SkTArray<SkString>& configs, struct Tagged : public SkAutoTDelete<T> { const char* tag; };
GrGLStandard gpuAPI,
DM::Reporter* reporter, static const bool kMemcpyOK = true;
DM::TaskRunner* tasks) {
#define START(name, type, ...) \ static SkTArray<Tagged<Src>, kMemcpyOK> gSrcs;
if (lowercase(configs[j]).equals(name)) { \ static SkTArray<Tagged<Sink>, kMemcpyOK> gSinks;
tasks->add(SkNEW_ARGS(DM::type, (name, reporter, tasks, gms[i], ## __VA_ARGS__))); \
static void push_src(const char* tag, Src* s) {
SkAutoTDelete<Src> src(s);
if (FLAGS_src.contains(tag) &&
!SkCommandLineFlags::ShouldSkip(FLAGS_match, src->name().c_str())) {
Tagged<Src>& s = gSrcs.push_back();
s.reset(src.detach());
s.tag = tag;
} }
for (int i = 0; i < gms.count(); i++) { }
for (int j = 0; j < configs.count(); j++) {
START("565", CpuGMTask, kRGB_565_SkColorType); static void gather_srcs() {
START("8888", CpuGMTask, kN32_SkColorType); for (const skiagm::GMRegistry* r = skiagm::GMRegistry::Head(); r; r = r->next()) {
START("gpu", GpuGMTask, native, gpuAPI, 0, false); push_src("gm", new GMSrc(r->factory()));
START("msaa4", GpuGMTask, native, gpuAPI, 4, false); }
START("msaa16", GpuGMTask, native, gpuAPI, 16, false); if (!FLAGS_skps.isEmpty()) {
START("nvprmsaa4", GpuGMTask, nvpr, gpuAPI, 4, false); SkOSFile::Iter it(FLAGS_skps[0], "skp");
START("nvprmsaa16", GpuGMTask, nvpr, gpuAPI, 16, false); for (SkString file; it.next(&file); ) {
START("gpudft", GpuGMTask, native, gpuAPI, 0, true); push_src("skp", new SKPSrc(SkOSPath::Join(FLAGS_skps[0], file.c_str())));
START("gpunull", GpuGMTask, null, gpuAPI, 0, false);
START("gpudebug", GpuGMTask, debug, gpuAPI, 0, false);
#if SK_ANGLE
START("angle", GpuGMTask, angle, gpuAPI, 0, false);
#endif
#if SK_MESA
START("mesa", GpuGMTask, mesa, gpuAPI, 0, false);
#endif
START("pdf", PDFTask, get_pdf_rasterizer_proc());
} }
} }
#undef START if (!FLAGS_images.isEmpty()) {
} const char* exts[] = {
"bmp", "gif", "jpg", "jpeg", "png", "webp", "ktx", "astc", "wbmp", "ico",
static void kick_off_tests(const SkTDArray<TestRegistry::Factory>& tests, "BMP", "GIF", "JPG", "JPEG", "PNG", "WEBP", "KTX", "ASTC", "WBMP", "ICO",
DM::Reporter* reporter, };
DM::TaskRunner* tasks) { for (size_t i = 0; i < SK_ARRAY_COUNT(exts); i++) {
for (int i = 0; i < tests.count(); i++) { SkOSFile::Iter it(FLAGS_images[0], exts[i]);
SkAutoTDelete<Test> test(tests[i](NULL)); for (SkString file; it.next(&file); ) {
if (test->isGPUTest()) { SkString path = SkOSPath::Join(FLAGS_images[0], file.c_str());
tasks->add(SkNEW_ARGS(DM::GpuTestTask, (reporter, tasks, tests[i]))); push_src("image", new ImageSrc(path)); // Decode entire image.
} else { push_src("subset", new ImageSrc(path, 5)); // Decode 5 random subsets.
tasks->add(SkNEW_ARGS(DM::CpuTestTask, (reporter, tasks, tests[i])));
}
}
}
static void find_files(const char* dir,
const char* suffixes[],
size_t num_suffixes,
SkTArray<SkString>* files) {
if (0 == strcmp(dir, "")) {
return;
}
SkString filename;
for (size_t i = 0; i < num_suffixes; i++) {
SkOSFile::Iter it(dir, suffixes[i]);
while (it.next(&filename)) {
if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, filename.c_str())) {
files->push_back(SkOSPath::Join(dir, filename.c_str()));
} }
} }
} }
} }
static void kick_off_skps(const SkTArray<SkString>& skps, static GrGLStandard get_gpu_api() {
DM::Reporter* reporter, if (FLAGS_gpuAPI.contains("gl")) { return kGL_GrGLStandard; }
DM::TaskRunner* tasks) { if (FLAGS_gpuAPI.contains("gles")) { return kGLES_GrGLStandard; }
for (int i = 0; i < skps.count(); ++i) { return kNone_GrGLStandard;
SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(skps[i].c_str()));
if (stream.get() == NULL) {
SkDebugf("Could not read %s.\n", skps[i].c_str());
exit(1);
}
SkAutoTUnref<SkPicture> pic(
SkPicture::CreateFromStream(stream.get(), &sk_tools::LazyDecodeBitmap));
if (pic.get() == NULL) {
SkDebugf("Could not read %s as an SkPicture.\n", skps[i].c_str());
exit(1);
}
SkString filename = SkOSPath::Basename(skps[i].c_str());
tasks->add(SkNEW_ARGS(DM::SKPTask, (reporter, tasks, pic, filename)));
tasks->add(SkNEW_ARGS(DM::PDFTask, (reporter, tasks, pic, filename,
get_pdf_rasterizer_proc())));
}
} }
static void kick_off_images(const SkTArray<SkString>& images, static void push_sink(const char* tag, Sink* s) {
DM::Reporter* reporter, SkAutoTDelete<Sink> sink(s);
DM::TaskRunner* tasks) { if (!FLAGS_config.contains(tag)) {
for (int i = 0; i < images.count(); i++) { return;
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*/)));
} }
} // 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;
static void report_failures(const SkTArray<SkString>& failures) { SkDynamicMemoryWStream stream;
if (failures.count() == 0) { Error err = sink->draw(noop, &bitmap, &stream);
if (!err.isEmpty()) {
SkDebugf("Skipping %s: %s\n", tag, err.c_str());
return; return;
} }
SkDebugf("Failures:\n"); Tagged<Sink>& ts = gSinks.push_back();
for (int i = 0; i < failures.count(); i++) { ts.reset(sink.detach());
SkDebugf(" %s\n", failures[i].c_str()); ts.tag = tag;
}
static bool gpu_supported() {
#if SK_SUPPORT_GPU
return FLAGS_gpu;
#else
return false;
#endif
}
static Sink* create_sink(const char* tag) {
#define SINK(t, sink, ...) if (0 == strcmp(t, tag)) { return new sink(__VA_ARGS__); }
if (gpu_supported()) {
const GrGLStandard api = get_gpu_api();
SINK("gpunull", GPUSink, GrContextFactory::kNull_GLContextType, api, 0, false);
SINK("gpudebug", GPUSink, GrContextFactory::kDebug_GLContextType, api, 0, false);
SINK("gpu", GPUSink, GrContextFactory::kNative_GLContextType, api, 0, false);
SINK("gpudft", GPUSink, GrContextFactory::kNative_GLContextType, api, 0, true);
SINK("msaa4", GPUSink, GrContextFactory::kNative_GLContextType, api, 4, false);
SINK("msaa16", GPUSink, GrContextFactory::kNative_GLContextType, api, 16, false);
SINK("nvprmsaa4", GPUSink, GrContextFactory::kNVPR_GLContextType, api, 4, false);
SINK("nvprmsaa16", GPUSink, GrContextFactory::kNVPR_GLContextType, api, 16, false);
#if SK_ANGLE
SINK("angle", GPUSink, GrContextFactory::kANGLE_GLContextType, api, 0, false);
#endif
#if SK_MESA
SINK("mesa", GPUSink, GrContextFactory::kMESA_GLContextType, api, 0, false);
#endif
} }
SkDebugf("%d failures.\n", failures.count());
if (FLAGS_cpu) {
SINK("565", RasterSink, kRGB_565_SkColorType);
SINK("8888", RasterSink, kN32_SkColorType);
// TODO(mtklein): reenable once skiagold can handle .pdf uploads.
//SINK("pdf", PDFSink);
}
#undef SINK
return NULL;
} }
static GrGLStandard get_gl_standard() { static Sink* create_via(const char* tag, Sink* wrapped) {
if (FLAGS_gpuAPI.contains(kGpuAPINameGL)) { #define VIA(t, via, ...) if (0 == strcmp(t, tag)) { return new via(__VA_ARGS__); }
return kGL_GrGLStandard; VIA("serialize", ViaSerialization, wrapped);
}
if (FLAGS_gpuAPI.contains(kGpuAPINameGLES)) { VIA("tiles", ViaTiles, 256, 256, NULL, wrapped);
return kGLES_GrGLStandard; VIA("tiles_rt", ViaTiles, 256, 256, new SkRTreeFactory, wrapped);
}
return kNone_GrGLStandard; const int xp = SkGPipeWriter::kCrossProcess_Flag,
sa = xp | SkGPipeWriter::kSharedAddressSpace_Flag;
VIA("pipe", ViaPipe, 0, wrapped);
VIA("pipe_xp", ViaPipe, xp, wrapped);
VIA("pipe_sa", ViaPipe, sa, wrapped);
if (FLAGS_matrix.count() == 9) {
SkMatrix m;
for (int i = 0; i < 9; i++) {
m[i] = (SkScalar)atof(FLAGS_matrix[i]);
}
VIA("matrix", ViaMatrix, m, wrapped);
}
#undef VIA
return NULL;
} }
template <typename T, typename Registry> static void gather_sinks() {
static void append_matching_factories(Registry* head, SkTDArray<typename Registry::Factory>* out) { for (int i = 0; i < FLAGS_config.count(); i++) {
for (const Registry* reg = head; reg != NULL; reg = reg->next()) { const char* config = FLAGS_config[i];
SkAutoTDelete<T> forName(reg->factory()(NULL)); SkTArray<SkString> parts;
if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, forName->getName())) { SkStrSplit(config, "-", &parts);
*out->append() = reg->factory();
Sink* sink = NULL;
for (int i = parts.count(); i-- > 0;) {
const char* part = parts[i].c_str();
Sink* next = (sink == NULL) ? create_sink(part) : create_via(part, sink);
if (next == NULL) {
SkDebugf("Skipping %s: Don't understand '%s'.\n", config, part);
delete sink;
sink = NULL;
break;
}
sink = next;
}
if (sink) {
push_sink(config, sink);
} }
} }
} }
// The finest-grained unit of work we can run: draw a single Src into a single Sink,
// report any errors, and perhaps write out the output: a .png of the bitmap, or a raw stream.
struct Task {
Task(const Tagged<Src>& src, const Tagged<Sink>& sink) : src(src), sink(sink) {}
const Tagged<Src>& src;
const Tagged<Sink>& sink;
static void Run(Task* task) {
WallTimer timer;
timer.start();
if (!FLAGS_dryRun) {
SkBitmap bitmap;
SkDynamicMemoryWStream stream;
Error err = task->sink->draw(*task->src, &bitmap, &stream);
if (!err.isEmpty()) {
fail(SkStringPrintf("%s %s %s: %s",
task->sink.tag,
task->src.tag,
task->src->name().c_str(),
err.c_str()));
}
if (!FLAGS_writePath.isEmpty()) {
const char* ext = task->sink->fileExtension();
if (stream.bytesWritten() == 0) {
SkMemoryStream pixels(bitmap.getPixels(), bitmap.getSize());
WriteToDisk(*task, &pixels, bitmap.getSize(), &bitmap, ext);
} else {
SkAutoTDelete<SkStreamAsset> data(stream.detachAsStream());
WriteToDisk(*task, data, data->getLength(), NULL, ext);
}
}
}
timer.end();
done(timer.fWall, task->sink.tag, task->src.tag, task->src->name());
}
static void WriteToDisk(const Task& task,
SkStream* data, size_t len,
const SkBitmap* bitmap,
const char* ext) {
SkMD5 hash;
hash.writeStream(data, len);
SkMD5::Digest digest;
hash.finish(digest);
JsonWriter::BitmapResult result;
result.name = task.src->name();
result.config = task.sink.tag;
result.sourceType = task.src.tag;
result.ext = ext;
for (int i = 0; i < 16; i++) {
result.md5.appendf("%02x", digest.data[i]);
}
JsonWriter::AddBitmapResult(result);
const char* dir = FLAGS_writePath[0];
if (0 == strcmp(dir, "@")) { // Needed for iOS.
dir = FLAGS_resourcePath[0];
}
sk_mkdir(dir);
SkString path;
if (FLAGS_nameByHash) {
path = SkOSPath::Join(dir, result.md5.c_str());
path.append(".");
path.append(ext);
if (sk_exists(path.c_str())) {
return; // Content-addressed. If it exists already, we're done.
}
} else {
path = SkOSPath::Join(dir, task.sink.tag);
sk_mkdir(path.c_str());
path = SkOSPath::Join(path.c_str(), task.src.tag);
sk_mkdir(path.c_str());
path = SkOSPath::Join(path.c_str(), task.src->name().c_str());
path.append(".");
path.append(ext);
}
SkFILEWStream file(path.c_str());
if (!file.isValid()) {
fail(SkStringPrintf("Can't open %s for writing.\n", path.c_str()));
return;
}
data->rewind();
if (bitmap) {
// We can't encode A8 bitmaps as PNGs. Convert them to 8888 first.
SkBitmap converted;
if (bitmap->info().colorType() == kAlpha_8_SkColorType) {
if (!bitmap->copyTo(&converted, kN32_SkColorType)) {
fail("Can't convert A8 to 8888.\n");
return;
}
bitmap = &converted;
}
if (!SkImageEncoder::EncodeStream(&file, *bitmap, SkImageEncoder::kPNG_Type, 100)) {
fail(SkStringPrintf("Can't encode PNG to %s.\n", path.c_str()));
return;
}
} else {
if (!file.writeStream(data, len)) {
fail(SkStringPrintf("Can't write to %s.\n", path.c_str()));
return;
}
}
}
};
// Run all tasks in the same enclave serially on the same thread.
// They can't possibly run concurrently with each other.
static void run_enclave(SkTArray<Task>* tasks) {
for (int i = 0; i < tasks->count(); i++) {
Task::Run(tasks->begin() + i);
}
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
// Unit tests don't fit so well into the Src/Sink model, so we give them special treatment.
static struct : public skiatest::Reporter {
void onReportFailed(const skiatest::Failure& failure) SK_OVERRIDE {
SkString s;
failure.getFailureString(&s);
fail(s);
JsonWriter::AddTestFailure(failure);
}
bool allowExtendedTest() const SK_OVERRIDE { return FLAGS_pathOpsExtended; }
bool verbose() const SK_OVERRIDE { return FLAGS_veryVerbose; }
} gTestReporter;
static SkTArray<SkAutoTDelete<skiatest::Test>, kMemcpyOK> gTests;
static void gather_tests() {
if (!FLAGS_tests) {
return;
}
for (const skiatest::TestRegistry* r = skiatest::TestRegistry::Head(); r; r = r->next()) {
SkAutoTDelete<skiatest::Test> test(r->factory()(NULL));
if (SkCommandLineFlags::ShouldSkip(FLAGS_match, test->getName())) {
continue;
}
if (test->isGPUTest() /*&& !gpu_supported()*/) { // TEMPORARILY DISABLED
continue;
}
if (!test->isGPUTest() && !FLAGS_cpu) {
continue;
}
test->setReporter(&gTestReporter);
gTests.push_back().reset(test.detach());
}
}
static void run_test(SkAutoTDelete<skiatest::Test>* t) {
WallTimer timer;
timer.start();
skiatest::Test* test = t->get();
if (!FLAGS_dryRun) {
GrContextFactory grFactory;
test->setGrContextFactory(&grFactory);
test->run();
if (!test->passed()) {
fail(SkStringPrintf("test %s failed", test->getName()));
}
}
timer.end();
done(timer.fWall, "unit", "test", test->getName());
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
int dm_main(); int dm_main();
int dm_main() { int dm_main() {
SetupCrashHandler(); SetupCrashHandler();
SkAutoGraphics ag; SkAutoGraphics ag;
SkTaskGroup::Enabler enabled(FLAGS_threads); SkTaskGroup::Enabler enabled(FLAGS_threads);
if (FLAGS_dryRun || FLAGS_veryVerbose) { gather_srcs();
FLAGS_verbose = true; gather_sinks();
} gather_tests();
#if SK_ENABLE_INST_COUNT
gPrintInstCount = FLAGS_leaks;
#endif
SkTArray<SkString> configs; gPending = gSrcs.count() * gSinks.count() + gTests.count();
for (int i = 0; i < FLAGS_config.count(); i++) { SkDebugf("%d srcs * %d sinks + %d tests == %d tasks\n",
SkStrSplit(FLAGS_config[i], ", ", &configs); gSrcs.count(), gSinks.count(), gTests.count(), gPending);
// We try to exploit as much parallelism as is safe. Most Src/Sink pairs run on any thread,
// but Sinks that identify as part of a particular enclave run serially on a single thread.
// Tests run on any thread, with a separate GrContextFactory for each GPU test.
SkTArray<Task> enclaves[kNumEnclaves];
for (int j = 0; j < gSinks.count(); j++) {
SkTArray<Task>& tasks = enclaves[gSinks[j]->enclave()];
for (int i = 0; i < gSrcs.count(); i++) {
tasks.push_back(Task(gSrcs[i], gSinks[j]));
}
} }
GrGLStandard gpuAPI = get_gl_standard(); SK_COMPILE_ASSERT(kAnyThread_Enclave == 0, AnyThreadZero);
SkTaskGroup tg;
tg.batch( Task::Run, enclaves[0].begin(), enclaves[0].count());
tg.batch(run_enclave, enclaves+1, kNumEnclaves-1);
tg.batch( run_test, gTests.begin(), gTests.count());
tg.wait();
SkTDArray<GMRegistry::Factory> gms; // At this point we're back in single-threaded land.
if (FLAGS_gms) {
append_matching_factories<GM>(GMRegistry::Head(), &gms); if (!FLAGS_verbose) {
SkDebugf("\n");
} }
SkTDArray<TestRegistry::Factory> tests; JsonWriter::DumpJson();
if (FLAGS_tests) {
append_matching_factories<Test>(TestRegistry::Head(), &tests); if (gFailures.count() > 0) {
SkDebugf("Failures:\n");
for (int i = 0; i < gFailures.count(); i++) {
SkDebugf("\t%s", gFailures[i].c_str());
}
SkDebugf("%d failures\n", gFailures.count());
return 1;
} }
if (gPending > 0) {
SkDebugf("Hrm, we didn't seem to run everything we intended to! Please file a bug.\n");
SkTArray<SkString> skps; return 1;
if (!FLAGS_skps.isEmpty()) {
const char* suffixes[] = { "skp" };
find_files(FLAGS_skps[0], suffixes, SK_ARRAY_COUNT(suffixes), &skps);
} }
return 0;
SkTArray<SkString> images;
if (!FLAGS_images.isEmpty()) {
const char* suffixes[] = {
"bmp", "gif", "jpg", "jpeg", "png", "webp", "ktx", "astc", "wbmp", "ico",
"BMP", "GIF", "JPG", "JPEG", "PNG", "WEBP", "KTX", "ASTC", "WBMP", "ICO",
};
find_files(FLAGS_images[0], suffixes, SK_ARRAY_COUNT(suffixes), &images);
}
SkDebugf("%d GMs x %d configs, %d tests, %d pictures, %d images\n",
gms.count(), configs.count(), tests.count(), skps.count(), images.count());
DM::Reporter reporter;
DM::TaskRunner tasks;
kick_off_tests(tests, &reporter, &tasks);
kick_off_gms(gms, configs, gpuAPI, &reporter, &tasks);
kick_off_skps(skps, &reporter, &tasks);
kick_off_images(images, &reporter, &tasks);
tasks.wait();
DM::JsonWriter::DumpJson();
SkDebugf("\n");
#ifdef SK_DEBUG
if (FLAGS_portableFonts && FLAGS_reportUsedChars) {
sk_tool_utils::report_used_chars();
}
#endif
SkTArray<SkString> failures;
reporter.getFailures(&failures);
report_failures(failures);
return failures.count() > 0;
} }
#if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL) #if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -52,8 +52,8 @@ void JsonWriter::DumpJson() {
Json::Value result; Json::Value result;
result["key"]["name"] = gBitmapResults[i].name.c_str(); result["key"]["name"] = gBitmapResults[i].name.c_str();
result["key"]["config"] = gBitmapResults[i].config.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["key"]["source_type"] = gBitmapResults[i].sourceType.c_str();
result["ext"] = gBitmapResults[i].ext.c_str();
result["md5"] = gBitmapResults[i].md5.c_str(); result["md5"] = gBitmapResults[i].md5.c_str();
root["results"].append(result); root["results"].append(result);

View File

@ -23,11 +23,11 @@ public:
* Info describing a single run. * Info describing a single run.
*/ */
struct BitmapResult { struct BitmapResult {
SkString name; // E.g. "ninepatch-stretch", "desk-gws_skp" SkString name; // E.g. "ninepatch-stretch", "desk_gws.skp"
SkString config; // "gpu", "8888" SkString config; // "gpu", "8888", "serialize", "pipe"
SkString mode; // "direct", "default-tilegrid", "pipe" SkString sourceType; // "gm", "skp", "image"
SkString sourceType; // "GM", "SKP"
SkString md5; // In ASCII, so 32 bytes long. SkString md5; // In ASCII, so 32 bytes long.
SkString ext; // Extension of file we wrote: "png", "pdf", ...
}; };
/** /**

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

389
dm/DMSrcSink.cpp Normal file
View File

@ -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

177
dm/DMSrcSink.h Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -21,6 +21,7 @@
'tools.gyp:crash_handler', 'tools.gyp:crash_handler',
'tools.gyp:proc_stats', 'tools.gyp:proc_stats',
'tools.gyp:sk_tool_utils', 'tools.gyp:sk_tool_utils',
'tools.gyp:timer',
], ],
'includes': [ 'includes': [
'gmslides.gypi', 'gmslides.gypi',
@ -29,22 +30,8 @@
], ],
'sources': [ 'sources': [
'../dm/DM.cpp', '../dm/DM.cpp',
'../dm/DMCpuGMTask.cpp', '../dm/DMSrcSink.cpp',
'../dm/DMGpuGMTask.cpp',
'../dm/DMPDFRasterizeTask.cpp',
'../dm/DMImageTask.cpp',
'../dm/DMJsonWriter.cpp', '../dm/DMJsonWriter.cpp',
'../dm/DMPDFTask.cpp',
'../dm/DMPipeTask.cpp',
'../dm/DMQuiltTask.cpp',
'../dm/DMReporter.cpp',
'../dm/DMSKPTask.cpp',
'../dm/DMSerializeTask.cpp',
'../dm/DMTask.cpp',
'../dm/DMTaskRunner.cpp',
'../dm/DMTestTask.cpp',
'../dm/DMUtil.cpp',
'../dm/DMWriteTask.cpp',
'../gm/gm.cpp', '../gm/gm.cpp',
'../src/pipe/utils/SamplePipeControllers.cpp', '../src/pipe/utils/SamplePipeControllers.cpp',

View File

@ -7,9 +7,9 @@
#include "SkCommonFlags.h" #include "SkCommonFlags.h"
DEFINE_string(config, "565 8888 pdf gpu nonrendering angle nvprmsaa4", DEFINE_string(config, "565 8888 gpu nonrendering angle nvprmsaa4 ",
"Options: 565 8888 pdf gpu nonrendering msaa4 msaa16 nvprmsaa4 nvprmsaa16 " "Options: 565 8888 pdf gpu nonrendering msaa4 msaa16 nvprmsaa4 nvprmsaa16 "
"gpudft gpunull gpudebug angle mesa"); "gpudft gpunull gpudebug angle mesa (and many more)");
DEFINE_bool(cpu, true, "master switch for running CPU-bound work."); DEFINE_bool(cpu, true, "master switch for running CPU-bound work.");

View File

@ -51,3 +51,14 @@ void WallTimer::start() {
void WallTimer::end() { void WallTimer::end() {
fWall = fSysTimer.endWall(); fWall = fSysTimer.endWall();
} }
SkString HumanizeMs(double ms) {
if (ms > 1e+3) return SkStringPrintf("%.3gs", ms/1e3);
if (ms < 1e-3) return SkStringPrintf("%.3gns", ms*1e6);
#ifdef SK_BUILD_FOR_WIN
if (ms < 1) return SkStringPrintf("%.3gus", ms*1e3);
#else
if (ms < 1) return SkStringPrintf("%.3gµs", ms*1e3);
#endif
return SkStringPrintf("%.3gms", ms);
}

View File

@ -8,6 +8,7 @@
#define Timer_DEFINED #define Timer_DEFINED
#include "SkTypes.h" #include "SkTypes.h"
#include "SkString.h"
#if defined(SK_BUILD_FOR_WIN32) #if defined(SK_BUILD_FOR_WIN32)
#include "SysTimer_windows.h" #include "SysTimer_windows.h"
@ -70,4 +71,6 @@ private:
SysTimer fSysTimer; SysTimer fSysTimer;
}; };
SkString HumanizeMs(double);
#endif #endif