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(©, 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 = ⌖ 139 if (target.config() != SkBitmap::kARGB_8888_Config) { 140 target.copyTo(©, SkBitmap::kARGB_8888_Config); 141 bm = © 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