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