00b621cfc0
This should be a drop-in replacement for most for-loops to make them run in parallel: for (int i = 0; i < N; i++) { code... } ~~~> sk_parallel_for(N, [&](int i) { code... }); This is just syntax sugar over SkTaskGroup to make this use case really easy to write. There's no more overhead that we weren't already forced to add using an interface like batch(), and no extra heap allocations. I've replaced 3 uses of SkTaskGroup with sk_parallel_for: 1) My unit tests for SkOnce. 2) Cary's path fuzzer. 3) SkMultiPictureDraw. Performance should be the same. Please compare left and right for readability. :) BUG=skia: No public API changes. TBR=reed@google.com Review URL: https://codereview.chromium.org/1184373003
1115 lines
35 KiB
C++
Executable File
1115 lines
35 KiB
C++
Executable File
/*
|
|
* Copyright 2013 Google Inc.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "CrashHandler.h"
|
|
// #include "OverwriteLine.h"
|
|
#include "Resources.h"
|
|
#include "SkBitmap.h"
|
|
#include "SkCanvas.h"
|
|
#include "SkColor.h"
|
|
#include "SkColorPriv.h"
|
|
#include "SkCommandLineFlags.h"
|
|
#include "SkDevice.h"
|
|
#include "SkForceLinking.h"
|
|
#include "SkGraphics.h"
|
|
#include "SkImageDecoder.h"
|
|
#include "SkImageEncoder.h"
|
|
#include "SkOSFile.h"
|
|
#include "SkPathOpsDebug.h"
|
|
#include "SkPicture.h"
|
|
#include "SkRTConf.h"
|
|
#include "SkRunnable.h"
|
|
#include "SkTSort.h"
|
|
#include "SkStream.h"
|
|
#include "SkString.h"
|
|
#include "SkTArray.h"
|
|
#include "SkTDArray.h"
|
|
#include "SkTaskGroup.h"
|
|
#include "SkTemplates.h"
|
|
#include "SkTime.h"
|
|
|
|
__SK_FORCE_IMAGE_DECODER_LINKING;
|
|
|
|
/* add local exceptions here */
|
|
/* TODO : add command flag interface */
|
|
const struct SkipOverTest {
|
|
int directory;
|
|
const char* filename;
|
|
bool blamePathOps;
|
|
} skipOver[] = {
|
|
{ 2, "http___www_groupon_sg_.skp", false}, // SkAAClip::Builder::addRun SkASSERT(fBounds.contains(x, y));
|
|
{ 6, "http___www_googleventures_com_.skp", true}, // addTCoincident SkASSERT(test->fT < 1);
|
|
{ 7, "http___www_foxsports_nl_.skp", true}, // (no repro on mac) addT SkASSERT(this != other || fVerb == SkPath::kCubic_Verb)
|
|
{13, "http___www_modernqigong_com_.skp", false}, // SkAAClip::Builder::addRun SkASSERT(fBounds.contains(x, y));
|
|
{14, "http___www_devbridge_com_.skp", true}, // checkSmallCoincidence SkASSERT(!next->fSmall || checkMultiple);
|
|
{16, "http___www_1023world_net_.skp", false}, // bitmap decode assert (corrupt skp?)
|
|
{19, "http___www_alamdi_com_.skp", true}, // cubic/quad intersection
|
|
{26, "http___www_liveencounters_net_.skp", true}, // (no repro on mac) checkSmall addT:549 (line, expects cubic)
|
|
{28, "http___www_encros_fr_.skp", false}, // SkAAClip::Builder::addRun SkASSERT(fBounds.contains(x, y));
|
|
{37, "http___www_familysurvivalprotocol_wordpress_com_.skp", true}, // bumpSpan SkASSERT(span->fOppValue >= 0);
|
|
{39, "http___sufeinet_com_.skp", false}, // bitmap decode assert (corrupt skp?)
|
|
{41, "http___www_rano360_com_.skp", true}, // checkSmallCoincidence SkASSERT(!next->fSmall || checkMultiple);
|
|
{44, "http___www_firstunitedbank_com_.skp", true}, // addTCancel SkASSERT(oIndex > 0);
|
|
{46, "http___www_shinydemos_com_.skp", true}, // addSimpleAngle SkASSERT(index == count() - 2);
|
|
{48, "http___www_familysurvivalprotocol_com_.skp", true}, // bumpSpan SkASSERT "span->fOppValue >= 0"
|
|
{57, "http___www_lptemp_com_.skp", true}, // addTCoincident oPeek = &other->fTs[++oPeekIndex];
|
|
{71, "http___www_1milyonkahraman_org_.skp", true}, // addTCoincident SkASSERT(test->fT < 1);
|
|
{88, "http___www_apuntesdelechuza_wordpress_com_.skp", true}, // bumpSpan SkASSERT "span->fOppValue >= 0"
|
|
{89, "http___www_mobilizedconsulting_com_.skp", true}, // addTCancel SkASSERT(oIndex > 0);
|
|
{93, "http___www_simple_living_in_suffolk_co_uk_.skp", true}, // bumpSpan SkASSERT "span->fOppValue >= 0"
|
|
};
|
|
|
|
size_t skipOverCount = sizeof(skipOver) / sizeof(skipOver[0]);
|
|
|
|
|
|
/* customize file in/out here */
|
|
/* TODO : add command flag interface */
|
|
#define CHROME_VERSION "1e5dfa4-4a995df"
|
|
#define SUMMARY_RUN 1
|
|
|
|
#ifdef SK_BUILD_FOR_WIN
|
|
#define DRIVE_SPEC "D:"
|
|
#define PATH_SLASH "\\"
|
|
#else
|
|
#define DRIVE_SPEC ""
|
|
#define PATH_SLASH "/"
|
|
#endif
|
|
|
|
#define IN_DIR_PRE DRIVE_SPEC PATH_SLASH "skps" PATH_SLASH "slave"
|
|
#define OUT_DIR_PRE DRIVE_SPEC PATH_SLASH "skpOut" PATH_SLASH "slave"
|
|
#define OUT_DIR_SUM DRIVE_SPEC PATH_SLASH "skpOut" PATH_SLASH "summary"
|
|
#define DIR_POST PATH_SLASH "All" PATH_SLASH CHROME_VERSION
|
|
|
|
static const char outOpDir[] = "opClip";
|
|
static const char outOldDir[] = "oldClip";
|
|
static const char outStatusDir[] = "statusTest";
|
|
|
|
static SkString get_in_path(int dirNo, const char* filename) {
|
|
SkString path;
|
|
SkASSERT(dirNo);
|
|
path.appendf("%s%d%s", IN_DIR_PRE, dirNo, DIR_POST);
|
|
if (!sk_exists(path.c_str())) {
|
|
SkDebugf("could not read %s\n", path.c_str());
|
|
return SkString();
|
|
}
|
|
if (filename) {
|
|
path.appendf("%s%s", PATH_SLASH, filename);
|
|
if (!sk_exists(path.c_str())) {
|
|
SkDebugf("could not read %s\n", path.c_str());
|
|
return SkString();
|
|
}
|
|
}
|
|
return path;
|
|
}
|
|
|
|
static void make_recursive_dir(const SkString& path) {
|
|
if (sk_exists(path.c_str())) {
|
|
return;
|
|
}
|
|
const char* pathStr = path.c_str();
|
|
int last = (int) path.size();
|
|
do {
|
|
while (last > 0 && pathStr[--last] != PATH_SLASH[0])
|
|
;
|
|
SkASSERT(last > 0);
|
|
SkString shorter(pathStr, last);
|
|
if (sk_mkdir(shorter.c_str())) {
|
|
break;
|
|
}
|
|
} while (true);
|
|
do {
|
|
while (last < (int) path.size() && pathStr[++last] != PATH_SLASH[0])
|
|
;
|
|
SkString shorter(pathStr, last);
|
|
SkAssertResult(sk_mkdir(shorter.c_str()));
|
|
} while (last < (int) path.size());
|
|
}
|
|
|
|
static SkString get_out_path(int dirNo, const char* dirName) {
|
|
SkString path;
|
|
SkASSERT(dirNo);
|
|
SkASSERT(dirName);
|
|
path.appendf("%s%d%s%s%s", OUT_DIR_PRE, dirNo, DIR_POST, PATH_SLASH, dirName);
|
|
make_recursive_dir(path);
|
|
return path;
|
|
}
|
|
|
|
static SkString get_sum_path(const char* dirName) {
|
|
SkString path;
|
|
SkASSERT(dirName);
|
|
path.appendf("%s%d%s%s", OUT_DIR_SUM, SUMMARY_RUN, PATH_SLASH, dirName);
|
|
SkDebugf("%s\n", path.c_str());
|
|
make_recursive_dir(path);
|
|
return path;
|
|
}
|
|
|
|
static SkString make_png_name(const char* filename) {
|
|
SkString pngName = SkString(filename);
|
|
pngName.remove(pngName.size() - 3, 3);
|
|
pngName.append("png");
|
|
return pngName;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////
|
|
|
|
enum TestStep {
|
|
kCompareBits,
|
|
kEncodeFiles,
|
|
};
|
|
|
|
enum {
|
|
kMaxLength = 256,
|
|
kMaxFiles = 128,
|
|
kSmallLimit = 1000,
|
|
};
|
|
|
|
struct TestResult {
|
|
void init(int dirNo) {
|
|
fDirNo = dirNo;
|
|
sk_bzero(fFilename, sizeof(fFilename));
|
|
fTestStep = kCompareBits;
|
|
fScale = 1;
|
|
}
|
|
|
|
void init(int dirNo, const SkString& filename) {
|
|
fDirNo = dirNo;
|
|
strcpy(fFilename, filename.c_str());
|
|
fTestStep = kCompareBits;
|
|
fScale = 1;
|
|
}
|
|
|
|
SkString status() {
|
|
SkString outStr;
|
|
outStr.printf("%s %d %d\n", fFilename, fPixelError, fTime);
|
|
return outStr;
|
|
}
|
|
|
|
SkString progress() {
|
|
SkString outStr;
|
|
outStr.printf("dir=%d %s ", fDirNo, fFilename);
|
|
if (fPixelError) {
|
|
outStr.appendf(" err=%d", fPixelError);
|
|
}
|
|
if (fTime) {
|
|
outStr.appendf(" time=%d", fTime);
|
|
}
|
|
if (fScale != 1) {
|
|
outStr.appendf(" scale=%d", fScale);
|
|
}
|
|
outStr.appendf("\n");
|
|
return outStr;
|
|
|
|
}
|
|
|
|
void test(int dirNo, const SkString& filename) {
|
|
init(dirNo);
|
|
strcpy(fFilename, filename.c_str());
|
|
testOne();
|
|
}
|
|
|
|
void testOne();
|
|
|
|
char fFilename[kMaxLength];
|
|
TestStep fTestStep;
|
|
int fDirNo;
|
|
int fPixelError;
|
|
int fTime;
|
|
int fScale;
|
|
};
|
|
|
|
class SortByPixel : public TestResult {
|
|
public:
|
|
bool operator<(const SortByPixel& rh) const {
|
|
return fPixelError < rh.fPixelError;
|
|
}
|
|
};
|
|
|
|
class SortByTime : public TestResult {
|
|
public:
|
|
bool operator<(const SortByTime& rh) const {
|
|
return fTime < rh.fTime;
|
|
}
|
|
};
|
|
|
|
class SortByName : public TestResult {
|
|
public:
|
|
bool operator<(const SortByName& rh) const {
|
|
return strcmp(fFilename, rh.fFilename) < 0;
|
|
}
|
|
};
|
|
|
|
struct TestState {
|
|
void init(int dirNo) {
|
|
fResult.init(dirNo);
|
|
}
|
|
|
|
SkTDArray<SortByPixel> fPixelWorst;
|
|
SkTDArray<SortByTime> fSlowest;
|
|
TestResult fResult;
|
|
};
|
|
|
|
struct TestRunner {
|
|
~TestRunner();
|
|
void render();
|
|
SkTDArray<class TestRunnable*> fRunnables;
|
|
};
|
|
|
|
class TestRunnable : public SkRunnable {
|
|
public:
|
|
void run() override {
|
|
SkGraphics::SetTLSFontCacheLimit(1 * 1024 * 1024);
|
|
(*fTestFun)(&fState);
|
|
}
|
|
|
|
TestState fState;
|
|
void (*fTestFun)(TestState*);
|
|
};
|
|
|
|
|
|
class TestRunnableDir : public TestRunnable {
|
|
public:
|
|
TestRunnableDir(void (*testFun)(TestState*), int dirNo, TestRunner* runner) {
|
|
fState.init(dirNo);
|
|
fTestFun = testFun;
|
|
}
|
|
|
|
};
|
|
|
|
class TestRunnableFile : public TestRunnable {
|
|
public:
|
|
TestRunnableFile(void (*testFun)(TestState*), int dirNo, const char* name, TestRunner* runner) {
|
|
fState.init(dirNo);
|
|
strcpy(fState.fResult.fFilename, name);
|
|
fTestFun = testFun;
|
|
}
|
|
};
|
|
|
|
class TestRunnableEncode : public TestRunnableFile {
|
|
public:
|
|
TestRunnableEncode(void (*testFun)(TestState*), int dirNo, const char* name, TestRunner* runner)
|
|
: TestRunnableFile(testFun, dirNo, name, runner) {
|
|
fState.fResult.fTestStep = kEncodeFiles;
|
|
}
|
|
};
|
|
|
|
TestRunner::~TestRunner() {
|
|
for (int index = 0; index < fRunnables.count(); index++) {
|
|
SkDELETE(fRunnables[index]);
|
|
}
|
|
}
|
|
|
|
void TestRunner::render() {
|
|
// TODO: this doesn't really need to use SkRunnables any more.
|
|
// We can just write the code to run in the for-loop directly.
|
|
sk_parallel_for(fRunnables.count(), [&](int i) {
|
|
fRunnables[i]->run();
|
|
});
|
|
}
|
|
|
|
////////////////////////////////////////////////
|
|
|
|
|
|
static int similarBits(const SkBitmap& gr, const SkBitmap& sk) {
|
|
const int kRowCount = 3;
|
|
const int kThreshold = 3;
|
|
int width = SkTMin(gr.width(), sk.width());
|
|
if (width < kRowCount) {
|
|
return true;
|
|
}
|
|
int height = SkTMin(gr.height(), sk.height());
|
|
if (height < kRowCount) {
|
|
return true;
|
|
}
|
|
int errorTotal = 0;
|
|
SkTArray<int, true> errorRows;
|
|
errorRows.push_back_n(width * kRowCount);
|
|
SkAutoLockPixels autoGr(gr);
|
|
SkAutoLockPixels autoSk(sk);
|
|
for (int y = 0; y < height; ++y) {
|
|
SkPMColor* grRow = gr.getAddr32(0, y);
|
|
SkPMColor* skRow = sk.getAddr32(0, y);
|
|
int* base = &errorRows[0];
|
|
int* cOut = &errorRows[y % kRowCount];
|
|
for (int x = 0; x < width; ++x) {
|
|
SkPMColor grColor = grRow[x];
|
|
SkPMColor skColor = skRow[x];
|
|
int dr = SkGetPackedR32(grColor) - SkGetPackedR32(skColor);
|
|
int dg = SkGetPackedG32(grColor) - SkGetPackedG32(skColor);
|
|
int db = SkGetPackedB32(grColor) - SkGetPackedB32(skColor);
|
|
int error = cOut[x] = SkTMax(SkAbs32(dr), SkTMax(SkAbs32(dg), SkAbs32(db)));
|
|
if (error < kThreshold || x < 2) {
|
|
continue;
|
|
}
|
|
if (base[x - 2] < kThreshold
|
|
|| base[width + x - 2] < kThreshold
|
|
|| base[width * 2 + x - 2] < kThreshold
|
|
|| base[x - 1] < kThreshold
|
|
|| base[width + x - 1] < kThreshold
|
|
|| base[width * 2 + x - 1] < kThreshold
|
|
|| base[x] < kThreshold
|
|
|| base[width + x] < kThreshold
|
|
|| base[width * 2 + x] < kThreshold) {
|
|
continue;
|
|
}
|
|
errorTotal += error;
|
|
}
|
|
}
|
|
return errorTotal;
|
|
}
|
|
|
|
static bool addError(TestState* data, const TestResult& testResult) {
|
|
if (testResult.fPixelError <= 0 && testResult.fTime <= 0) {
|
|
return false;
|
|
}
|
|
int worstCount = data->fPixelWorst.count();
|
|
int pixelError = testResult.fPixelError;
|
|
if (pixelError > 0) {
|
|
for (int index = 0; index < worstCount; ++index) {
|
|
if (pixelError > data->fPixelWorst[index].fPixelError) {
|
|
data->fPixelWorst[index] = *(SortByPixel*) &testResult;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
int slowCount = data->fSlowest.count();
|
|
int time = testResult.fTime;
|
|
if (time > 0) {
|
|
for (int index = 0; index < slowCount; ++index) {
|
|
if (time > data->fSlowest[index].fTime) {
|
|
data->fSlowest[index] = *(SortByTime*) &testResult;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
if (pixelError > 0 && worstCount < kMaxFiles) {
|
|
*data->fPixelWorst.append() = *(SortByPixel*) &testResult;
|
|
return true;
|
|
}
|
|
if (time > 0 && slowCount < kMaxFiles) {
|
|
*data->fSlowest.append() = *(SortByTime*) &testResult;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static SkMSec timePict(SkPicture* pic, SkCanvas* canvas) {
|
|
canvas->save();
|
|
SkScalar pWidth = pic->cullRect().width();
|
|
SkScalar pHeight = pic->cullRect().height();
|
|
const SkScalar maxDimension = 1000.0f;
|
|
const int slices = 3;
|
|
SkScalar xInterval = SkTMax(pWidth - maxDimension, 0.0f) / (slices - 1);
|
|
SkScalar yInterval = SkTMax(pHeight - maxDimension, 0.0f) / (slices - 1);
|
|
SkRect rect = {0, 0, SkTMin(maxDimension, pWidth), SkTMin(maxDimension, pHeight) };
|
|
canvas->clipRect(rect);
|
|
SkMSec start = SkTime::GetMSecs();
|
|
for (int x = 0; x < slices; ++x) {
|
|
for (int y = 0; y < slices; ++y) {
|
|
pic->playback(canvas);
|
|
canvas->translate(0, yInterval);
|
|
}
|
|
canvas->translate(xInterval, -yInterval * slices);
|
|
}
|
|
SkMSec end = SkTime::GetMSecs();
|
|
canvas->restore();
|
|
return end - start;
|
|
}
|
|
|
|
static void drawPict(SkPicture* pic, SkCanvas* canvas, int scale) {
|
|
canvas->clear(SK_ColorWHITE);
|
|
if (scale != 1) {
|
|
canvas->save();
|
|
canvas->scale(1.0f / scale, 1.0f / scale);
|
|
}
|
|
pic->playback(canvas);
|
|
if (scale != 1) {
|
|
canvas->restore();
|
|
}
|
|
}
|
|
|
|
static void writePict(const SkBitmap& bitmap, const char* outDir, const char* pngName) {
|
|
SkString outFile = get_sum_path(outDir);
|
|
outFile.appendf("%s%s", PATH_SLASH, pngName);
|
|
if (!SkImageEncoder::EncodeFile(outFile.c_str(), bitmap, SkImageEncoder::kPNG_Type, 100)) {
|
|
SkDebugf("unable to encode gr %s (width=%d height=%d)\n", pngName,
|
|
bitmap.width(), bitmap.height());
|
|
}
|
|
}
|
|
|
|
void TestResult::testOne() {
|
|
SkPicture* pic = NULL;
|
|
{
|
|
#if DEBUG_SHOW_TEST_NAME
|
|
if (fTestStep == kCompareBits) {
|
|
SkString testName(fFilename);
|
|
const char http[] = "http";
|
|
if (testName.startsWith(http)) {
|
|
testName.remove(0, sizeof(http) - 1);
|
|
}
|
|
while (testName.startsWith("_")) {
|
|
testName.remove(0, 1);
|
|
}
|
|
const char dotSkp[] = ".skp";
|
|
if (testName.endsWith(dotSkp)) {
|
|
size_t len = testName.size();
|
|
testName.remove(len - (sizeof(dotSkp) - 1), sizeof(dotSkp) - 1);
|
|
}
|
|
testName.prepend("skp");
|
|
testName.append("1");
|
|
strncpy(DEBUG_FILENAME_STRING, testName.c_str(), DEBUG_FILENAME_STRING_LENGTH);
|
|
} else if (fTestStep == kEncodeFiles) {
|
|
strncpy(DEBUG_FILENAME_STRING, "", DEBUG_FILENAME_STRING_LENGTH);
|
|
}
|
|
#endif
|
|
SkString path = get_in_path(fDirNo, fFilename);
|
|
SkFILEStream stream(path.c_str());
|
|
if (!stream.isValid()) {
|
|
SkDebugf("invalid stream %s\n", path.c_str());
|
|
goto finish;
|
|
}
|
|
pic = SkPicture::CreateFromStream(&stream, &SkImageDecoder::DecodeMemory);
|
|
if (!pic) {
|
|
SkDebugf("unable to decode %s\n", fFilename);
|
|
goto finish;
|
|
}
|
|
SkScalar width = pic->cullRect().width();
|
|
SkScalar height = pic->cullRect().height();
|
|
SkBitmap oldBitmap, opBitmap;
|
|
fScale = 1;
|
|
while (width / fScale > 32767 || height / fScale > 32767) {
|
|
++fScale;
|
|
}
|
|
do {
|
|
int dimX = SkScalarCeilToInt(width / fScale);
|
|
int dimY = SkScalarCeilToInt(height / fScale);
|
|
if (oldBitmap.tryAllocN32Pixels(dimX, dimY) && opBitmap.tryAllocN32Pixels(dimX, dimY)) {
|
|
break;
|
|
}
|
|
SkDebugf("-%d-", fScale);
|
|
} while (++fScale < 256);
|
|
if (fScale >= 256) {
|
|
SkDebugf("unable to allocate bitmap for %s (w=%f h=%f)\n", fFilename,
|
|
width, height);
|
|
goto finish;
|
|
}
|
|
oldBitmap.eraseColor(SK_ColorWHITE);
|
|
SkCanvas oldCanvas(oldBitmap);
|
|
oldCanvas.setAllowSimplifyClip(false);
|
|
opBitmap.eraseColor(SK_ColorWHITE);
|
|
SkCanvas opCanvas(opBitmap);
|
|
opCanvas.setAllowSimplifyClip(true);
|
|
drawPict(pic, &oldCanvas, fScale);
|
|
drawPict(pic, &opCanvas, fScale);
|
|
if (fTestStep == kCompareBits) {
|
|
fPixelError = similarBits(oldBitmap, opBitmap);
|
|
int oldTime = timePict(pic, &oldCanvas);
|
|
int opTime = timePict(pic, &opCanvas);
|
|
fTime = SkTMax(0, oldTime - opTime);
|
|
} else if (fTestStep == kEncodeFiles) {
|
|
SkString pngStr = make_png_name(fFilename);
|
|
const char* pngName = pngStr.c_str();
|
|
writePict(oldBitmap, outOldDir, pngName);
|
|
writePict(opBitmap, outOpDir, pngName);
|
|
}
|
|
}
|
|
finish:
|
|
if (pic) {
|
|
pic->unref();
|
|
}
|
|
}
|
|
|
|
DEFINE_string2(match, m, "PathOpsSkpClipThreaded",
|
|
"[~][^]substring[$] [...] of test name to run.\n"
|
|
"Multiple matches may be separated by spaces.\n"
|
|
"~ causes a matching test to always be skipped\n"
|
|
"^ requires the start of the test to match\n"
|
|
"$ requires the end of the test to match\n"
|
|
"^ and $ requires an exact match\n"
|
|
"If a test does not match any list entry,\n"
|
|
"it is skipped unless some list entry starts with ~");
|
|
DEFINE_string2(dir, d, NULL, "range of directories (e.g., 1-100)");
|
|
DEFINE_string2(skp, s, NULL, "skp to test");
|
|
DEFINE_bool2(single, z, false, "run tests on a single thread internally.");
|
|
DEFINE_int32(testIndex, 0, "override local test index (PathOpsSkpClipOneOff only).");
|
|
DEFINE_bool2(verbose, v, false, "enable verbose output.");
|
|
|
|
static bool verbose() {
|
|
return FLAGS_verbose;
|
|
}
|
|
|
|
class Dirs {
|
|
public:
|
|
Dirs() {
|
|
reset();
|
|
sk_bzero(fRun, sizeof(fRun));
|
|
fSet = false;
|
|
}
|
|
|
|
int first() const {
|
|
int index = 0;
|
|
while (++index < kMaxDir) {
|
|
if (fRun[index]) {
|
|
return index;
|
|
}
|
|
}
|
|
SkASSERT(0);
|
|
return -1;
|
|
}
|
|
|
|
int last() const {
|
|
int index = kMaxDir;
|
|
while (--index > 0 && !fRun[index])
|
|
;
|
|
return index;
|
|
}
|
|
|
|
int next() {
|
|
while (++fIndex < kMaxDir) {
|
|
if (fRun[fIndex]) {
|
|
return fIndex;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void reset() {
|
|
fIndex = -1;
|
|
}
|
|
|
|
void set(int start, int end) {
|
|
while (start < end) {
|
|
fRun[start++] = 1;
|
|
}
|
|
fSet = true;
|
|
}
|
|
|
|
void setDefault() {
|
|
if (!fSet) {
|
|
set(1, 100);
|
|
}
|
|
}
|
|
|
|
private:
|
|
enum {
|
|
kMaxDir = 101
|
|
};
|
|
char fRun[kMaxDir];
|
|
int fIndex;
|
|
bool fSet;
|
|
} gDirs;
|
|
|
|
class Filenames {
|
|
public:
|
|
Filenames()
|
|
: fIndex(-1) {
|
|
}
|
|
|
|
const char* next() {
|
|
while (fNames && ++fIndex < fNames->count()) {
|
|
return (*fNames)[fIndex];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void set(const SkCommandLineFlags::StringArray& names) {
|
|
fNames = &names;
|
|
}
|
|
|
|
private:
|
|
int fIndex;
|
|
const SkCommandLineFlags::StringArray* fNames;
|
|
} gNames;
|
|
|
|
static bool buildTestDir(int dirNo, int firstDirNo,
|
|
SkTDArray<TestResult>* tests, SkTDArray<SortByName*>* sorted) {
|
|
SkString dirName = get_out_path(dirNo, outStatusDir);
|
|
if (!dirName.size()) {
|
|
return false;
|
|
}
|
|
SkOSFile::Iter iter(dirName.c_str(), "skp");
|
|
SkString filename;
|
|
while (iter.next(&filename)) {
|
|
TestResult test;
|
|
test.init(dirNo);
|
|
SkString spaceFile(filename);
|
|
char* spaces = spaceFile.writable_str();
|
|
int spaceSize = (int) spaceFile.size();
|
|
for (int index = 0; index < spaceSize; ++index) {
|
|
if (spaces[index] == '.') {
|
|
spaces[index] = ' ';
|
|
}
|
|
}
|
|
int success = sscanf(spaces, "%s %d %d skp", test.fFilename,
|
|
&test.fPixelError, &test.fTime);
|
|
if (success < 3) {
|
|
SkDebugf("failed to scan %s matched=%d\n", filename.c_str(), success);
|
|
return false;
|
|
}
|
|
*tests[dirNo - firstDirNo].append() = test;
|
|
}
|
|
if (!sorted) {
|
|
return true;
|
|
}
|
|
SkTDArray<TestResult>& testSet = tests[dirNo - firstDirNo];
|
|
int count = testSet.count();
|
|
for (int index = 0; index < count; ++index) {
|
|
*sorted[dirNo - firstDirNo].append() = (SortByName*) &testSet[index];
|
|
}
|
|
if (sorted[dirNo - firstDirNo].count()) {
|
|
SkTQSort<SortByName>(sorted[dirNo - firstDirNo].begin(),
|
|
sorted[dirNo - firstDirNo].end() - 1);
|
|
if (verbose()) {
|
|
SkDebugf("+");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void testSkpClip(TestState* data) {
|
|
data->fResult.testOne();
|
|
SkString statName(data->fResult.fFilename);
|
|
SkASSERT(statName.endsWith(".skp"));
|
|
statName.remove(statName.size() - 4, 4);
|
|
statName.appendf(".%d.%d.skp", data->fResult.fPixelError, data->fResult.fTime);
|
|
SkString statusFile = get_out_path(data->fResult.fDirNo, outStatusDir);
|
|
if (!statusFile.size()) {
|
|
SkDebugf("failed to create %s", statusFile.c_str());
|
|
return;
|
|
}
|
|
statusFile.appendf("%s%s", PATH_SLASH, statName.c_str());
|
|
SkFILE* file = sk_fopen(statusFile.c_str(), kWrite_SkFILE_Flag);
|
|
if (!file) {
|
|
SkDebugf("failed to create %s", statusFile.c_str());
|
|
return;
|
|
}
|
|
sk_fclose(file);
|
|
if (verbose()) {
|
|
if (data->fResult.fPixelError || data->fResult.fTime) {
|
|
SkDebugf("%s", data->fResult.progress().c_str());
|
|
} else {
|
|
SkDebugf(".");
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Less(const SortByName& a, const SortByName& b);
|
|
bool Less(const SortByName& a, const SortByName& b) {
|
|
return a < b;
|
|
}
|
|
|
|
static bool doOneDir(TestState* state, bool threaded) {
|
|
int dirNo = state->fResult.fDirNo;
|
|
SkString dirName = get_in_path(dirNo, NULL);
|
|
if (!dirName.size()) {
|
|
return false;
|
|
}
|
|
SkTDArray<TestResult> tests[1];
|
|
SkTDArray<SortByName*> sorted[1];
|
|
if (!buildTestDir(dirNo, dirNo, tests, sorted)) {
|
|
return false;
|
|
}
|
|
SkOSFile::Iter iter(dirName.c_str(), "skp");
|
|
SkString filename;
|
|
while (iter.next(&filename)) {
|
|
for (size_t index = 0; index < skipOverCount; ++index) {
|
|
if (skipOver[index].directory == dirNo
|
|
&& strcmp(filename.c_str(), skipOver[index].filename) == 0) {
|
|
goto checkEarlyExit;
|
|
}
|
|
}
|
|
{
|
|
SortByName name;
|
|
name.init(dirNo);
|
|
strncpy(name.fFilename, filename.c_str(), filename.size() - 4); // drop .skp
|
|
int count = sorted[0].count();
|
|
int idx = SkTSearch<SortByName, Less>(sorted[0].begin(), count, &name, sizeof(&name));
|
|
if (idx >= 0) {
|
|
SortByName* found = sorted[0][idx];
|
|
(void) addError(state, *found);
|
|
continue;
|
|
}
|
|
TestResult test;
|
|
test.init(dirNo, filename);
|
|
state->fResult = test;
|
|
testSkpClip(state);
|
|
#if 0 // artificially limit to a few while debugging code
|
|
static int debugLimit = 0;
|
|
if (++debugLimit == 5) {
|
|
return true;
|
|
}
|
|
#endif
|
|
}
|
|
checkEarlyExit:
|
|
;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void initTest() {
|
|
#if !defined SK_BUILD_FOR_WIN && !defined SK_BUILD_FOR_MAC
|
|
SK_CONF_SET("images.jpeg.suppressDecoderWarnings", true);
|
|
SK_CONF_SET("images.png.suppressDecoderWarnings", true);
|
|
#endif
|
|
}
|
|
|
|
static void testSkpClipEncode(TestState* data) {
|
|
data->fResult.testOne();
|
|
if (verbose()) {
|
|
SkDebugf("+");
|
|
}
|
|
}
|
|
|
|
static void encodeFound(TestState& state) {
|
|
if (verbose()) {
|
|
if (state.fPixelWorst.count()) {
|
|
SkTDArray<SortByPixel*> worst;
|
|
for (int index = 0; index < state.fPixelWorst.count(); ++index) {
|
|
*worst.append() = &state.fPixelWorst[index];
|
|
}
|
|
SkTQSort<SortByPixel>(worst.begin(), worst.end() - 1);
|
|
for (int index = 0; index < state.fPixelWorst.count(); ++index) {
|
|
const TestResult& result = *worst[index];
|
|
SkDebugf("%d %s pixelError=%d\n", result.fDirNo, result.fFilename, result.fPixelError);
|
|
}
|
|
}
|
|
if (state.fSlowest.count()) {
|
|
SkTDArray<SortByTime*> slowest;
|
|
for (int index = 0; index < state.fSlowest.count(); ++index) {
|
|
*slowest.append() = &state.fSlowest[index];
|
|
}
|
|
if (slowest.count() > 0) {
|
|
SkTQSort<SortByTime>(slowest.begin(), slowest.end() - 1);
|
|
for (int index = 0; index < slowest.count(); ++index) {
|
|
const TestResult& result = *slowest[index];
|
|
SkDebugf("%d %s time=%d\n", result.fDirNo, result.fFilename, result.fTime);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
TestRunner testRunner;
|
|
for (int index = 0; index < state.fPixelWorst.count(); ++index) {
|
|
const TestResult& result = state.fPixelWorst[index];
|
|
SkString filename(result.fFilename);
|
|
if (!filename.endsWith(".skp")) {
|
|
filename.append(".skp");
|
|
}
|
|
*testRunner.fRunnables.append() = SkNEW_ARGS(TestRunnableEncode,
|
|
(&testSkpClipEncode, result.fDirNo, filename.c_str(), &testRunner));
|
|
}
|
|
testRunner.render();
|
|
}
|
|
|
|
class Test {
|
|
public:
|
|
Test() {}
|
|
virtual ~Test() {}
|
|
|
|
const char* getName() { onGetName(&fName); return fName.c_str(); }
|
|
void run() { onRun(); }
|
|
|
|
protected:
|
|
virtual void onGetName(SkString*) = 0;
|
|
virtual void onRun() = 0;
|
|
|
|
private:
|
|
SkString fName;
|
|
};
|
|
|
|
typedef SkTRegistry<Test*(*)(void*)> TestRegistry;
|
|
|
|
#define DEF_TEST(name) \
|
|
static void test_##name(); \
|
|
class name##Class : public Test { \
|
|
public: \
|
|
static Test* Factory(void*) { return SkNEW(name##Class); } \
|
|
protected: \
|
|
void onGetName(SkString* name) override { \
|
|
name->set(#name); \
|
|
} \
|
|
void onRun() override { test_##name(); } \
|
|
}; \
|
|
static TestRegistry gReg_##name##Class(name##Class::Factory); \
|
|
static void test_##name()
|
|
|
|
DEF_TEST(PathOpsSkpClip) {
|
|
gDirs.setDefault();
|
|
initTest();
|
|
SkTArray<TestResult, true> errors;
|
|
TestState state;
|
|
state.init(0);
|
|
int dirNo;
|
|
gDirs.reset();
|
|
while ((dirNo = gDirs.next()) > 0) {
|
|
if (verbose()) {
|
|
SkDebugf("dirNo=%d\n", dirNo);
|
|
}
|
|
state.fResult.fDirNo = dirNo;
|
|
if (!doOneDir(&state, false)) {
|
|
break;
|
|
}
|
|
}
|
|
encodeFound(state);
|
|
}
|
|
|
|
static void testSkpClipMain(TestState* data) {
|
|
(void) doOneDir(data, true);
|
|
}
|
|
|
|
DEF_TEST(PathOpsSkpClipThreaded) {
|
|
gDirs.setDefault();
|
|
initTest();
|
|
TestRunner testRunner;
|
|
int dirNo;
|
|
gDirs.reset();
|
|
while ((dirNo = gDirs.next()) > 0) {
|
|
*testRunner.fRunnables.append() = SkNEW_ARGS(TestRunnableDir,
|
|
(&testSkpClipMain, dirNo, &testRunner));
|
|
}
|
|
testRunner.render();
|
|
TestState state;
|
|
state.init(0);
|
|
gDirs.reset();
|
|
while ((dirNo = gDirs.next()) > 0) {
|
|
TestState& testState = testRunner.fRunnables[dirNo - 1]->fState;
|
|
SkASSERT(testState.fResult.fDirNo == dirNo);
|
|
for (int inner = 0; inner < testState.fPixelWorst.count(); ++inner) {
|
|
addError(&state, testState.fPixelWorst[inner]);
|
|
}
|
|
for (int inner = 0; inner < testState.fSlowest.count(); ++inner) {
|
|
addError(&state, testState.fSlowest[inner]);
|
|
}
|
|
}
|
|
encodeFound(state);
|
|
}
|
|
|
|
static bool buildTests(SkTDArray<TestResult>* tests, SkTDArray<SortByName*>* sorted) {
|
|
int firstDirNo = gDirs.first();
|
|
int dirNo;
|
|
while ((dirNo = gDirs.next()) > 0) {
|
|
if (!buildTestDir(dirNo, firstDirNo, tests, sorted)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
DEF_TEST(PathOpsSkpClipUberThreaded) {
|
|
gDirs.setDefault();
|
|
const int firstDirNo = gDirs.next();
|
|
const int lastDirNo = gDirs.last();
|
|
initTest();
|
|
int dirCount = lastDirNo - firstDirNo + 1;
|
|
SkAutoTDeleteArray<SkTDArray<TestResult> > tests(new SkTDArray<TestResult>[dirCount]);
|
|
SkAutoTDeleteArray<SkTDArray<SortByName*> > sorted(new SkTDArray<SortByName*>[dirCount]);
|
|
if (!buildTests(tests.get(), sorted.get())) {
|
|
return;
|
|
}
|
|
TestRunner testRunner;
|
|
int dirNo;
|
|
gDirs.reset();
|
|
while ((dirNo = gDirs.next()) > 0) {
|
|
SkString dirName = get_in_path(dirNo, NULL);
|
|
if (!dirName.size()) {
|
|
continue;
|
|
}
|
|
SkOSFile::Iter iter(dirName.c_str(), "skp");
|
|
SkString filename;
|
|
while (iter.next(&filename)) {
|
|
for (size_t index = 0; index < skipOverCount; ++index) {
|
|
if (skipOver[index].directory == dirNo
|
|
&& strcmp(filename.c_str(), skipOver[index].filename) == 0) {
|
|
goto checkEarlyExit;
|
|
}
|
|
}
|
|
{
|
|
SortByName name;
|
|
name.init(dirNo);
|
|
strncpy(name.fFilename, filename.c_str(), filename.size() - 4); // drop .skp
|
|
int count = sorted.get()[dirNo - firstDirNo].count();
|
|
if (SkTSearch<SortByName, Less>(sorted.get()[dirNo - firstDirNo].begin(),
|
|
count, &name, sizeof(&name)) < 0) {
|
|
*testRunner.fRunnables.append() = SkNEW_ARGS(TestRunnableFile,
|
|
(&testSkpClip, dirNo, filename.c_str(), &testRunner));
|
|
}
|
|
}
|
|
checkEarlyExit:
|
|
;
|
|
}
|
|
|
|
}
|
|
testRunner.render();
|
|
SkAutoTDeleteArray<SkTDArray<TestResult> > results(new SkTDArray<TestResult>[dirCount]);
|
|
if (!buildTests(results.get(), NULL)) {
|
|
return;
|
|
}
|
|
SkTDArray<TestResult> allResults;
|
|
for (int dirNo = firstDirNo; dirNo <= lastDirNo; ++dirNo) {
|
|
SkTDArray<TestResult>& array = results.get()[dirNo - firstDirNo];
|
|
allResults.append(array.count(), array.begin());
|
|
}
|
|
int allCount = allResults.count();
|
|
SkTDArray<SortByPixel*> pixels;
|
|
SkTDArray<SortByTime*> times;
|
|
for (int index = 0; index < allCount; ++index) {
|
|
*pixels.append() = (SortByPixel*) &allResults[index];
|
|
*times.append() = (SortByTime*) &allResults[index];
|
|
}
|
|
TestState state;
|
|
if (pixels.count()) {
|
|
SkTQSort<SortByPixel>(pixels.begin(), pixels.end() - 1);
|
|
for (int inner = 0; inner < kMaxFiles; ++inner) {
|
|
*state.fPixelWorst.append() = *pixels[allCount - inner - 1];
|
|
}
|
|
}
|
|
if (times.count()) {
|
|
SkTQSort<SortByTime>(times.begin(), times.end() - 1);
|
|
for (int inner = 0; inner < kMaxFiles; ++inner) {
|
|
*state.fSlowest.append() = *times[allCount - inner - 1];
|
|
}
|
|
}
|
|
encodeFound(state);
|
|
}
|
|
|
|
DEF_TEST(PathOpsSkpClipOneOff) {
|
|
const int testIndex = FLAGS_testIndex;
|
|
int dirNo = gDirs.next();
|
|
if (dirNo < 0) {
|
|
dirNo = skipOver[testIndex].directory;
|
|
}
|
|
const char* skp = gNames.next();
|
|
if (!skp) {
|
|
skp = skipOver[testIndex].filename;
|
|
}
|
|
initTest();
|
|
SkAssertResult(get_in_path(dirNo, skp).size());
|
|
SkString filename(skp);
|
|
TestResult state;
|
|
state.test(dirNo, filename);
|
|
if (verbose()) {
|
|
SkDebugf("%s", state.status().c_str());
|
|
}
|
|
state.fTestStep = kEncodeFiles;
|
|
state.testOne();
|
|
}
|
|
|
|
DEF_TEST(PathOpsTestSkipped) {
|
|
for (size_t index = 0; index < skipOverCount; ++index) {
|
|
const SkipOverTest& skip = skipOver[index];
|
|
if (!skip.blamePathOps) {
|
|
continue;
|
|
}
|
|
int dirNo = skip.directory;
|
|
const char* skp = skip.filename;
|
|
initTest();
|
|
SkAssertResult(get_in_path(dirNo, skp).size());
|
|
SkString filename(skp);
|
|
TestResult state;
|
|
state.test(dirNo, filename);
|
|
if (verbose()) {
|
|
SkDebugf("%s", state.status().c_str());
|
|
}
|
|
state.fTestStep = kEncodeFiles;
|
|
state.testOne();
|
|
}
|
|
}
|
|
|
|
DEF_TEST(PathOpsCopyFails) {
|
|
FLAGS_verbose = true;
|
|
for (size_t index = 0; index < skipOverCount; ++index) {
|
|
int dirNo = skipOver[index].directory;
|
|
SkDebugf("mkdir -p " IN_DIR_PRE "%d" DIR_POST "\n", dirNo);
|
|
}
|
|
for (size_t index = 0; index < skipOverCount; ++index) {
|
|
int dirNo = skipOver[index].directory;
|
|
const char* filename = skipOver[index].filename;
|
|
SkDebugf("rsync -av cary-linux.cnc:/tera" PATH_SLASH "skps" PATH_SLASH "slave"
|
|
"%d" DIR_POST "/%s " IN_DIR_PRE "%d" DIR_POST "\n", dirNo, filename, dirNo);
|
|
}
|
|
}
|
|
|
|
template TestRegistry* TestRegistry::gHead;
|
|
|
|
class Iter {
|
|
public:
|
|
Iter() { this->reset(); }
|
|
void reset() { fReg = TestRegistry::Head(); }
|
|
|
|
Test* next() {
|
|
if (fReg) {
|
|
TestRegistry::Factory fact = fReg->factory();
|
|
fReg = fReg->next();
|
|
Test* test = fact(NULL);
|
|
return test;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
private:
|
|
const TestRegistry* fReg;
|
|
};
|
|
|
|
int tool_main(int argc, char** argv);
|
|
int tool_main(int argc, char** argv) {
|
|
SetupCrashHandler();
|
|
SkCommandLineFlags::SetUsage("");
|
|
SkCommandLineFlags::Parse(argc, argv);
|
|
SkGraphics::Init();
|
|
SkString header("PathOps SkpClip:");
|
|
if (!FLAGS_match.isEmpty()) {
|
|
header.appendf(" --match");
|
|
for (int index = 0; index < FLAGS_match.count(); ++index) {
|
|
header.appendf(" %s", FLAGS_match[index]);
|
|
}
|
|
}
|
|
if (!FLAGS_dir.isEmpty()) {
|
|
int count = FLAGS_dir.count();
|
|
for (int i = 0; i < count; ++i) {
|
|
const char* range = FLAGS_dir[i];
|
|
const char* dash = strchr(range, '-');
|
|
if (!dash) {
|
|
dash = strchr(range, ',');
|
|
}
|
|
int first = atoi(range);
|
|
int last = dash ? atoi(dash + 1) : first;
|
|
if (!first || !last) {
|
|
SkDebugf("couldn't parse --dir %s\n", range);
|
|
return 1;
|
|
}
|
|
gDirs.set(first, last);
|
|
}
|
|
}
|
|
if (!FLAGS_skp.isEmpty()) {
|
|
gNames.set(FLAGS_skp);
|
|
}
|
|
#ifdef SK_DEBUG
|
|
header.append(" SK_DEBUG");
|
|
#else
|
|
header.append(" SK_RELEASE");
|
|
#endif
|
|
header.appendf(" skia_arch_width=%d", (int)sizeof(void*) * 8);
|
|
if (FLAGS_verbose) {
|
|
header.appendf("\n");
|
|
}
|
|
SkDebugf("%s", header.c_str());
|
|
Iter iter;
|
|
Test* test;
|
|
while ((test = iter.next()) != NULL) {
|
|
SkAutoTDelete<Test> owned(test);
|
|
if (!SkCommandLineFlags::ShouldSkip(FLAGS_match, test->getName())) {
|
|
test->run();
|
|
}
|
|
}
|
|
SkGraphics::Term();
|
|
return 0;
|
|
}
|
|
|
|
#if !defined(SK_BUILD_FOR_IOS)
|
|
int main(int argc, char * const argv[]) {
|
|
return tool_main(argc, (char**) argv);
|
|
}
|
|
#endif
|