Home | History | Annotate | Download | only in gm
      1 #include "gm.h"
      2 #include "SkColorPriv.h"
      3 #include "SkGraphics.h"
      4 #include "SkImageDecoder.h"
      5 #include "SkImageEncoder.h"
      6 #include "SkPicture.h"
      7 #include "SkStream.h"
      8 #include "SkRefCnt.h"
      9 
     10 #include "GrContext.h"
     11 #include "SkGpuCanvas.h"
     12 #include "SkGpuDevice.h"
     13 #include "SkEGLContext.h"
     14 #include "SkDevice.h"
     15 
     16 #ifdef SK_SUPPORT_PDF
     17     #include "SkPDFDevice.h"
     18     #include "SkPDFDocument.h"
     19 #endif
     20 
     21 using namespace skiagm;
     22 
     23 // need to explicitly declare this, or we get some weird infinite loop llist
     24 template GMRegistry* GMRegistry::gHead;
     25 
     26 class Iter {
     27 public:
     28     Iter() {
     29         fReg = GMRegistry::Head();
     30     }
     31 
     32     GM* next() {
     33         if (fReg) {
     34             GMRegistry::Factory fact = fReg->factory();
     35             fReg = fReg->next();
     36             return fact(0);
     37         }
     38         return NULL;
     39     }
     40 
     41     static int Count() {
     42         const GMRegistry* reg = GMRegistry::Head();
     43         int count = 0;
     44         while (reg) {
     45             count += 1;
     46             reg = reg->next();
     47         }
     48         return count;
     49     }
     50 
     51 private:
     52     const GMRegistry* fReg;
     53 };
     54 
     55 static SkString make_name(const char shortName[], const char configName[]) {
     56     SkString name(shortName);
     57     name.appendf("_%s", configName);
     58     return name;
     59 }
     60 
     61 static SkString make_filename(const char path[],
     62                               const char pathSuffix[],
     63                               const SkString& name,
     64                               const char suffix[]) {
     65     SkString filename(path);
     66     if (filename.endsWith("/")) {
     67         filename.remove(filename.size() - 1, 1);
     68     }
     69     filename.append(pathSuffix);
     70     filename.append("/");
     71     filename.appendf("%s.%s", name.c_str(), suffix);
     72     return filename;
     73 }
     74 
     75 /* since PNG insists on unpremultiplying our alpha, we take no precision chances
     76     and force all pixels to be 100% opaque, otherwise on compare we may not get
     77     a perfect match.
     78  */
     79 static void force_all_opaque(const SkBitmap& bitmap) {
     80     SkAutoLockPixels lock(bitmap);
     81     for (int y = 0; y < bitmap.height(); y++) {
     82         for (int x = 0; x < bitmap.width(); x++) {
     83             *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT);
     84         }
     85     }
     86 }
     87 
     88 static bool write_bitmap(const SkString& path, const SkBitmap& bitmap) {
     89     SkBitmap copy;
     90     bitmap.copyTo(&copy, SkBitmap::kARGB_8888_Config);
     91     force_all_opaque(copy);
     92     return SkImageEncoder::EncodeFile(path.c_str(), copy,
     93                                       SkImageEncoder::kPNG_Type, 100);
     94 }
     95 
     96 static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
     97     int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
     98     int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
     99     int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);
    100     return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
    101 }
    102 
    103 static void compute_diff(const SkBitmap& target, const SkBitmap& base,
    104                          SkBitmap* diff) {
    105     SkAutoLockPixels alp(*diff);
    106 
    107     const int w = target.width();
    108     const int h = target.height();
    109     for (int y = 0; y < h; y++) {
    110         for (int x = 0; x < w; x++) {
    111             SkPMColor c0 = *base.getAddr32(x, y);
    112             SkPMColor c1 = *target.getAddr32(x, y);
    113             SkPMColor d = 0;
    114             if (c0 != c1) {
    115                 d = compute_diff_pmcolor(c0, c1);
    116             }
    117             *diff->getAddr32(x, y) = d;
    118         }
    119     }
    120 }
    121 
    122 static bool compare(const SkBitmap& target, const SkBitmap& base,
    123                     const SkString& name, const char* renderModeDescriptor,
    124                     SkBitmap* diff) {
    125     SkBitmap copy;
    126     const SkBitmap* bm = &target;
    127     if (target.config() != SkBitmap::kARGB_8888_Config) {
    128         target.copyTo(&copy, SkBitmap::kARGB_8888_Config);
    129         bm = &copy;
    130     }
    131 
    132     force_all_opaque(*bm);
    133 
    134     const int w = bm->width();
    135     const int h = bm->height();
    136     if (w != base.width() || h != base.height()) {
    137         SkDebugf(
    138 "---- %s dimensions mismatch for %s base [%d %d] current [%d %d]\n",
    139                  renderModeDescriptor, name.c_str(),
    140                  base.width(), base.height(), w, h);
    141         return false;
    142     }
    143 
    144     SkAutoLockPixels bmLock(*bm);
    145     SkAutoLockPixels baseLock(base);
    146 
    147     for (int y = 0; y < h; y++) {
    148         for (int x = 0; x < w; x++) {
    149             SkPMColor c0 = *base.getAddr32(x, y);
    150             SkPMColor c1 = *bm->getAddr32(x, y);
    151             if (c0 != c1) {
    152                 SkDebugf(
    153 "----- %s pixel mismatch for %s at [%d %d] base 0x%08X current 0x%08X\n",
    154                          renderModeDescriptor, name.c_str(), x, y, c0, c1);
    155 
    156                 if (diff) {
    157                     diff->setConfig(SkBitmap::kARGB_8888_Config, w, h);
    158                     diff->allocPixels();
    159                     compute_diff(*bm, base, diff);
    160                 }
    161                 return false;
    162             }
    163         }
    164     }
    165 
    166     // they're equal
    167     return true;
    168 }
    169 
    170 static bool write_pdf(const SkString& path, const SkDynamicMemoryWStream& pdf) {
    171     SkFILEWStream stream(path.c_str());
    172     return stream.write(pdf.getStream(), pdf.getOffset());
    173 }
    174 
    175 enum Backend {
    176   kRaster_Backend,
    177   kGPU_Backend,
    178   kPDF_Backend,
    179 };
    180 
    181 struct ConfigData {
    182     SkBitmap::Config    fConfig;
    183     Backend             fBackend;
    184     const char*         fName;
    185 };
    186 
    187 /// Returns true if processing should continue, false to skip the
    188 /// remainder of this config for this GM.
    189 //@todo thudson 22 April 2011 - could refactor this to take in
    190 // a factory to generate the context, always call readPixels()
    191 // (logically a noop for rasters, if wasted time), and thus collapse the
    192 // GPU special case and also let this be used for SkPicture testing.
    193 static void setup_bitmap(const ConfigData& gRec, SkISize& size,
    194                          SkBitmap* bitmap) {
    195     bitmap->setConfig(gRec.fConfig, size.width(), size.height());
    196     bitmap->allocPixels();
    197     bitmap->eraseColor(0);
    198 }
    199 
    200 // Returns true if the test should continue, false if the test should
    201 // halt.
    202 static bool generate_image(GM* gm, const ConfigData& gRec,
    203                            GrContext* context,
    204                            SkBitmap& bitmap) {
    205     SkISize size (gm->getISize());
    206     setup_bitmap(gRec, size, &bitmap);
    207     SkCanvas canvas(bitmap);
    208 
    209     if (gRec.fBackend == kRaster_Backend) {
    210         gm->draw(&canvas);
    211     } else {  // GPU
    212         if (NULL == context) {
    213             return false;
    214         }
    215         SkGpuCanvas gc(context,
    216                        SkGpuDevice::Current3DApiRenderTarget());
    217         gc.setDevice(gc.createDevice(bitmap.config(),
    218                                      bitmap.width(),
    219                                      bitmap.height(),
    220                                      bitmap.isOpaque(),
    221                                      false))->unref();
    222         gm->draw(&gc);
    223         gc.readPixels(&bitmap); // overwrite our previous allocation
    224     }
    225     return true;
    226 }
    227 
    228 static void generate_image_from_picture(GM* gm, const ConfigData& gRec,
    229                                         SkPicture* pict, SkBitmap* bitmap) {
    230     SkISize size = gm->getISize();
    231     setup_bitmap(gRec, size, bitmap);
    232     SkCanvas canvas(*bitmap);
    233     canvas.drawPicture(*pict);
    234 }
    235 
    236 static void generate_pdf(GM* gm, SkDynamicMemoryWStream& pdf) {
    237 #ifdef SK_SUPPORT_PDF
    238     SkISize size = gm->getISize();
    239     SkMatrix identity;
    240     identity.reset();
    241     SkPDFDevice* dev = new SkPDFDevice(size, size, identity);
    242     SkAutoUnref aur(dev);
    243 
    244     SkCanvas c(dev);
    245     gm->draw(&c);
    246 
    247     SkPDFDocument doc;
    248     doc.appendPage(dev);
    249     doc.emitPDF(&pdf);
    250 #endif
    251 }
    252 
    253 static bool write_reference_image(const ConfigData& gRec,
    254                                   const char writePath [],
    255                                   const char renderModeDescriptor [],
    256                                   const SkString& name,
    257                                   SkBitmap& bitmap,
    258                                   SkDynamicMemoryWStream* pdf) {
    259     SkString path;
    260     bool success = false;
    261     if (gRec.fBackend != kPDF_Backend) {
    262         path = make_filename(writePath, renderModeDescriptor, name, "png");
    263         success = write_bitmap(path, bitmap);
    264     } else if (pdf) {
    265         path = make_filename(writePath, renderModeDescriptor, name, "pdf");
    266         success = write_pdf(path, *pdf);
    267     }
    268     if (!success) {
    269         fprintf(stderr, "FAILED to write %s\n", path.c_str());
    270     }
    271     return success;
    272 }
    273 
    274 static bool compare_to_reference_image(const char readPath [],
    275                                        const SkString& name,
    276                                        SkBitmap &bitmap,
    277                                        const char diffPath [],
    278                                        const char renderModeDescriptor []) {
    279     SkString path = make_filename(readPath, "", name, "png");
    280     SkBitmap orig;
    281     bool success = SkImageDecoder::DecodeFile(path.c_str(), &orig,
    282                         SkBitmap::kARGB_8888_Config,
    283                         SkImageDecoder::kDecodePixels_Mode, NULL);
    284     if (success) {
    285         SkBitmap diffBitmap;
    286         success = compare(bitmap, orig, name, renderModeDescriptor,
    287                           diffPath ? &diffBitmap : NULL);
    288         if (!success && diffPath) {
    289             SkString diffName = make_filename(diffPath, "", name, ".diff.png");
    290             fprintf(stderr, "Writing %s\n", diffName.c_str());
    291             write_bitmap(diffName, diffBitmap);
    292         }
    293     } else {
    294         fprintf(stderr, "FAILED to read %s\n", path.c_str());
    295     }
    296     return success;
    297 }
    298 
    299 static bool handle_test_results(GM* gm,
    300                                 const ConfigData& gRec,
    301                                 const char writePath [],
    302                                 const char readPath [],
    303                                 const char diffPath [],
    304                                 const char renderModeDescriptor [],
    305                                 SkBitmap& bitmap,
    306                                 SkDynamicMemoryWStream* pdf) {
    307     SkString name = make_name(gm->shortName(), gRec.fName);
    308 
    309     if (writePath) {
    310         write_reference_image(gRec, writePath, renderModeDescriptor,
    311                               name, bitmap, pdf);
    312     // TODO: Figure out a way to compare PDFs.
    313     } else if (readPath && gRec.fBackend != kPDF_Backend) {
    314         return compare_to_reference_image(readPath, name, bitmap,
    315                                    diffPath, renderModeDescriptor);
    316     }
    317     return true;
    318 }
    319 
    320 static SkPicture* generate_new_picture(GM* gm) {
    321     // Pictures are refcounted so must be on heap
    322     SkPicture* pict = new SkPicture;
    323     SkCanvas* cv = pict->beginRecording(1000, 1000);
    324     gm->draw(cv);
    325     pict->endRecording();
    326 
    327     return pict;
    328 }
    329 
    330 static SkPicture* stream_to_new_picture(const SkPicture& src) {
    331 
    332     // To do in-memory commiunications with a stream, we need to:
    333     // * create a dynamic memory stream
    334     // * copy it into a buffer
    335     // * create a read stream from it
    336     // ?!?!
    337 
    338     SkDynamicMemoryWStream storage;
    339     src.serialize(&storage);
    340 
    341     int streamSize = storage.getOffset();
    342     SkAutoMalloc dstStorage(streamSize);
    343     void* dst = dstStorage.get();
    344     //char* dst = new char [streamSize];
    345     //@todo thudson 22 April 2011 when can we safely delete [] dst?
    346     storage.copyTo(dst);
    347     SkMemoryStream pictReadback(dst, streamSize);
    348     SkPicture* retval = new SkPicture (&pictReadback);
    349     return retval;
    350 }
    351 
    352 // Test: draw into a bitmap or pdf.
    353 // Depending on flags, possibly compare to an expected image
    354 // and possibly output a diff image if it fails to match.
    355 static bool test_drawing(GM* gm,
    356                          const ConfigData& gRec,
    357                          const char writePath [],
    358                          const char readPath [],
    359                          const char diffPath [],
    360                          GrContext* context) {
    361     SkBitmap bitmap;
    362     SkDynamicMemoryWStream pdf;
    363 
    364     if (gRec.fBackend == kRaster_Backend ||
    365             gRec.fBackend == kGPU_Backend) {
    366         // Early exit if we can't generate the image, but this is
    367         // expected in some cases, so don't report a test failure.
    368         if (!generate_image(gm, gRec, context, bitmap)) {
    369             return true;
    370         }
    371     }
    372     // TODO: Figure out a way to compare PDFs.
    373     if (gRec.fBackend == kPDF_Backend && writePath) {
    374         generate_pdf(gm, pdf);
    375     }
    376     return handle_test_results(gm, gRec, writePath, readPath, diffPath,
    377                         "", bitmap, &pdf);
    378 }
    379 
    380 static bool test_picture_playback(GM* gm,
    381                                   const ConfigData& gRec,
    382                                   const char readPath [],
    383                                   const char diffPath []) {
    384     SkPicture* pict = generate_new_picture(gm);
    385     SkAutoUnref aur(pict);
    386 
    387     if (kRaster_Backend == gRec.fBackend) {
    388         SkBitmap bitmap;
    389         generate_image_from_picture(gm, gRec, pict, &bitmap);
    390         return handle_test_results(gm, gRec, NULL, readPath, diffPath,
    391                             "-replay", bitmap, NULL);
    392     }
    393     return true;
    394 }
    395 
    396 static bool test_picture_serialization(GM* gm,
    397                                        const ConfigData& gRec,
    398                                        const char readPath [],
    399                                        const char diffPath []) {
    400     SkPicture* pict = generate_new_picture(gm);
    401     SkAutoUnref aurp(pict);
    402     SkPicture* repict = stream_to_new_picture(*pict);
    403     SkAutoUnref aurr(repict);
    404 
    405     if (kRaster_Backend == gRec.fBackend) {
    406         SkBitmap bitmap;
    407         generate_image_from_picture(gm, gRec, repict, &bitmap);
    408         return handle_test_results(gm, gRec, NULL, readPath, diffPath,
    409                             "-serialize", bitmap, NULL);
    410     }
    411     return true;
    412 }
    413 
    414 static void usage(const char * argv0) {
    415     SkDebugf("%s [-w writePath] [-r readPath] [-d diffPath]\n", argv0);
    416     SkDebugf("    [--replay] [--serialize]\n");
    417     SkDebugf("    writePath: directory to write rendered images in.\n");
    418     SkDebugf(
    419 "    readPath: directory to read reference images from;\n"
    420 "        reports if any pixels mismatch between reference and new images\n");
    421     SkDebugf("    diffPath: directory to write difference images in.\n");
    422     SkDebugf("    --replay: exercise SkPicture replay.\n");
    423     SkDebugf(
    424 "    --serialize: exercise SkPicture serialization & deserialization.\n");
    425 }
    426 
    427 static const ConfigData gRec[] = {
    428     { SkBitmap::kARGB_8888_Config, kRaster_Backend, "8888" },
    429     { SkBitmap::kARGB_4444_Config, kRaster_Backend, "4444" },
    430     { SkBitmap::kRGB_565_Config,   kRaster_Backend, "565" },
    431     { SkBitmap::kARGB_8888_Config, kGPU_Backend,    "gpu" },
    432 #ifdef SK_SUPPORT_PDF
    433     { SkBitmap::kARGB_8888_Config, kPDF_Backend,    "pdf" },
    434 #endif
    435 };
    436 
    437 int main(int argc, char * const argv[]) {
    438     SkAutoGraphics ag;
    439 
    440     const char* writePath = NULL;   // if non-null, where we write the originals
    441     const char* readPath = NULL;    // if non-null, were we read from to compare
    442     const char* diffPath = NULL;    // if non-null, where we write our diffs (from compare)
    443 
    444     bool doReplay = true;
    445     bool doSerialize = false;
    446     const char* const commandName = argv[0];
    447     char* const* stop = argv + argc;
    448     for (++argv; argv < stop; ++argv) {
    449         if (strcmp(*argv, "-w") == 0) {
    450             argv++;
    451             if (argv < stop && **argv) {
    452                 writePath = *argv;
    453             }
    454         } else if (strcmp(*argv, "-r") == 0) {
    455             argv++;
    456             if (argv < stop && **argv) {
    457                 readPath = *argv;
    458             }
    459         } else if (strcmp(*argv, "-d") == 0) {
    460             argv++;
    461             if (argv < stop && **argv) {
    462                 diffPath = *argv;
    463             }
    464         } else if (strcmp(*argv, "--noreplay") == 0) {
    465             doReplay = false;
    466         } else if (strcmp(*argv, "--serialize") == 0) {
    467             doSerialize = true;
    468         } else {
    469           usage(commandName);
    470           return -1;
    471         }
    472     }
    473     if (argv != stop) {
    474       usage(commandName);
    475       return -1;
    476     }
    477 
    478     // setup a GL context for drawing offscreen
    479     GrContext* context = NULL;
    480     SkEGLContext eglContext;
    481     if (eglContext.init(1024, 1024)) {
    482         context = GrContext::CreateGLShaderContext();
    483     }
    484 
    485     Iter iter;
    486     GM* gm;
    487 
    488     if (readPath) {
    489         fprintf(stderr, "reading from %s\n", readPath);
    490     } else if (writePath) {
    491         fprintf(stderr, "writing to %s\n", writePath);
    492     }
    493 
    494     // Accumulate success of all tests so we can flag error in any
    495     // one with the return value.
    496     bool overallSuccess = true;
    497     while ((gm = iter.next()) != NULL) {
    498         SkISize size = gm->getISize();
    499         SkDebugf("drawing... %s [%d %d]\n", gm->shortName(),
    500                  size.width(), size.height());
    501 
    502         for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); i++) {
    503             bool testSuccess = test_drawing(gm, gRec[i],
    504                          writePath, readPath, diffPath, context);
    505             overallSuccess &= testSuccess;
    506 
    507             if (doReplay && testSuccess) {
    508                 testSuccess = test_picture_playback(gm, gRec[i],
    509                                       readPath, diffPath);
    510                 overallSuccess &= testSuccess;
    511             }
    512 
    513             if (doSerialize && testSuccess) {
    514                 overallSuccess &= test_picture_serialization(gm, gRec[i],
    515                                            readPath, diffPath);
    516             }
    517         }
    518         SkDELETE(gm);
    519     }
    520     if (false == overallSuccess) {
    521         return -1;
    522     }
    523     return 0;
    524 }
    525 
    526 ///////////////////////////////////////////////////////////////////////////////
    527 
    528 using namespace skiagm;
    529 
    530 GM::GM() {}
    531 GM::~GM() {}
    532 
    533 void GM::draw(SkCanvas* canvas) {
    534     this->onDraw(canvas);
    535 }
    536