Output encoded PNGs when gltestthreading or serialize fails

For configs that compare results of two drawing methods, it's helpful to
know how the two images differed. This takes both images, PNG encodes them
(at maximum compression settings), then base64 encodes them into data URIs
so they can be viewed directly from the logs.

These kinds of failures are typically very rare, and currently happen
infrequently due to flaky GMs - we don't want to be using this kind of
facility all the time.

Bug: skia:7011
Change-Id: Ib6c271cb8f6cd657cf6ca8ccfee97d0193b4e6ed
Reviewed-on: https://skia-review.googlesource.com/43240
Reviewed-by: Kevin Lubick <kjlubick@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
This commit is contained in:
Brian Osman 2017-09-06 17:08:30 -04:00 committed by Skia Commit-Bot
parent ff9d6d3bc2
commit e5756eccc4

View File

@ -9,6 +9,7 @@
#include "Resources.h"
#include "SkAndroidCodec.h"
#include "SkAutoMalloc.h"
#include "SkBase64.h"
#include "SkCodec.h"
#include "SkCodecImageGenerator.h"
#include "SkColorSpace.h"
@ -37,6 +38,7 @@
#include "SkPictureData.h"
#include "SkPictureRecorder.h"
#include "SkPipe.h"
#include "SkPngEncoder.h"
#include "SkRandom.h"
#include "SkRecordDraw.h"
#include "SkRecorder.h"
@ -1428,6 +1430,73 @@ Error NullSink::draw(const Src& src, SkBitmap*, SkWStream*, SkString*) const {
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
static bool encode_png_base64(const SkBitmap& bitmap, SkString* dst) {
SkPixmap pm;
if (!bitmap.peekPixels(&pm)) {
dst->set("peekPixels failed");
return false;
}
// We're going to embed this PNG in a data URI, so make it as small as possible
SkPngEncoder::Options options;
options.fFilterFlags = SkPngEncoder::FilterFlag::kAll;
options.fZLibLevel = 9;
options.fUnpremulBehavior = pm.colorSpace() ? SkTransferFunctionBehavior::kRespect
: SkTransferFunctionBehavior::kIgnore;
SkDynamicMemoryWStream wStream;
if (!SkPngEncoder::Encode(&wStream, pm, options)) {
dst->set("SkPngEncoder::Encode failed");
return false;
}
sk_sp<SkData> pngData = wStream.detachAsData();
size_t len = SkBase64::Encode(pngData->data(), pngData->size(), nullptr);
// The PNG can be almost arbitrarily large. We don't want to fill our logs with enormous URLs.
// Infra says these can be pretty big, as long as we're only outputting them on failure.
static const size_t kMaxBase64Length = 1024 * 1024;
if (len > kMaxBase64Length) {
dst->printf("Encoded image too large (%u bytes)", static_cast<uint32_t>(len));
return false;
}
dst->resize(len);
SkBase64::Encode(pngData->data(), pngData->size(), dst->writable_str());
return true;
}
static Error compare_bitmaps(const SkBitmap& reference, const SkBitmap& bitmap) {
// The dimensions are a property of the Src only, and so should be identical.
SkASSERT(reference.getSize() == bitmap.getSize());
if (reference.getSize() != bitmap.getSize()) {
return "Dimensions don't match reference";
}
// All SkBitmaps in DM are tight, so this comparison is easy.
if (0 != memcmp(reference.getPixels(), bitmap.getPixels(), reference.getSize())) {
SkString encoded;
SkString errString("Pixels don't match reference");
if (encode_png_base64(reference, &encoded)) {
errString.append("\nExpected: data:image/png;base64,");
errString.append(encoded);
} else {
errString.append("\nExpected image failed to encode: ");
errString.append(encoded);
}
if (encode_png_base64(bitmap, &encoded)) {
errString.append("\nActual: data:image/png;base64,");
errString.append(encoded);
} else {
errString.append("\nActual image failed to encode: ");
errString.append(encoded);
}
return errString;
}
return "";
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
DEFINE_bool(gpuStats, false, "Append GPU stats to the log for each GPU task?");
GPUSink::GPUSink(GrContextFactory::ContextType ct,
@ -1547,16 +1616,7 @@ Error GPUThreadTestingSink::draw(const Src& src, SkBitmap* dst, SkWStream* wStre
return refErr;
}
// The dimensions are a property of the Src only, and so should be identical.
SkASSERT(reference.getSize() == dst->getSize());
if (reference.getSize() != dst->getSize()) {
return "Dimensions don't match reference";
}
// All SkBitmaps in DM are tight, so this comparison is easy.
if (0 != memcmp(reference.getPixels(), dst->getPixels(), reference.getSize())) {
return "Pixels don't match reference";
}
return "";
return compare_bitmaps(reference, *dst);
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
@ -1746,15 +1806,7 @@ static Error check_against_reference(const SkBitmap* bitmap, const Src& src, Sin
if (!err.isEmpty()) {
return err;
}
// The dimensions are a property of the Src only, and so should be identical.
SkASSERT(reference.getSize() == bitmap->getSize());
if (reference.getSize() != bitmap->getSize()) {
return "Dimensions don't match reference";
}
// All SkBitmaps in DM are tight, so this comparison is easy.
if (0 != memcmp(reference.getPixels(), bitmap->getPixels(), reference.getSize())) {
return "Pixels don't match reference";
}
return compare_bitmaps(reference, *bitmap);
}
return "";
}