OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright 2011 Google Inc. | |
3 * | |
4 * Use of this source code is governed by a BSD-style license that can be | |
5 * found in the LICENSE file. | |
6 */ | |
7 | |
8 /* | |
9 * Code for the "gm" (Golden Master) rendering comparison tool. | |
10 * | |
11 * If you make changes to this, re-run the self-tests at gm/tests/run.sh | |
12 * to make sure they still pass... you may need to change the expected | |
13 * results of the self-test. | |
14 */ | |
15 | |
16 #include "gm.h" | |
17 #include "gm_error.h" | |
18 #include "gm_expectations.h" | |
19 #include "system_preferences.h" | |
20 #include "CrashHandler.h" | |
21 #include "ProcStats.h" | |
22 #include "Resources.h" | |
23 #include "SamplePipeControllers.h" | |
24 #include "SkBitmap.h" | |
25 #include "SkColorPriv.h" | |
26 #include "SkCommandLineFlags.h" | |
27 #include "SkData.h" | |
28 #include "SkDeferredCanvas.h" | |
29 #include "SkDevice.h" | |
30 #include "SkDocument.h" | |
31 #include "SkDrawFilter.h" | |
32 #include "SkForceLinking.h" | |
33 #include "SkGPipe.h" | |
34 #include "SkGraphics.h" | |
35 #include "SkImageDecoder.h" | |
36 #include "SkImageEncoder.h" | |
37 #include "SkJSONCPP.h" | |
38 #include "SkMultiPictureDraw.h" | |
39 #include "SkOSFile.h" | |
40 #include "SkPDFRasterizer.h" | |
41 #include "SkPicture.h" | |
42 #include "SkPictureRecorder.h" | |
43 #include "SkRefCnt.h" | |
44 #include "SkScalar.h" | |
45 #include "SkStream.h" | |
46 #include "SkString.h" | |
47 #include "SkSurface.h" | |
48 #include "SkTArray.h" | |
49 #include "SkTDict.h" | |
50 | |
51 #ifdef SK_DEBUG | |
52 static const bool kDebugOnly = true; | |
53 #define GR_DUMP_FONT_CACHE 0 | |
54 #else | |
55 static const bool kDebugOnly = false; | |
56 #endif | |
57 | |
58 __SK_FORCE_IMAGE_DECODER_LINKING; | |
59 | |
60 #if SK_SUPPORT_GPU | |
61 #include "GrContextFactory.h" | |
62 #include "SkGpuDevice.h" | |
63 typedef GrContextFactory::GLContextType GLContextType; | |
64 #define DEFAULT_CACHE_VALUE -1 | |
65 static int gGpuCacheSizeBytes; | |
66 static int gGpuCacheSizeCount; | |
67 #else | |
68 class GrContextFactory; | |
69 class GrContext; | |
70 class GrSurface; | |
71 typedef int GLContextType; | |
72 typedef int GrGLStandard; | |
73 #endif | |
74 | |
75 #define DEBUGFAIL_SEE_STDERR SkDEBUGFAIL("see stderr for message") | |
76 | |
77 DECLARE_bool(useDocumentInsteadOfDevice); | |
78 | |
79 #ifdef SK_SUPPORT_PDF | |
80 #include "SkPDFDevice.h" | |
81 #include "SkPDFDocument.h" | |
82 #endif | |
83 | |
84 // Until we resolve http://code.google.com/p/skia/issues/detail?id=455 , | |
85 // stop writing out XPS-format image baselines in gm. | |
86 #undef SK_SUPPORT_XPS | |
87 #ifdef SK_SUPPORT_XPS | |
88 #include "SkXPSDevice.h" | |
89 #endif | |
90 | |
91 #ifdef SK_BUILD_FOR_MAC | |
92 #include "SkCGUtils.h" | |
93 #endif | |
94 | |
95 using namespace skiagm; | |
96 | |
97 class Iter { | |
98 public: | |
99 Iter() { | |
100 this->reset(); | |
101 } | |
102 | |
103 void reset() { | |
104 fReg = GMRegistry::Head(); | |
105 } | |
106 | |
107 GM* next() { | |
108 if (fReg) { | |
109 GMRegistry::Factory fact = fReg->factory(); | |
110 fReg = fReg->next(); | |
111 return fact(0); | |
112 } | |
113 return NULL; | |
114 } | |
115 | |
116 static int Count() { | |
117 const GMRegistry* reg = GMRegistry::Head(); | |
118 int count = 0; | |
119 while (reg) { | |
120 count += 1; | |
121 reg = reg->next(); | |
122 } | |
123 return count; | |
124 } | |
125 | |
126 private: | |
127 const GMRegistry* fReg; | |
128 }; | |
129 | |
130 // TODO(epoger): Right now, various places in this code assume that all the | |
131 // image files read/written by GM use this file extension. | |
132 // Search for references to this constant to find these assumptions. | |
133 const static char kPNG_FileExtension[] = "png"; | |
134 | |
135 enum Backend { | |
136 kRaster_Backend, | |
137 kGPU_Backend, | |
138 kPDF_Backend, | |
139 kXPS_Backend, | |
140 }; | |
141 | |
142 enum BbhType { | |
143 kNone_BbhType, | |
144 kRTree_BbhType, | |
145 }; | |
146 | |
147 enum ConfigFlags { | |
148 kNone_ConfigFlag = 0x0, | |
149 /* Write GM images if a write path is provided. */ | |
150 kWrite_ConfigFlag = 0x1, | |
151 /* Read reference GM images if a read path is provided. */ | |
152 kRead_ConfigFlag = 0x2, | |
153 kRW_ConfigFlag = (kWrite_ConfigFlag | kRead_ConfigFlag), | |
154 /* Use distance fields for rendering text */ | |
155 kDFText_ConfigFlag = 0x4, | |
156 kRWDFT_ConfigFlag = (kRW_ConfigFlag | kDFText_ConfigFlag), | |
157 }; | |
158 | |
159 struct ConfigData { | |
160 SkColorType fColorType; | |
161 Backend fBackend; | |
162 GLContextType fGLContextType; // GPU backend only | |
163 int fSampleCnt; // GPU backend only | |
164 ConfigFlags fFlags; | |
165 const char* fName; | |
166 bool fRunByDefault; | |
167 }; | |
168 | |
169 struct PDFRasterizerData { | |
170 bool (*fRasterizerFunction)(SkStream*, SkBitmap*); | |
171 const char* fName; | |
172 bool fRunByDefault; | |
173 }; | |
174 | |
175 class BWTextDrawFilter : public SkDrawFilter { | |
176 public: | |
177 bool filter(SkPaint*, Type) SK_OVERRIDE; | |
178 }; | |
179 bool BWTextDrawFilter::filter(SkPaint* p, Type t) { | |
180 if (kText_Type == t) { | |
181 p->setAntiAlias(false); | |
182 } | |
183 return true; | |
184 } | |
185 | |
186 struct PipeFlagComboData { | |
187 const char* name; | |
188 uint32_t flags; | |
189 }; | |
190 | |
191 static PipeFlagComboData gPipeWritingFlagCombos[] = { | |
192 { "", 0 }, | |
193 { " cross-process", SkGPipeWriter::kCrossProcess_Flag }, | |
194 { " cross-process, shared address", SkGPipeWriter::kCrossProcess_Flag | |
195 | SkGPipeWriter::kSharedAddressSpace_Flag } | |
196 }; | |
197 | |
198 static SkData* encode_to_dct_data(size_t* pixelRefOffset, const SkBitmap& bitmap
); | |
199 DECLARE_int32(pdfRasterDpi); | |
200 | |
201 const static ErrorCombination kDefaultIgnorableErrorTypes = ErrorCombination() | |
202 .plus(kMissingExpectations_ErrorType) | |
203 .plus(kIntentionallySkipped_ErrorType); | |
204 | |
205 class GMMain { | |
206 public: | |
207 GMMain() : fUseFileHierarchy(false), fWriteChecksumBasedFilenames(false), | |
208 fIgnorableErrorTypes(kDefaultIgnorableErrorTypes), | |
209 fMismatchPath(NULL), fMissingExpectationsPath(NULL), fTestsRun(0)
, | |
210 fRenderModesEncountered(1) {} | |
211 | |
212 /** | |
213 * Assemble shortNamePlusConfig from (surprise!) shortName and configName. | |
214 * | |
215 * The method for doing so depends on whether we are using hierarchical nami
ng. | |
216 * For example, shortName "selftest1" and configName "8888" could be assembl
ed into | |
217 * either "selftest1_8888" or "8888/selftest1". | |
218 */ | |
219 SkString make_shortname_plus_config(const char *shortName, const char *confi
gName) { | |
220 SkString name; | |
221 if (0 == strlen(configName)) { | |
222 name.append(shortName); | |
223 } else if (fUseFileHierarchy) { | |
224 name.appendf("%s%c%s", configName, SkPATH_SEPARATOR, shortName); | |
225 } else { | |
226 name.appendf("%s_%s", shortName, configName); | |
227 } | |
228 return name; | |
229 } | |
230 | |
231 /** | |
232 * Assemble filename, suitable for writing out the results of a particular t
est. | |
233 */ | |
234 SkString make_filename(const char *path, | |
235 const char *shortName, | |
236 const char *configName, | |
237 const char *renderModeDescriptor, | |
238 const char *suffix) { | |
239 SkString filename = make_shortname_plus_config(shortName, configName); | |
240 filename.append(renderModeDescriptor); | |
241 filename.appendUnichar('.'); | |
242 filename.append(suffix); | |
243 return SkOSPath::Join(path, filename.c_str()); | |
244 } | |
245 | |
246 /** | |
247 * Assemble filename suitable for writing out an SkBitmap. | |
248 */ | |
249 SkString makeBitmapFilename(const char *path, | |
250 const char *shortName, | |
251 const char *configName, | |
252 const char *renderModeDescriptor, | |
253 const GmResultDigest &bitmapDigest) { | |
254 if (fWriteChecksumBasedFilenames) { | |
255 SkString filename; | |
256 filename.append(bitmapDigest.getHashType()); | |
257 filename.appendUnichar('_'); | |
258 filename.append(shortName); | |
259 filename.appendUnichar('_'); | |
260 filename.append(bitmapDigest.getDigestValue()); | |
261 filename.appendUnichar('.'); | |
262 filename.append(kPNG_FileExtension); | |
263 return SkOSPath::Join(path, filename.c_str()); | |
264 } else { | |
265 return make_filename(path, shortName, configName, renderModeDescript
or, | |
266 kPNG_FileExtension); | |
267 } | |
268 } | |
269 | |
270 /* since PNG insists on unpremultiplying our alpha, we take no | |
271 precision chances and force all pixels to be 100% opaque, | |
272 otherwise on compare we may not get a perfect match. | |
273 */ | |
274 static void force_all_opaque(const SkBitmap& bitmap) { | |
275 SkColorType colorType = bitmap.colorType(); | |
276 switch (colorType) { | |
277 case kN32_SkColorType: | |
278 force_all_opaque_8888(bitmap); | |
279 break; | |
280 case kRGB_565_SkColorType: | |
281 // nothing to do here; 565 bitmaps are inherently opaque | |
282 break; | |
283 default: | |
284 SkDebugf("unsupported bitmap colorType %d\n", colorType); | |
285 DEBUGFAIL_SEE_STDERR; | |
286 } | |
287 } | |
288 | |
289 static void force_all_opaque_8888(const SkBitmap& bitmap) { | |
290 SkAutoLockPixels lock(bitmap); | |
291 for (int y = 0; y < bitmap.height(); y++) { | |
292 for (int x = 0; x < bitmap.width(); x++) { | |
293 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT); | |
294 } | |
295 } | |
296 } | |
297 | |
298 static ErrorCombination WriteBitmap(const SkString& path, const SkBitmap& bi
tmap) { | |
299 // TODO(epoger): Now that we have removed force_all_opaque() | |
300 // from this method, we should be able to get rid of the | |
301 // transformation to 8888 format also. | |
302 SkBitmap copy; | |
303 bitmap.copyTo(©, kN32_SkColorType); | |
304 if (!SkImageEncoder::EncodeFile(path.c_str(), copy, | |
305 SkImageEncoder::kPNG_Type, | |
306 100)) { | |
307 SkDebugf("FAILED to write bitmap: %s\n", path.c_str()); | |
308 return ErrorCombination(kWritingReferenceImage_ErrorType); | |
309 } | |
310 return kEmpty_ErrorCombination; | |
311 } | |
312 | |
313 /** | |
314 * Add all render modes encountered thus far to the "modes" array. | |
315 */ | |
316 void GetRenderModesEncountered(SkTArray<SkString> &modes) { | |
317 SkTDict<int>::Iter iter(this->fRenderModesEncountered); | |
318 const char* mode; | |
319 while ((mode = iter.next(NULL)) != NULL) { | |
320 SkString modeAsString = SkString(mode); | |
321 // TODO(epoger): It seems a bit silly that all of these modes were | |
322 // recorded with a leading "-" which we have to remove here | |
323 // (except for mode "", which means plain old original mode). | |
324 // But that's how renderModeDescriptor has been passed into | |
325 // compare_test_results_to_reference_bitmap() historically, | |
326 // and changing that now may affect other parts of our code. | |
327 if (modeAsString.startsWith("-")) { | |
328 modeAsString.remove(0, 1); | |
329 } | |
330 modes.push_back(modeAsString); | |
331 } | |
332 } | |
333 | |
334 /** | |
335 * Returns true if failures on this test should be ignored. | |
336 */ | |
337 bool shouldIgnoreTest(const char *name) const { | |
338 for (int i = 0; i < fIgnorableTestNames.count(); i++) { | |
339 if (fIgnorableTestNames[i].equals(name)) { | |
340 return true; | |
341 } | |
342 } | |
343 return false; | |
344 } | |
345 | |
346 /** | |
347 * Calls RecordTestResults to record that we skipped a test. | |
348 * | |
349 * Depending on the backend, this may mean that we skipped a single rendermo
de, or all | |
350 * rendermodes; see http://skbug.com/1994 and https://codereview.chromium.or
g/129203002/ | |
351 */ | |
352 void RecordSkippedTest(const SkString& shortNamePlusConfig, | |
353 const char renderModeDescriptor [], Backend backend)
{ | |
354 if (kRaster_Backend == backend) { | |
355 // Skipping a test on kRaster_Backend means that we will skip ALL re
nderModes | |
356 // (as opposed to other backends, on which we only run the default r
enderMode). | |
357 // | |
358 // We cannot call RecordTestResults yet, because we won't know the f
ull set of | |
359 // renderModes until we have run all tests. | |
360 fTestsSkippedOnAllRenderModes.push_back(shortNamePlusConfig); | |
361 } else { | |
362 this->RecordTestResults(kIntentionallySkipped_ErrorType, shortNamePl
usConfig, | |
363 renderModeDescriptor); | |
364 } | |
365 } | |
366 | |
367 /** | |
368 * Records the results of this test in fTestsRun and fFailedTests. | |
369 * | |
370 * We even record successes, and errors that we regard as | |
371 * "ignorable"; we can filter them out later. | |
372 */ | |
373 void RecordTestResults(const ErrorCombination& errorCombination, | |
374 const SkString& shortNamePlusConfig, | |
375 const char renderModeDescriptor []) { | |
376 // Things to do regardless of errorCombination. | |
377 fTestsRun++; | |
378 int renderModeCount = 0; | |
379 this->fRenderModesEncountered.find(renderModeDescriptor, &renderModeCoun
t); | |
380 renderModeCount++; | |
381 this->fRenderModesEncountered.set(renderModeDescriptor, renderModeCount)
; | |
382 | |
383 if (errorCombination.isEmpty()) { | |
384 return; | |
385 } | |
386 | |
387 // Things to do only if there is some error condition. | |
388 SkString fullName = shortNamePlusConfig; | |
389 fullName.append(renderModeDescriptor); | |
390 for (int typeInt = 0; typeInt <= kLast_ErrorType; typeInt++) { | |
391 ErrorType type = static_cast<ErrorType>(typeInt); | |
392 if (errorCombination.includes(type)) { | |
393 fFailedTests[type].push_back(fullName); | |
394 } | |
395 } | |
396 } | |
397 | |
398 /** | |
399 * Return the number of significant (non-ignorable) errors we have | |
400 * encountered so far. | |
401 */ | |
402 int NumSignificantErrors() { | |
403 int significantErrors = 0; | |
404 for (int typeInt = 0; typeInt <= kLast_ErrorType; typeInt++) { | |
405 ErrorType type = static_cast<ErrorType>(typeInt); | |
406 if (!fIgnorableErrorTypes.includes(type)) { | |
407 significantErrors += fFailedTests[type].count(); | |
408 } | |
409 } | |
410 return significantErrors; | |
411 } | |
412 | |
413 /** | |
414 * Display the summary of results with this ErrorType. | |
415 * | |
416 * @param type which ErrorType | |
417 * @param verbose whether to be all verbose about it | |
418 */ | |
419 void DisplayResultTypeSummary(ErrorType type, bool verbose) { | |
420 bool isIgnorableType = fIgnorableErrorTypes.includes(type); | |
421 | |
422 SkString line; | |
423 if (isIgnorableType) { | |
424 line.append("[ ] "); | |
425 } else { | |
426 line.append("[*] "); | |
427 } | |
428 | |
429 SkTArray<SkString> *failedTestsOfThisType = &fFailedTests[type]; | |
430 int count = failedTestsOfThisType->count(); | |
431 line.appendf("%d %s", count, getErrorTypeName(type)); | |
432 if (!isIgnorableType || verbose) { | |
433 line.append(":"); | |
434 for (int i = 0; i < count; ++i) { | |
435 line.append(" "); | |
436 line.append((*failedTestsOfThisType)[i]); | |
437 } | |
438 } | |
439 SkDebugf("%s\n", line.c_str()); | |
440 } | |
441 | |
442 /** | |
443 * List contents of fFailedTests to stdout. | |
444 * | |
445 * @param verbose whether to be all verbose about it | |
446 */ | |
447 void ListErrors(bool verbose) { | |
448 // First, print a single summary line. | |
449 SkString summary; | |
450 summary.appendf("Ran %d tests:", fTestsRun); | |
451 for (int typeInt = 0; typeInt <= kLast_ErrorType; typeInt++) { | |
452 ErrorType type = static_cast<ErrorType>(typeInt); | |
453 summary.appendf(" %s=%d", getErrorTypeName(type), fFailedTests[type]
.count()); | |
454 } | |
455 SkDebugf("%s\n", summary.c_str()); | |
456 | |
457 // Now, for each failure type, list the tests that failed that way. | |
458 for (int typeInt = 0; typeInt <= kLast_ErrorType; typeInt++) { | |
459 this->DisplayResultTypeSummary(static_cast<ErrorType>(typeInt), verb
ose); | |
460 } | |
461 SkDebugf("(results marked with [*] will cause nonzero return value)\n"); | |
462 } | |
463 | |
464 static ErrorCombination write_document(const SkString& path, SkStreamAsset*
asset) { | |
465 SkFILEWStream stream(path.c_str()); | |
466 if (!stream.writeStream(asset, asset->getLength())) { | |
467 SkDebugf("FAILED to write document: %s\n", path.c_str()); | |
468 return ErrorCombination(kWritingReferenceImage_ErrorType); | |
469 } | |
470 return kEmpty_ErrorCombination; | |
471 } | |
472 | |
473 /** | |
474 * Prepare an SkBitmap to render a GM into. | |
475 * | |
476 * After you've rendered the GM into the SkBitmap, you must call | |
477 * complete_bitmap()! | |
478 * | |
479 * @todo thudson 22 April 2011 - could refactor this to take in | |
480 * a factory to generate the context, always call readPixels() | |
481 * (logically a noop for rasters, if wasted time), and thus collapse the | |
482 * GPU special case and also let this be used for SkPicture testing. | |
483 */ | |
484 static void setup_bitmap(const ConfigData& gRec, const SkISize& size, | |
485 SkBitmap* bitmap) { | |
486 bitmap->allocPixels(SkImageInfo::Make(size.width(), size.height(), | |
487 gRec.fColorType, kPremul_SkAlphaTy
pe)); | |
488 bitmap->eraseColor(SK_ColorTRANSPARENT); | |
489 } | |
490 | |
491 /** | |
492 * Any finalization steps we need to perform on the SkBitmap after | |
493 * we have rendered the GM into it. | |
494 * | |
495 * It's too bad that we are throwing away alpha channel data | |
496 * we could otherwise be examining, but this had always been happening | |
497 * before... it was buried within the compare() method at | |
498 * https://code.google.com/p/skia/source/browse/trunk/gm/gmmain.cpp?r=7289#3
05 . | |
499 * | |
500 * Apparently we need this, at least for bitmaps that are either: | |
501 * (a) destined to be written out as PNG files, or | |
502 * (b) compared against bitmaps read in from PNG files | |
503 * for the reasons described just above the force_all_opaque() method. | |
504 * | |
505 * Neglecting to do this led to the difficult-to-diagnose | |
506 * http://code.google.com/p/skia/issues/detail?id=1079 ('gm generating | |
507 * spurious pixel_error messages as of r7258') | |
508 * | |
509 * TODO(epoger): Come up with a better solution that allows us to | |
510 * compare full pixel data, including alpha channel, while still being | |
511 * robust in the face of transformations to/from PNG files. | |
512 * Options include: | |
513 * | |
514 * 1. Continue to call force_all_opaque(), but ONLY for bitmaps that | |
515 * will be written to, or compared against, PNG files. | |
516 * PRO: Preserve/compare alpha channel info for the non-PNG cases | |
517 * (comparing different renderModes in-memory) | |
518 * CON: The bitmaps (and hash digests) for these non-PNG cases would be | |
519 * different than those for the PNG-compared cases, and in the | |
520 * case of a failed renderMode comparison, how would we write the | |
521 * image to disk for examination? | |
522 * | |
523 * 2. Always compute image hash digests from PNG format (either | |
524 * directly from the the bytes of a PNG file, or capturing the | |
525 * bytes we would have written to disk if we were writing the | |
526 * bitmap out as a PNG). | |
527 * PRO: I think this would allow us to never force opaque, and to | |
528 * the extent that alpha channel data can be preserved in a PNG | |
529 * file, we could observe it. | |
530 * CON: If we read a bitmap from disk, we need to take its hash digest | |
531 * from the source PNG (we can't compute it from the bitmap we | |
532 * read out of the PNG, because we will have already premultiplied | |
533 * the alpha). | |
534 * CON: Seems wasteful to convert a bitmap to PNG format just to take | |
535 * its hash digest. (Although we're wasting lots of effort already | |
536 * calling force_all_opaque().) | |
537 * | |
538 * 3. Make the alpha premultiply/unpremultiply routines 100% consistent, | |
539 * so we can transform images back and forth without fear of off-by-one | |
540 * errors. | |
541 * CON: Math is hard. | |
542 * | |
543 * 4. Perform a "close enough" comparison of bitmaps (+/- 1 bit in each | |
544 * channel), rather than demanding absolute equality. | |
545 * CON: Can't do this with hash digests. | |
546 */ | |
547 static void complete_bitmap(SkBitmap* bitmap) { | |
548 force_all_opaque(*bitmap); | |
549 } | |
550 | |
551 static void InstallFilter(SkCanvas* canvas); | |
552 | |
553 static void invokeGM(GM* gm, SkCanvas* canvas, bool isPDF, bool isDeferred)
{ | |
554 SkAutoCanvasRestore acr(canvas, true); | |
555 | |
556 if (!isPDF) { | |
557 canvas->concat(gm->getInitialTransform()); | |
558 } | |
559 InstallFilter(canvas); | |
560 gm->setCanvasIsDeferred(isDeferred); | |
561 gm->draw(canvas); | |
562 canvas->setDrawFilter(NULL); | |
563 } | |
564 | |
565 static ErrorCombination generate_image(GM* gm, const ConfigData& gRec, | |
566 GrSurface* gpuTarget, | |
567 SkBitmap* bitmap, | |
568 bool deferred) { | |
569 const SkISize size (gm->getISize()); | |
570 | |
571 SkAutoTUnref<SkSurface> surface(CreateSurface(gRec, size, gpuTarget)); | |
572 SkAutoTUnref<SkCanvas> canvas; | |
573 | |
574 if (deferred) { | |
575 canvas.reset(SkDeferredCanvas::Create(surface)); | |
576 } else { | |
577 canvas.reset(SkRef(surface->getCanvas())); | |
578 } | |
579 invokeGM(gm, canvas, false, deferred); | |
580 canvas->flush(); | |
581 | |
582 setup_bitmap(gRec, size, bitmap); | |
583 surface->readPixels(bitmap->info(), bitmap->getPixels(), bitmap->rowByte
s(), 0, 0); | |
584 complete_bitmap(bitmap); | |
585 return kEmpty_ErrorCombination; | |
586 } | |
587 | |
588 static void DrawPictureToSurface(SkSurface* surf, | |
589 const SkPicture* pict, | |
590 SkScalar scale, | |
591 bool tile, | |
592 bool useMPD) { | |
593 SkASSERT(surf->width() == pict->cullRect().width() && | |
594 surf->height() == pict->cullRect().height()); | |
595 | |
596 if (tile) { | |
597 SkMultiPictureDraw mpd; | |
598 SkTDArray<SkSurface*> surfaces; | |
599 | |
600 const SkISize tileSize = SkISize::Make(16, 16); | |
601 | |
602 const SkImageInfo ii = surf->getCanvas()->imageInfo().makeWH(tileSiz
e.width(), | |
603 tileSiz
e.height()); | |
604 | |
605 for (int tileY = 0; tileY < pict->cullRect().height(); tileY += tile
Size.height()) { | |
606 for (int tileX = 0; tileX < pict->cullRect().width(); tileX += t
ileSize.width()) { | |
607 | |
608 *surfaces.append() = surf->getCanvas()->newSurface(ii); | |
609 | |
610 InstallFilter(surfaces.top()->getCanvas()); | |
611 | |
612 SkMatrix matrix; | |
613 matrix.setTranslate(-pict->cullRect().fLeft, -pict->cullRect
().fTop); | |
614 matrix.postTranslate(-SkIntToScalar(tileX), -SkIntToScalar(t
ileY)); | |
615 matrix.postScale(scale, scale); | |
616 | |
617 if (useMPD) { | |
618 mpd.add(surfaces.top()->getCanvas(), pict, &matrix, NULL
); | |
619 } else { | |
620 surfaces.top()->getCanvas()->drawPicture(pict, &matrix,
NULL); | |
621 } | |
622 } | |
623 } | |
624 | |
625 mpd.draw(); | |
626 | |
627 SkPaint gatherPaint; | |
628 gatherPaint.setXfermodeMode(SkXfermode::kSrc_Mode); | |
629 | |
630 int tileIndex = 0; | |
631 for (int tileY = 0; tileY < pict->cullRect().height(); tileY += tile
Size.height()) { | |
632 for (int tileX = 0; tileX < pict->cullRect().width(); tileX += t
ileSize.width()) { | |
633 surf->getCanvas()->drawImage(surfaces[tileIndex]->newImageSn
apshot(), | |
634 SkIntToScalar(tileX), SkIntToSc
alar(tileY), | |
635 &gatherPaint); | |
636 surfaces[tileIndex]->unref(); | |
637 tileIndex++; | |
638 } | |
639 } | |
640 } else { | |
641 InstallFilter(surf->getCanvas()); | |
642 | |
643 SkMatrix matrix; | |
644 matrix.setTranslate(-pict->cullRect().fLeft, -pict->cullRect().fTop)
; | |
645 matrix.postScale(scale, scale); | |
646 | |
647 if (useMPD) { | |
648 SkMultiPictureDraw mpd; | |
649 mpd.add(surf->getCanvas(), pict, &matrix, NULL); | |
650 mpd.draw(); | |
651 } else { | |
652 surf->getCanvas()->drawPicture(pict, &matrix, NULL); | |
653 } | |
654 } | |
655 } | |
656 | |
657 static void generate_image_from_picture(GM* gm, const ConfigData& config, | |
658 GrSurface* gpuTarget, | |
659 SkPicture* pict, SkBitmap* bitmap, | |
660 SkScalar scale = SK_Scalar1, | |
661 bool tile = false) { | |
662 const SkISize size = gm->getISize(); | |
663 | |
664 SkAutoTUnref<SkSurface> surf(CreateSurface(config, size, gpuTarget)); | |
665 | |
666 DrawPictureToSurface(surf, pict, scale, tile, false); | |
667 | |
668 setup_bitmap(config, size, bitmap); | |
669 | |
670 surf->readPixels(bitmap->info(), bitmap->getPixels(), bitmap->rowBytes()
, 0, 0); | |
671 | |
672 complete_bitmap(bitmap); | |
673 } | |
674 | |
675 static bool generate_pdf(GM* gm, SkDynamicMemoryWStream& pdf) { | |
676 #ifdef SK_SUPPORT_PDF | |
677 SkMatrix initialTransform = gm->getInitialTransform(); | |
678 if (FLAGS_useDocumentInsteadOfDevice) { | |
679 SkISize pageISize = gm->getISize(); | |
680 SkAutoTUnref<SkDocument> pdfDoc( | |
681 SkDocument::CreatePDF(&pdf, NULL, | |
682 encode_to_dct_data, | |
683 SkIntToScalar(FLAGS_pdfRasterDpi))); | |
684 | |
685 if (!pdfDoc.get()) { | |
686 return false; | |
687 } | |
688 | |
689 SkCanvas* canvas = NULL; | |
690 canvas = pdfDoc->beginPage(SkIntToScalar(pageISize.width()), | |
691 SkIntToScalar(pageISize.height())); | |
692 canvas->concat(initialTransform); | |
693 | |
694 invokeGM(gm, canvas, true, false); | |
695 | |
696 return pdfDoc->close(); | |
697 } else { | |
698 SkISize pageSize = gm->getISize(); | |
699 SkPDFDevice* dev = NULL; | |
700 if (initialTransform.isIdentity()) { | |
701 dev = new SkPDFDevice(pageSize, pageSize, initialTransform); | |
702 } else { | |
703 SkRect content = SkRect::MakeWH(SkIntToScalar(pageSize.width()), | |
704 SkIntToScalar(pageSize.height())
); | |
705 initialTransform.mapRect(&content); | |
706 if (!content.intersect(0, 0, SkIntToScalar(pageSize.width()), | |
707 SkIntToScalar(pageSize.height()))) { | |
708 content.setEmpty(); | |
709 } | |
710 SkISize contentSize = | |
711 SkISize::Make(SkScalarRoundToInt(content.width()), | |
712 SkScalarRoundToInt(content.height())); | |
713 dev = new SkPDFDevice(pageSize, contentSize, initialTransform); | |
714 } | |
715 dev->setDCTEncoder(encode_to_dct_data); | |
716 dev->setRasterDpi(SkIntToScalar(FLAGS_pdfRasterDpi)); | |
717 SkAutoUnref aur(dev); | |
718 SkCanvas c(dev); | |
719 invokeGM(gm, &c, true, false); | |
720 SkPDFDocument doc; | |
721 doc.appendPage(dev); | |
722 doc.emitPDF(&pdf); | |
723 } | |
724 #endif // SK_SUPPORT_PDF | |
725 return true; // Do not report failure if pdf is not supported. | |
726 } | |
727 | |
728 static void generate_xps(GM* gm, SkDynamicMemoryWStream& xps) { | |
729 #ifdef SK_SUPPORT_XPS | |
730 SkISize size = gm->getISize(); | |
731 | |
732 SkSize trimSize = SkSize::Make(SkIntToScalar(size.width()), | |
733 SkIntToScalar(size.height())); | |
734 static const SkScalar inchesPerMeter = SkScalarDiv(10000, 254); | |
735 static const SkScalar upm = 72 * inchesPerMeter; | |
736 SkVector unitsPerMeter = SkPoint::Make(upm, upm); | |
737 static const SkScalar ppm = 200 * inchesPerMeter; | |
738 SkVector pixelsPerMeter = SkPoint::Make(ppm, ppm); | |
739 | |
740 SkXPSDevice* dev = new SkXPSDevice(); | |
741 SkAutoUnref aur(dev); | |
742 | |
743 SkCanvas c(dev); | |
744 dev->beginPortfolio(&xps); | |
745 dev->beginSheet(unitsPerMeter, pixelsPerMeter, trimSize); | |
746 invokeGM(gm, &c, false, false); | |
747 dev->endSheet(); | |
748 dev->endPortfolio(); | |
749 | |
750 #endif | |
751 } | |
752 | |
753 /** | |
754 * Log more detail about the mistmatch between expectedBitmap and | |
755 * actualBitmap. | |
756 */ | |
757 void report_bitmap_diffs(const SkBitmap& expectedBitmap, const SkBitmap& act
ualBitmap, | |
758 const char *testName) { | |
759 const int expectedWidth = expectedBitmap.width(); | |
760 const int expectedHeight = expectedBitmap.height(); | |
761 const int width = actualBitmap.width(); | |
762 const int height = actualBitmap.height(); | |
763 if ((expectedWidth != width) || (expectedHeight != height)) { | |
764 SkDebugf("---- %s: dimension mismatch -- expected [%d %d], actual [%
d %d]\n", | |
765 testName, expectedWidth, expectedHeight, width, height); | |
766 return; | |
767 } | |
768 | |
769 if ((kN32_SkColorType != expectedBitmap.colorType()) || | |
770 (kN32_SkColorType != actualBitmap.colorType())) { | |
771 SkDebugf("---- %s: not computing max per-channel pixel mismatch beca
use non-8888\n", | |
772 testName); | |
773 return; | |
774 } | |
775 | |
776 SkAutoLockPixels alp0(expectedBitmap); | |
777 SkAutoLockPixels alp1(actualBitmap); | |
778 int errR = 0; | |
779 int errG = 0; | |
780 int errB = 0; | |
781 int errA = 0; | |
782 int differingPixels = 0; | |
783 | |
784 for (int y = 0; y < height; ++y) { | |
785 const SkPMColor* expectedPixelPtr = expectedBitmap.getAddr32(0, y); | |
786 const SkPMColor* actualPixelPtr = actualBitmap.getAddr32(0, y); | |
787 for (int x = 0; x < width; ++x) { | |
788 SkPMColor expectedPixel = *expectedPixelPtr++; | |
789 SkPMColor actualPixel = *actualPixelPtr++; | |
790 if (expectedPixel != actualPixel) { | |
791 differingPixels++; | |
792 errR = SkMax32(errR, SkAbs32((int)SkGetPackedR32(expectedPix
el) - | |
793 (int)SkGetPackedR32(actualPixel
))); | |
794 errG = SkMax32(errG, SkAbs32((int)SkGetPackedG32(expectedPix
el) - | |
795 (int)SkGetPackedG32(actualPixel
))); | |
796 errB = SkMax32(errB, SkAbs32((int)SkGetPackedB32(expectedPix
el) - | |
797 (int)SkGetPackedB32(actualPixel
))); | |
798 errA = SkMax32(errA, SkAbs32((int)SkGetPackedA32(expectedPix
el) - | |
799 (int)SkGetPackedA32(actualPixel
))); | |
800 } | |
801 } | |
802 } | |
803 SkDebugf("---- %s: %d (of %d) differing pixels, " | |
804 "max per-channel mismatch R=%d G=%d B=%d A=%d\n", | |
805 testName, differingPixels, width*height, errR, errG, errB, errA
); | |
806 } | |
807 | |
808 /** | |
809 * Compares actual hash digest to expectations, returning the set of errors | |
810 * (if any) that we saw along the way. | |
811 * | |
812 * If fMismatchPath has been set, and there are pixel diffs, then the | |
813 * actual bitmap will be written out to a file within fMismatchPath. | |
814 * And similarly for fMissingExpectationsPath... | |
815 * | |
816 * @param expectations what expectations to compare actualBitmap against | |
817 * @param actualBitmapAndDigest the SkBitmap we actually generated, and its
GmResultDigest | |
818 * @param shortName name of test, e.g. "selftest1" | |
819 * @param configName name of config, e.g. "8888" | |
820 * @param renderModeDescriptor e.g., "-rtree", "-deferred" | |
821 * @param addToJsonSummary whether to add these results (both actual and | |
822 * expected) to the JSON summary. Regardless of this setting, if | |
823 * we find an image mismatch in this test, we will write these | |
824 * results to the JSON summary. (This is so that we will always | |
825 * report errors across rendering modes, such as pipe vs tiled. | |
826 * See https://codereview.chromium.org/13650002/ ) | |
827 */ | |
828 ErrorCombination compare_to_expectations(Expectations expectations, | |
829 const BitmapAndDigest& actualBitmap
AndDigest, | |
830 const char *shortName, const char *
configName, | |
831 const char *renderModeDescriptor, | |
832 bool addToJsonSummary) { | |
833 ErrorCombination errors; | |
834 SkString shortNamePlusConfig = make_shortname_plus_config(shortName, con
figName); | |
835 SkString completeNameString(shortNamePlusConfig); | |
836 completeNameString.append(renderModeDescriptor); | |
837 completeNameString.append("."); | |
838 completeNameString.append(kPNG_FileExtension); | |
839 const char* completeName = completeNameString.c_str(); | |
840 | |
841 if (expectations.empty()) { | |
842 errors.add(kMissingExpectations_ErrorType); | |
843 | |
844 // Write out the "actuals" for any tests without expectations, if we
have | |
845 // been directed to do so. | |
846 if (fMissingExpectationsPath) { | |
847 SkString path = this->makeBitmapFilename(fMissingExpectationsPat
h, shortName, | |
848 configName, renderModeD
escriptor, | |
849 actualBitmapAndDigest.f
Digest); | |
850 WriteBitmap(path, actualBitmapAndDigest.fBitmap); | |
851 } | |
852 | |
853 } else if (!expectations.match(actualBitmapAndDigest.fDigest)) { | |
854 addToJsonSummary = true; | |
855 // The error mode we record depends on whether this was running | |
856 // in a non-standard renderMode. | |
857 if ('\0' == *renderModeDescriptor) { | |
858 errors.add(kExpectationsMismatch_ErrorType); | |
859 } else { | |
860 errors.add(kRenderModeMismatch_ErrorType); | |
861 } | |
862 | |
863 // Write out the "actuals" for any mismatches, if we have | |
864 // been directed to do so. | |
865 if (fMismatchPath) { | |
866 SkString path = this->makeBitmapFilename(fMismatchPath, shortNam
e, configName, | |
867 renderModeDescriptor, | |
868 actualBitmapAndDigest.f
Digest); | |
869 WriteBitmap(path, actualBitmapAndDigest.fBitmap); | |
870 } | |
871 | |
872 // If we have access to a single expected bitmap, log more | |
873 // detail about the mismatch. | |
874 const SkBitmap *expectedBitmapPtr = expectations.asBitmap(); | |
875 if (expectedBitmapPtr) { | |
876 report_bitmap_diffs(*expectedBitmapPtr, actualBitmapAndDigest.fB
itmap, | |
877 completeName); | |
878 } | |
879 } | |
880 | |
881 if (addToJsonSummary) { | |
882 add_actual_results_to_json_summary(completeName, actualBitmapAndDige
st.fDigest, errors, | |
883 expectations.ignoreFailure()); | |
884 add_expected_results_to_json_summary(completeName, expectations); | |
885 } | |
886 | |
887 return errors; | |
888 } | |
889 | |
890 /** | |
891 * Add this result to the appropriate JSON collection of actual results (but
just ONE), | |
892 * depending on errors encountered. | |
893 */ | |
894 void add_actual_results_to_json_summary(const char testName[], | |
895 const GmResultDigest &actualResultDi
gest, | |
896 ErrorCombination errors, | |
897 bool ignoreFailure) { | |
898 Json::Value jsonActualResults = actualResultDigest.asJsonTypeValuePair()
; | |
899 Json::Value *resultCollection = NULL; | |
900 | |
901 if (errors.isEmpty()) { | |
902 resultCollection = &this->fJsonActualResults_Succeeded; | |
903 } else if (errors.includes(kRenderModeMismatch_ErrorType)) { | |
904 resultCollection = &this->fJsonActualResults_Failed; | |
905 } else if (errors.includes(kExpectationsMismatch_ErrorType)) { | |
906 if (ignoreFailure) { | |
907 resultCollection = &this->fJsonActualResults_FailureIgnored; | |
908 } else { | |
909 resultCollection = &this->fJsonActualResults_Failed; | |
910 } | |
911 } else if (errors.includes(kMissingExpectations_ErrorType)) { | |
912 // TODO: What about the case where there IS an expected | |
913 // image hash digest, but that gm test doesn't actually | |
914 // run? For now, those cases will always be ignored, | |
915 // because gm only looks at expectations that correspond | |
916 // to gm tests that were actually run. | |
917 // | |
918 // Once we have the ability to express expectations as a | |
919 // JSON file, we should fix this (and add a test case for | |
920 // which an expectation is given but the test is never | |
921 // run). | |
922 resultCollection = &this->fJsonActualResults_NoComparison; | |
923 } | |
924 | |
925 // If none of the above cases match, we don't add it to ANY tally of act
ual results. | |
926 if (resultCollection) { | |
927 (*resultCollection)[testName] = jsonActualResults; | |
928 } | |
929 } | |
930 | |
931 /** | |
932 * Add this test to the JSON collection of expected results. | |
933 */ | |
934 void add_expected_results_to_json_summary(const char testName[], | |
935 Expectations expectations) { | |
936 this->fJsonExpectedResults[testName] = expectations.asJsonValue(); | |
937 } | |
938 | |
939 /** | |
940 * Compare actualBitmap to expectations stored in this->fExpectationsSource. | |
941 * | |
942 * @param gm which test generated the actualBitmap | |
943 * @param gRec | |
944 * @param configName The config name to look for in the expectation file. | |
945 * @param actualBitmapAndDigest ptr to bitmap generated by this run, or NULL | |
946 * if we don't have a usable bitmap representation | |
947 */ | |
948 ErrorCombination compareTestResultsToStoredExpectations( | |
949 GM* gm, const ConfigData& gRec, const char* configName, | |
950 const BitmapAndDigest* actualBitmapAndDigest) { | |
951 ErrorCombination errors; | |
952 | |
953 if (NULL == actualBitmapAndDigest) { | |
954 // Note that we intentionally skipped validating the results for | |
955 // this test, because we don't know how to generate an SkBitmap | |
956 // version of the output. | |
957 errors.add(ErrorCombination(kIntentionallySkipped_ErrorType)); | |
958 } else if (!(gRec.fFlags & kWrite_ConfigFlag)) { | |
959 // We don't record the results for this test or compare them | |
960 // against any expectations, because the output image isn't | |
961 // meaningful. | |
962 // See https://code.google.com/p/skia/issues/detail?id=1410 ('some | |
963 // GM result images not available for download from Google Storage') | |
964 errors.add(ErrorCombination(kIntentionallySkipped_ErrorType)); | |
965 } else { | |
966 ExpectationsSource *expectationsSource = this->fExpectationsSource.g
et(); | |
967 SkString nameWithExtension = make_shortname_plus_config(gm->getName(
), configName); | |
968 nameWithExtension.append("."); | |
969 nameWithExtension.append(kPNG_FileExtension); | |
970 | |
971 if (expectationsSource && (gRec.fFlags & kRead_ConfigFlag)) { | |
972 /* | |
973 * Get the expected results for this test, as one or more allowe
d | |
974 * hash digests. The current implementation of expectationsSourc
e | |
975 * get this by computing the hash digest of a single PNG file on
disk. | |
976 * | |
977 * TODO(epoger): This relies on the fact that | |
978 * force_all_opaque() was called on the bitmap before it | |
979 * was written to disk as a PNG in the first place. If | |
980 * not, the hash digest returned here may not match the | |
981 * hash digest of actualBitmap, which *has* been run through | |
982 * force_all_opaque(). | |
983 * See comments above complete_bitmap() for more detail. | |
984 */ | |
985 Expectations expectations = expectationsSource->get(nameWithExte
nsion.c_str()); | |
986 if (this->shouldIgnoreTest(gm->getName())) { | |
987 expectations.setIgnoreFailure(true); | |
988 } | |
989 errors.add(compare_to_expectations(expectations, *actualBitmapAn
dDigest, | |
990 gm->getName(), configName, ""
, true)); | |
991 } else { | |
992 // If we are running without expectations, we still want to | |
993 // record the actual results. | |
994 add_actual_results_to_json_summary(nameWithExtension.c_str(), | |
995 actualBitmapAndDigest->fDiges
t, | |
996 ErrorCombination(kMissingExpe
ctations_ErrorType), | |
997 false); | |
998 errors.add(ErrorCombination(kMissingExpectations_ErrorType)); | |
999 } | |
1000 } | |
1001 return errors; | |
1002 } | |
1003 | |
1004 /** | |
1005 * Compare actualBitmap to referenceBitmap. | |
1006 * | |
1007 * @param shortName test name, e.g. "selftest1" | |
1008 * @param configName configuration name, e.g. "8888" | |
1009 * @param renderModeDescriptor | |
1010 * @param actualBitmap actual bitmap generated by this run | |
1011 * @param referenceBitmap bitmap we expected to be generated | |
1012 */ | |
1013 ErrorCombination compare_test_results_to_reference_bitmap( | |
1014 const char *shortName, const char *configName, const char *renderModeDes
criptor, | |
1015 SkBitmap& actualBitmap, const SkBitmap* referenceBitmap) { | |
1016 | |
1017 SkASSERT(referenceBitmap); | |
1018 Expectations expectations(*referenceBitmap); | |
1019 BitmapAndDigest actualBitmapAndDigest(actualBitmap); | |
1020 | |
1021 // TODO: Eliminate RecordTestResults from here. | |
1022 // Results recording code for the test_drawing path has been refactored
so that | |
1023 // RecordTestResults is only called once, at the topmost level. However,
the | |
1024 // other paths have not yet been refactored, and RecordTestResults has b
een added | |
1025 // here to maintain proper behavior for calls not coming from the test_d
rawing path. | |
1026 ErrorCombination errors; | |
1027 errors.add(compare_to_expectations(expectations, actualBitmapAndDigest,
shortName, | |
1028 configName, renderModeDescriptor, fal
se)); | |
1029 SkString shortNamePlusConfig = make_shortname_plus_config(shortName, con
figName); | |
1030 RecordTestResults(errors, shortNamePlusConfig, renderModeDescriptor); | |
1031 | |
1032 return errors; | |
1033 } | |
1034 | |
1035 static SkPicture* generate_new_picture(GM* gm, BbhType bbhType, uint32_t rec
ordFlags, | |
1036 SkScalar scale = SK_Scalar1) { | |
1037 SkScalar width = SkScalarMul(SkIntToScalar(gm->getISize().width()), scal
e); | |
1038 SkScalar height = SkScalarMul(SkIntToScalar(gm->getISize().height()), sc
ale); | |
1039 | |
1040 SkAutoTDelete<SkBBHFactory> factory; | |
1041 if (kRTree_BbhType == bbhType) { | |
1042 factory.reset(SkNEW(SkRTreeFactory)); | |
1043 } | |
1044 SkPictureRecorder recorder; | |
1045 SkCanvas* cv = recorder.beginRecording(width, height, factory.get(), rec
ordFlags); | |
1046 cv->scale(scale, scale); | |
1047 invokeGM(gm, cv, false, false); | |
1048 return recorder.endRecording(); | |
1049 } | |
1050 | |
1051 static SkPicture* stream_to_new_picture(const SkPicture& src) { | |
1052 SkDynamicMemoryWStream storage; | |
1053 src.serialize(&storage); | |
1054 SkAutoTUnref<SkStreamAsset> pictReadback(storage.detachAsStream()); | |
1055 SkPicture* retval = SkPicture::CreateFromStream(pictReadback, | |
1056 &SkImageDecoder::DecodeM
emory); | |
1057 return retval; | |
1058 } | |
1059 | |
1060 // Test: draw into a bitmap or pdf. | |
1061 // Depending on flags, possibly compare to an expected image. | |
1062 // If writePath is not NULL, also write images (or documents) to the specifi
ed path. | |
1063 ErrorCombination test_drawing(GM* gm, const ConfigData& gRec, | |
1064 const SkTDArray<const PDFRasterizerData*> &pdf
Rasterizers, | |
1065 const char writePath [], | |
1066 GrSurface* gpuTarget, | |
1067 SkBitmap* bitmap) { | |
1068 ErrorCombination errors; | |
1069 SkDynamicMemoryWStream document; | |
1070 | |
1071 if (gRec.fBackend == kRaster_Backend || | |
1072 gRec.fBackend == kGPU_Backend) { | |
1073 // Early exit if we can't generate the image. | |
1074 errors.add(generate_image(gm, gRec, gpuTarget, bitmap, false)); | |
1075 if (!errors.isEmpty()) { | |
1076 // TODO: Add a test to exercise what the stdout and | |
1077 // JSON look like if we get an "early error" while | |
1078 // trying to generate the image. | |
1079 return errors; | |
1080 } | |
1081 | |
1082 errors.add(this->writeBitmap(gm, gRec, gRec.fName, writePath, *bitma
p)); | |
1083 } else if (gRec.fBackend == kPDF_Backend) { | |
1084 if (!generate_pdf(gm, document)) { | |
1085 errors.add(kGeneratePdfFailed_ErrorType); | |
1086 } else { | |
1087 SkAutoTUnref<SkStreamAsset> documentStream(document.detachAsStre
am()); | |
1088 if (writePath && (gRec.fFlags & kWrite_ConfigFlag)) { | |
1089 SkString path = make_filename(writePath, gm->getName(), gRec
.fName, "", "pdf"); | |
1090 errors.add(write_document(path, documentStream)); | |
1091 } | |
1092 | |
1093 if (!(gm->getFlags() & GM::kSkipPDFRasterization_Flag)) { | |
1094 for (int i = 0; i < pdfRasterizers.count(); i++) { | |
1095 SkBitmap pdfBitmap; | |
1096 documentStream->rewind(); | |
1097 bool success = (*pdfRasterizers[i]->fRasterizerFunction)
( | |
1098 documentStream.get(), &pdfBitmap); | |
1099 if (!success) { | |
1100 SkDebugf("FAILED to render PDF for %s using renderer
%s\n", | |
1101 gm->getName(), | |
1102 pdfRasterizers[i]->fName); | |
1103 continue; | |
1104 } | |
1105 | |
1106 SkString configName(gRec.fName); | |
1107 configName.append("-"); | |
1108 configName.append(pdfRasterizers[i]->fName); | |
1109 | |
1110 errors.add(this->writeBitmap(gm, gRec, configName.c_str(
), | |
1111 writePath, pdfBitmap)); | |
1112 } | |
1113 } else { | |
1114 errors.add(kIntentionallySkipped_ErrorType); | |
1115 } | |
1116 } | |
1117 } else if (gRec.fBackend == kXPS_Backend) { | |
1118 generate_xps(gm, document); | |
1119 SkAutoTUnref<SkStreamAsset> documentStream(document.detachAsStream()
); | |
1120 | |
1121 errors.add(this->compareTestResultsToStoredExpectations( | |
1122 gm, gRec, gRec.fName, NULL)); | |
1123 | |
1124 if (writePath && (gRec.fFlags & kWrite_ConfigFlag)) { | |
1125 SkString path = make_filename(writePath, gm->getName(), gRec.fNa
me, "", "xps"); | |
1126 errors.add(write_document(path, documentStream)); | |
1127 } | |
1128 } else { | |
1129 SkASSERT(false); | |
1130 } | |
1131 return errors; | |
1132 } | |
1133 | |
1134 ErrorCombination test_deferred_drawing(GM* gm, | |
1135 const ConfigData& gRec, | |
1136 const SkBitmap& referenceBitmap, | |
1137 GrSurface* gpuTarget) { | |
1138 if (gRec.fBackend == kRaster_Backend || | |
1139 gRec.fBackend == kGPU_Backend) { | |
1140 const char renderModeDescriptor[] = "-deferred"; | |
1141 SkBitmap bitmap; | |
1142 // Early exit if we can't generate the image, but this is | |
1143 // expected in some cases, so don't report a test failure. | |
1144 ErrorCombination errors = generate_image(gm, gRec, gpuTarget, &bitma
p, true); | |
1145 // TODO(epoger): This logic is the opposite of what is | |
1146 // described above... if we succeeded in generating the | |
1147 // -deferred image, we exit early! We should fix this | |
1148 // ASAP, because it is hiding -deferred errors... but for | |
1149 // now, I'm leaving the logic as it is so that the | |
1150 // refactoring change | |
1151 // https://codereview.chromium.org/12992003/ is unblocked. | |
1152 // | |
1153 // Filed as https://code.google.com/p/skia/issues/detail?id=1180 | |
1154 // ('image-surface gm test is failing in "deferred" mode, | |
1155 // and gm is not reporting the failure') | |
1156 if (errors.isEmpty()) { | |
1157 // TODO(epoger): Report this as a new ErrorType, | |
1158 // something like kImageGeneration_ErrorType? | |
1159 return kEmpty_ErrorCombination; | |
1160 } | |
1161 return compare_test_results_to_reference_bitmap( | |
1162 gm->getName(), gRec.fName, renderModeDescriptor, bitmap, &refere
nceBitmap); | |
1163 } | |
1164 return kEmpty_ErrorCombination; | |
1165 } | |
1166 | |
1167 static SkSurface* CreateSurface(const ConfigData& config, | |
1168 const SkISize& size, | |
1169 GrSurface* gpuTarget) { | |
1170 if (config.fBackend == kRaster_Backend) { | |
1171 SkImageInfo ii = SkImageInfo::Make(size.width(), size.height(), | |
1172 config.fColorType, kPremul_SkAlph
aType); | |
1173 | |
1174 return SkSurface::NewRaster(ii); | |
1175 } | |
1176 #if SK_SUPPORT_GPU | |
1177 else { | |
1178 uint32_t flags = (config.fFlags & kDFText_ConfigFlag) ? | |
1179 SkSurfaceProps::kUseDistanceFieldFonts_Flag : 0; | |
1180 SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType
); | |
1181 return SkSurface::NewRenderTargetDirect(gpuTarget->asRenderTarget(),
&props); | |
1182 } | |
1183 #endif | |
1184 | |
1185 return NULL; | |
1186 } | |
1187 | |
1188 ErrorCombination writeBitmap(GM* gm, | |
1189 const ConfigData& config, | |
1190 const char* configName, | |
1191 const char* writePath, | |
1192 const SkBitmap& bitmap) { | |
1193 ErrorCombination errors; | |
1194 | |
1195 BitmapAndDigest bitmapAndDigest(bitmap); | |
1196 errors.add(this->compareTestResultsToStoredExpectations(gm, config, | |
1197 configName, &bit
mapAndDigest)); | |
1198 | |
1199 if (writePath && (config.fFlags & kWrite_ConfigFlag)) { | |
1200 SkString path; | |
1201 | |
1202 path = this->makeBitmapFilename(writePath, gm->getName(), configName
, | |
1203 "", bitmapAndDigest.fDigest); | |
1204 errors.add(WriteBitmap(path, bitmapAndDigest.fBitmap)); | |
1205 } | |
1206 | |
1207 return errors; | |
1208 } | |
1209 | |
1210 ErrorCombination testMPDDrawing(GM* gm, | |
1211 const ConfigData& config, | |
1212 const char* writePath, | |
1213 GrSurface* gpuTarget, | |
1214 const SkBitmap& referenceBitmap) { | |
1215 SkASSERT(kRaster_Backend == config.fBackend || kGPU_Backend == config.fB
ackend); | |
1216 | |
1217 static const uint32_t kMPDFlags = SkPictureRecorder::kComputeSaveLayerIn
fo_RecordFlag; | |
1218 | |
1219 SkAutoTUnref<SkPicture> pict(generate_new_picture(gm, kRTree_BbhType, kM
PDFlags)); | |
1220 | |
1221 SkAutoTUnref<SkSurface> surf(CreateSurface(config, gm->getISize(), gpuTa
rget)); | |
1222 | |
1223 DrawPictureToSurface(surf, pict, SK_Scalar1, false, true); | |
1224 | |
1225 SkBitmap bitmap; | |
1226 | |
1227 setup_bitmap(config, gm->getISize(), &bitmap); | |
1228 | |
1229 surf->readPixels(bitmap.info(), bitmap.getPixels(), bitmap.rowBytes(), 0
, 0); | |
1230 complete_bitmap(&bitmap); | |
1231 | |
1232 SkString configName(config.fName); | |
1233 configName.append("-mpd"); | |
1234 | |
1235 return this->writeBitmap(gm, config, configName.c_str(), writePath, bitm
ap); | |
1236 } | |
1237 | |
1238 ErrorCombination test_pipe_playback(GM* gm, const ConfigData& gRec, | |
1239 const SkBitmap& referenceBitmap, bool si
mulateFailure) { | |
1240 const SkString shortNamePlusConfig = make_shortname_plus_config(gm->getN
ame(), | |
1241 gRec.fNa
me); | |
1242 ErrorCombination errors; | |
1243 for (size_t i = 0; i < SK_ARRAY_COUNT(gPipeWritingFlagCombos); ++i) { | |
1244 SkString renderModeDescriptor("-pipe"); | |
1245 renderModeDescriptor.append(gPipeWritingFlagCombos[i].name); | |
1246 | |
1247 if (gm->getFlags() & GM::kSkipPipe_Flag | |
1248 || (gPipeWritingFlagCombos[i].flags == SkGPipeWriter::kCrossProc
ess_Flag | |
1249 && gm->getFlags() & GM::kSkipPipeCrossProcess_Flag)) { | |
1250 RecordTestResults(kIntentionallySkipped_ErrorType, shortNamePlus
Config, | |
1251 renderModeDescriptor.c_str()); | |
1252 errors.add(kIntentionallySkipped_ErrorType); | |
1253 } else { | |
1254 SkBitmap bitmap; | |
1255 SkISize size = gm->getISize(); | |
1256 setup_bitmap(gRec, size, &bitmap); | |
1257 SkCanvas canvas(bitmap); | |
1258 InstallFilter(&canvas); | |
1259 // Pass a decoding function so the factory GM (which has an SkBi
tmap | |
1260 // with encoded data) will not fail playback. | |
1261 PipeController pipeController(&canvas, &SkImageDecoder::DecodeMe
mory); | |
1262 SkGPipeWriter writer; | |
1263 SkCanvas* pipeCanvas = writer.startRecording(&pipeController, | |
1264 gPipeWritingFlagCom
bos[i].flags, | |
1265 size.width(), size.
height()); | |
1266 if (!simulateFailure) { | |
1267 invokeGM(gm, pipeCanvas, false, false); | |
1268 } | |
1269 complete_bitmap(&bitmap); | |
1270 writer.endRecording(); | |
1271 errors.add(compare_test_results_to_reference_bitmap( | |
1272 gm->getName(), gRec.fName, renderModeDescriptor.c_str(), bit
map, | |
1273 &referenceBitmap)); | |
1274 if (!errors.isEmpty()) { | |
1275 break; | |
1276 } | |
1277 } | |
1278 } | |
1279 return errors; | |
1280 } | |
1281 | |
1282 ErrorCombination test_tiled_pipe_playback(GM* gm, const ConfigData& gRec, | |
1283 const SkBitmap& referenceBitmap) { | |
1284 const SkString shortNamePlusConfig = make_shortname_plus_config(gm->getN
ame(), | |
1285 gRec.fNa
me); | |
1286 ErrorCombination errors; | |
1287 for (size_t i = 0; i < SK_ARRAY_COUNT(gPipeWritingFlagCombos); ++i) { | |
1288 SkString renderModeDescriptor("-tiled pipe"); | |
1289 renderModeDescriptor.append(gPipeWritingFlagCombos[i].name); | |
1290 | |
1291 if ((gm->getFlags() & GM::kSkipPipe_Flag) || | |
1292 (gm->getFlags() & GM::kSkipTiled_Flag)) { | |
1293 RecordTestResults(kIntentionallySkipped_ErrorType, shortNamePlus
Config, | |
1294 renderModeDescriptor.c_str()); | |
1295 errors.add(kIntentionallySkipped_ErrorType); | |
1296 } else { | |
1297 SkBitmap bitmap; | |
1298 SkISize size = gm->getISize(); | |
1299 setup_bitmap(gRec, size, &bitmap); | |
1300 SkCanvas canvas(bitmap); | |
1301 InstallFilter(&canvas); | |
1302 TiledPipeController pipeController(bitmap, &SkImageDecoder::Deco
deMemory); | |
1303 SkGPipeWriter writer; | |
1304 SkCanvas* pipeCanvas = writer.startRecording(&pipeController, | |
1305 gPipeWritingFlagCom
bos[i].flags, | |
1306 size.width(), size.
height()); | |
1307 invokeGM(gm, pipeCanvas, false, false); | |
1308 complete_bitmap(&bitmap); | |
1309 writer.endRecording(); | |
1310 errors.add(compare_test_results_to_reference_bitmap(gm->getName(
), gRec.fName, | |
1311 renderModeDe
scriptor.c_str(), | |
1312 bitmap, &ref
erenceBitmap)); | |
1313 if (!errors.isEmpty()) { | |
1314 break; | |
1315 } | |
1316 } | |
1317 } | |
1318 return errors; | |
1319 } | |
1320 | |
1321 // | |
1322 // member variables. | |
1323 // They are public for now, to allow easier setting by tool_main(). | |
1324 // | |
1325 | |
1326 bool fUseFileHierarchy, fWriteChecksumBasedFilenames; | |
1327 ErrorCombination fIgnorableErrorTypes; | |
1328 SkTArray<SkString> fIgnorableTestNames; | |
1329 | |
1330 const char* fMismatchPath; | |
1331 const char* fMissingExpectationsPath; | |
1332 | |
1333 // collection of tests that have failed with each ErrorType | |
1334 SkTArray<SkString> fFailedTests[kLast_ErrorType+1]; | |
1335 SkTArray<SkString> fTestsSkippedOnAllRenderModes; | |
1336 int fTestsRun; | |
1337 SkTDict<int> fRenderModesEncountered; | |
1338 | |
1339 // Where to read expectations (expected image hash digests, etc.) from. | |
1340 // If unset, we don't do comparisons. | |
1341 SkAutoTUnref<ExpectationsSource> fExpectationsSource; | |
1342 | |
1343 // JSON summaries that we generate as we go (just for output). | |
1344 Json::Value fJsonExpectedResults; | |
1345 Json::Value fJsonActualResults_Failed; | |
1346 Json::Value fJsonActualResults_FailureIgnored; | |
1347 Json::Value fJsonActualResults_NoComparison; | |
1348 Json::Value fJsonActualResults_Succeeded; | |
1349 }; // end of GMMain class definition | |
1350 | |
1351 #if SK_SUPPORT_GPU | |
1352 static const GLContextType kDontCare_GLContextType = GrContextFactory::kNative_G
LContextType; | |
1353 #else | |
1354 static const GLContextType kDontCare_GLContextType = 0; | |
1355 #endif | |
1356 | |
1357 static const ConfigData gRec[] = { | |
1358 { kN32_SkColorType, kRaster_Backend, kDontCare_GLContextType,
0, kRW_ConfigFlag, "8888", true }, | |
1359 { kRGB_565_SkColorType, kRaster_Backend, kDontCare_GLContextType,
0, kRW_ConfigFlag, "565", true }, | |
1360 #if SK_SUPPORT_GPU | |
1361 { kN32_SkColorType, kGPU_Backend, GrContextFactory::kNative_GLContextType
, 0, kRW_ConfigFlag, "gpu", true }, | |
1362 { kN32_SkColorType, kGPU_Backend, GrContextFactory::kNative_GLContextType
, 16, kRW_ConfigFlag, "msaa16", false}, | |
1363 { kN32_SkColorType, kGPU_Backend, GrContextFactory::kNative_GLContextType
, 4, kRW_ConfigFlag, "msaa4", false}, | |
1364 { kN32_SkColorType, kGPU_Backend, GrContextFactory::kNVPR_GLContextType,
4, kRW_ConfigFlag, "nvprmsaa4", true }, | |
1365 { kN32_SkColorType, kGPU_Backend, GrContextFactory::kNVPR_GLContextType,
16, kRW_ConfigFlag, "nvprmsaa16", false}, | |
1366 /* Not quite ready to turn on distance field text baselines */ | |
1367 { kN32_SkColorType, kGPU_Backend, GrContextFactory::kNative_GLContextType
, 0, kRWDFT_ConfigFlag, "gpudft", false }, | |
1368 /* The gpudebug context does not generate meaningful images, so don't record | |
1369 * the images it generates! We only run it to look for asserts. */ | |
1370 { kN32_SkColorType, kGPU_Backend, GrContextFactory::kDebug_GLContextType,
0, kNone_ConfigFlag, "gpudebug", kDebugOnly}, | |
1371 /* The gpunull context does the least amount of work possible and doesn't | |
1372 generate meaninful images, so don't record them!. It can be run to | |
1373 isolate the CPU-side processing expense from the GPU-side. | |
1374 */ | |
1375 { kN32_SkColorType, kGPU_Backend, GrContextFactory::kNull_GLContextType,
0, kNone_ConfigFlag, "gpunull", kDebugOnly}, | |
1376 #if SK_ANGLE | |
1377 { kN32_SkColorType, kGPU_Backend, GrContextFactory::kANGLE_GLContextType,
0, kRW_ConfigFlag, "angle", true }, | |
1378 { kN32_SkColorType, kGPU_Backend, GrContextFactory::kANGLE_GLContextType,
16, kRW_ConfigFlag, "anglemsaa16", true }, | |
1379 #endif // SK_ANGLE | |
1380 #ifdef SK_MESA | |
1381 { kN32_SkColorType, kGPU_Backend, GrContextFactory::kMESA_GLContextType,
0, kRW_ConfigFlag, "mesa", true }, | |
1382 #endif // SK_MESA | |
1383 #endif // SK_SUPPORT_GPU | |
1384 #ifdef SK_SUPPORT_XPS | |
1385 /* At present we have no way of comparing XPS files (either natively or by c
onverting to PNG). */ | |
1386 { kN32_SkColorType, kXPS_Backend, kDontCare_GLContextType,
0, kWrite_ConfigFlag, "xps", true }, | |
1387 #endif // SK_SUPPORT_XPS | |
1388 #ifdef SK_SUPPORT_PDF | |
1389 { kN32_SkColorType, kPDF_Backend, kDontCare_GLContextType,
0, kRW_ConfigFlag, "pdf", true }, | |
1390 #endif // SK_SUPPORT_PDF | |
1391 }; | |
1392 | |
1393 static bool SkNoRasterizePDF(SkStream*, SkBitmap*) { return false; } | |
1394 | |
1395 static const PDFRasterizerData kPDFRasterizers[] = { | |
1396 #ifdef SK_BUILD_FOR_MAC | |
1397 { &SkPDFDocumentToBitmap, "mac", true }, | |
1398 #endif | |
1399 #ifdef SK_BUILD_POPPLER | |
1400 { &SkPopplerRasterizePDF, "poppler", true }, | |
1401 #endif | |
1402 #ifdef SK_BUILD_NATIVE_PDF_RENDERER | |
1403 { &SkNativeRasterizePDF, "native", true }, | |
1404 #endif // SK_BUILD_NATIVE_PDF_RENDERER | |
1405 // The following exists so that this array is never zero length. | |
1406 { &SkNoRasterizePDF, "none", false}, | |
1407 }; | |
1408 | |
1409 static const char kDefaultsConfigStr[] = "defaults"; | |
1410 static const char kExcludeConfigChar = '~'; | |
1411 #if SK_SUPPORT_GPU | |
1412 static const char kGpuAPINameGL[] = "gl"; | |
1413 static const char kGpuAPINameGLES[] = "gles"; | |
1414 #endif | |
1415 | |
1416 static SkString configUsage() { | |
1417 SkString result; | |
1418 result.appendf("Space delimited list of which configs to run. Possible optio
ns: ["); | |
1419 for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); ++i) { | |
1420 SkASSERT(gRec[i].fName != kDefaultsConfigStr); | |
1421 if (i > 0) { | |
1422 result.append("|"); | |
1423 } | |
1424 result.appendf("%s", gRec[i].fName); | |
1425 } | |
1426 result.append("]\n"); | |
1427 result.appendf("The default value is: \""); | |
1428 SkString firstDefault; | |
1429 SkString allButFirstDefaults; | |
1430 SkString nonDefault; | |
1431 for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); ++i) { | |
1432 if (gRec[i].fRunByDefault) { | |
1433 if (i > 0) { | |
1434 result.append(" "); | |
1435 } | |
1436 result.append(gRec[i].fName); | |
1437 if (firstDefault.isEmpty()) { | |
1438 firstDefault = gRec[i].fName; | |
1439 } else { | |
1440 if (!allButFirstDefaults.isEmpty()) { | |
1441 allButFirstDefaults.append(" "); | |
1442 } | |
1443 allButFirstDefaults.append(gRec[i].fName); | |
1444 } | |
1445 } else { | |
1446 nonDefault = gRec[i].fName; | |
1447 } | |
1448 } | |
1449 result.append("\"\n"); | |
1450 result.appendf("\"%s\" evaluates to the default set of configs.\n", kDefault
sConfigStr); | |
1451 result.appendf("Prepending \"%c\" on a config name excludes it from the set
of configs to run.\n" | |
1452 "Exclusions always override inclusions regardless of order.\n
", | |
1453 kExcludeConfigChar); | |
1454 result.appendf("E.g. \"--config %s %c%s %s\" will run these configs:\n\t%s %
s", | |
1455 kDefaultsConfigStr, | |
1456 kExcludeConfigChar, | |
1457 firstDefault.c_str(), | |
1458 nonDefault.c_str(), | |
1459 allButFirstDefaults.c_str(), | |
1460 nonDefault.c_str()); | |
1461 return result; | |
1462 } | |
1463 | |
1464 static SkString pdfRasterizerUsage() { | |
1465 SkString result; | |
1466 result.appendf("Space delimited list of which PDF rasterizers to run. Possib
le options: ["); | |
1467 // For this (and further) loops through kPDFRasterizers, there is a typecast
to int to avoid | |
1468 // the compiler giving an "comparison of unsigned expression < 0 is always f
alse" warning | |
1469 // and turning it into a build-breaking error. | |
1470 for (int i = 0; i < (int)SK_ARRAY_COUNT(kPDFRasterizers); ++i) { | |
1471 if (i > 0) { | |
1472 result.append(" "); | |
1473 } | |
1474 result.append(kPDFRasterizers[i].fName); | |
1475 } | |
1476 result.append("]\n"); | |
1477 result.append("The default value is: \""); | |
1478 for (int i = 0; i < (int)SK_ARRAY_COUNT(kPDFRasterizers); ++i) { | |
1479 if (kPDFRasterizers[i].fRunByDefault) { | |
1480 if (i > 0) { | |
1481 result.append(" "); | |
1482 } | |
1483 result.append(kPDFRasterizers[i].fName); | |
1484 } | |
1485 } | |
1486 result.append("\""); | |
1487 return result; | |
1488 } | |
1489 | |
1490 // Macro magic to convert a numeric preprocessor token into a string. | |
1491 // Adapted from http://stackoverflow.com/questions/240353/convert-a-preprocessor
-token-to-a-string | |
1492 // This should probably be moved into one of our common headers... | |
1493 #define TOSTRING_INTERNAL(x) #x | |
1494 #define TOSTRING(x) TOSTRING_INTERNAL(x) | |
1495 | |
1496 // Alphabetized ignoring "no" prefix ("readPath", "noreplay", "resourcePath"). | |
1497 DEFINE_string(config, "", configUsage().c_str()); | |
1498 DEFINE_bool(cpu, true, "Allows non-GPU configs to be run. Applied after --config
."); | |
1499 DEFINE_string(pdfRasterizers, "default", pdfRasterizerUsage().c_str()); | |
1500 DEFINE_bool(deferred, false, "Exercise the deferred rendering test pass."); | |
1501 DEFINE_bool(mpd, false, "Exercise MultiPictureDraw."); | |
1502 | |
1503 DEFINE_bool(dryRun, false, "Don't actually run the tests, just print what would
have been done."); | |
1504 DEFINE_string(excludeConfig, "", "Space delimited list of configs to skip."); | |
1505 DEFINE_bool(forceBWtext, false, "Disable text anti-aliasing."); | |
1506 #if SK_SUPPORT_GPU | |
1507 DEFINE_string(gpuAPI, "", "Force use of specific gpu API. Using \"gl\" " | |
1508 "forces OpenGL API. Using \"gles\" forces OpenGL ES API. " | |
1509 "Defaults to empty string, which selects the API native to the " | |
1510 "system."); | |
1511 DEFINE_string(gpuCacheSize, "", "<bytes> <count>: Limit the gpu cache to byte si
ze or " | |
1512 "object count. " TOSTRING(DEFAULT_CACHE_VALUE) " for either value
means " | |
1513 "use the default. 0 for either disables the cache."); | |
1514 DEFINE_bool(gpu, true, "Allows GPU configs to be run. Applied after --config."); | |
1515 DEFINE_bool(gpuCompressAlphaMasks, false, "Compress masks generated from falling
back to " | |
1516 "software path rendering."); | |
1517 #endif | |
1518 DEFINE_bool(hierarchy, false, "Whether to use multilevel directory structure " | |
1519 "when reading/writing files."); | |
1520 DEFINE_string(ignoreErrorTypes, kDefaultIgnorableErrorTypes.asString(" ").c_str(
), | |
1521 "Space-separated list of ErrorTypes that should be ignored. If any
*other* error " | |
1522 "types are encountered, the tool will exit with a nonzero return v
alue."); | |
1523 DEFINE_string(ignoreFailuresFile, "", "Path to file containing a list of tests f
or which we " | |
1524 "should ignore failures.\n" | |
1525 "The file should list one test per line, except for comment lines
starting with #"); | |
1526 DEFINE_bool2(leaks, l, false, "show leaked ref cnt'd objects."); | |
1527 DEFINE_string(match, "", "[~][^]substring[$] [...] of test name to run.\n" | |
1528 "Multiple matches may be separated by spaces.\n" | |
1529 "~ causes a matching test to always be skipped\n" | |
1530 "^ requires the start of the test to match\n" | |
1531 "$ requires the end of the test to match\n" | |
1532 "^ and $ requires an exact match\n" | |
1533 "If a test does not match any list entry,\n" | |
1534 "it is skipped unless some list entry starts with ~"); | |
1535 DEFINE_string(missingExpectationsPath, "", "Write images for tests without expec
tations " | |
1536 "into this directory."); | |
1537 DEFINE_string(mismatchPath, "", "Write images for tests that failed due to " | |
1538 "pixel mismatches into this directory."); | |
1539 DEFINE_string(modulo, "", "[--modulo <remainder> <divisor>]: only run tests for
which " | |
1540 "testIndex %% divisor == remainder."); | |
1541 DEFINE_bool(pipe, false, "Exercise the SkGPipe replay test pass."); | |
1542 DEFINE_string2(readPath, r, "", "Read reference images from this dir, and report
" | |
1543 "any differences between those and the newly generated ones."); | |
1544 DEFINE_bool(replay, false, "Exercise the SkPicture replay test pass."); | |
1545 | |
1546 #ifdef SK_BUILD_FOR_ANDROID | |
1547 DEFINE_bool(resetGpuContext, true, "Reset the GrContext prior to running each GM
."); | |
1548 #else | |
1549 DEFINE_bool(resetGpuContext, false, "Reset the GrContext prior to running each G
M."); | |
1550 #endif | |
1551 | |
1552 DEFINE_bool(rtree, false, "Exercise the R-Tree variant of SkPicture test pass.")
; | |
1553 DEFINE_bool(serialize, false, "Exercise the SkPicture serialization & deserializ
ation test pass."); | |
1554 DEFINE_bool(simulatePipePlaybackFailure, false, "Simulate a rendering failure in
pipe mode only."); | |
1555 DEFINE_bool(tiledPipe, false, "Exercise tiled SkGPipe replay."); | |
1556 DEFINE_bool(tileGrid, false, "Exercise the tile grid variant of SkPicture."); | |
1557 DEFINE_string(tileGridReplayScales, "", "Space separated list of floating-point
scale " | |
1558 "factors to be used for tileGrid playback testing. Default value:
1.0"); | |
1559 DEFINE_bool2(verbose, v, false, "Give more detail (e.g. list all GMs run, more i
nfo about " | |
1560 "each test)."); | |
1561 DEFINE_bool(writeChecksumBasedFilenames, false, "When writing out actual images,
use checksum-" | |
1562 "based filenames, as rebaseline.py will use when downloading them fr
om Google Storage"); | |
1563 DEFINE_string(writeJsonSummaryPath, "", "Write a JSON-formatted result summary t
o this file."); | |
1564 DEFINE_string2(writePath, w, "", "Write rendered images into this directory."); | |
1565 DEFINE_string2(writePicturePath, p, "", "Write .skp files into this directory.")
; | |
1566 DEFINE_int32(pdfJpegQuality, -1, "Encodes images in JPEG at quality level N, " | |
1567 "which can be in range 0-100). N = -1 will disable JPEG compression
. " | |
1568 "Default is N = 100, maximum quality."); | |
1569 // TODO(edisonn): pass a matrix instead of forcePerspectiveMatrix | |
1570 // Either the 9 numbers defining the matrix | |
1571 // or probably more readable would be to replace it with a set of a few predicat
es | |
1572 // Like --prerotate 100 200 10 --posttranslate 10, 10 | |
1573 // Probably define spacial names like centerx, centery, top, bottom, left, right | |
1574 // then we can write something reabable like --rotate centerx centery 90 | |
1575 DEFINE_bool(forcePerspectiveMatrix, false, "Force a perspective matrix."); | |
1576 DEFINE_bool(useDocumentInsteadOfDevice, false, "Use SkDocument::CreateFoo instea
d of SkFooDevice."); | |
1577 DEFINE_int32(pdfRasterDpi, 72, "Scale at which at which the non suported " | |
1578 "features in PDF are rasterized. Must be be in range 0-10000. " | |
1579 "Default is 72. N = 0 will disable rasterizing features like " | |
1580 "text shadows or perspective bitmaps."); | |
1581 static SkData* encode_to_dct_data(size_t*, const SkBitmap& bitmap) { | |
1582 // Filter output of warnings that JPEG is not available for the image. | |
1583 if (bitmap.width() >= 65500 || bitmap.height() >= 65500) return NULL; | |
1584 if (FLAGS_pdfJpegQuality == -1) return NULL; | |
1585 | |
1586 SkBitmap bm = bitmap; | |
1587 #if defined(SK_BUILD_FOR_MAC) | |
1588 // Workaround bug #1043 where bitmaps with referenced pixels cause | |
1589 // CGImageDestinationFinalize to crash | |
1590 SkBitmap copy; | |
1591 bitmap.deepCopyTo(©); | |
1592 bm = copy; | |
1593 #endif | |
1594 | |
1595 SkPixelRef* pr = bm.pixelRef(); | |
1596 if (pr != NULL) { | |
1597 SkData* data = pr->refEncodedData(); | |
1598 if (data != NULL) { | |
1599 return data; | |
1600 } | |
1601 } | |
1602 | |
1603 return SkImageEncoder::EncodeData(bm, | |
1604 SkImageEncoder::kJPEG_Type, | |
1605 FLAGS_pdfJpegQuality); | |
1606 } | |
1607 | |
1608 static int findConfig(const char config[]) { | |
1609 for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); i++) { | |
1610 if (!strcmp(config, gRec[i].fName)) { | |
1611 return (int) i; | |
1612 } | |
1613 } | |
1614 return -1; | |
1615 } | |
1616 | |
1617 static const PDFRasterizerData* findPDFRasterizer(const char rasterizer[]) { | |
1618 for (int i = 0; i < (int)SK_ARRAY_COUNT(kPDFRasterizers); i++) { | |
1619 if (!strcmp(rasterizer, kPDFRasterizers[i].fName)) { | |
1620 return &kPDFRasterizers[i]; | |
1621 } | |
1622 } | |
1623 return NULL; | |
1624 } | |
1625 | |
1626 template <typename T> void appendUnique(SkTDArray<T>* array, const T& value) { | |
1627 int index = array->find(value); | |
1628 if (index < 0) { | |
1629 *array->append() = value; | |
1630 } | |
1631 } | |
1632 | |
1633 /** | |
1634 * Run this test in a number of different drawing modes (pipe, | |
1635 * deferred, tiled, etc.), confirming that the resulting bitmaps all | |
1636 * *exactly* match comparisonBitmap. | |
1637 * | |
1638 * Returns all errors encountered while doing so. | |
1639 */ | |
1640 ErrorCombination run_multiple_modes(GMMain &gmmain, GM *gm, | |
1641 const ConfigData &compareConfig, GrSurface*
gpuTarget, | |
1642 const SkBitmap &comparisonBitmap, | |
1643 const SkTDArray<SkScalar> &tileGridReplaySca
les); | |
1644 ErrorCombination run_multiple_modes(GMMain &gmmain, GM *gm, | |
1645 const ConfigData &compareConfig, GrSurface*
gpuTarget, | |
1646 const SkBitmap &comparisonBitmap, | |
1647 const SkTDArray<SkScalar> &tileGridReplaySca
les) { | |
1648 ErrorCombination errorsForAllModes; | |
1649 uint32_t gmFlags = gm->getFlags(); | |
1650 const SkString shortNamePlusConfig = gmmain.make_shortname_plus_config(gm->g
etName(), | |
1651 compa
reConfig.fName); | |
1652 | |
1653 SkPicture* pict = gmmain.generate_new_picture(gm, kNone_BbhType, 0); | |
1654 SkAutoTUnref<SkPicture> aur(pict); | |
1655 if (FLAGS_replay) { | |
1656 const char renderModeDescriptor[] = "-replay"; | |
1657 if (gmFlags & GM::kSkipPicture_Flag) { | |
1658 gmmain.RecordTestResults(kIntentionallySkipped_ErrorType, shortNameP
lusConfig, | |
1659 renderModeDescriptor); | |
1660 errorsForAllModes.add(kIntentionallySkipped_ErrorType); | |
1661 } else { | |
1662 SkBitmap bitmap; | |
1663 gmmain.generate_image_from_picture(gm, compareConfig, gpuTarget, pic
t, &bitmap); | |
1664 | |
1665 errorsForAllModes.add(gmmain.compare_test_results_to_reference_bitma
p( | |
1666 gm->getName(), compareConfig.fName, renderModeDescriptor, bitmap
, | |
1667 &comparisonBitmap)); | |
1668 } | |
1669 } | |
1670 | |
1671 if (FLAGS_serialize) { | |
1672 const char renderModeDescriptor[] = "-serialize"; | |
1673 if (gmFlags & GM::kSkipPicture_Flag) { | |
1674 gmmain.RecordTestResults(kIntentionallySkipped_ErrorType, shortNameP
lusConfig, | |
1675 renderModeDescriptor); | |
1676 errorsForAllModes.add(kIntentionallySkipped_ErrorType); | |
1677 } else { | |
1678 SkPicture* repict = gmmain.stream_to_new_picture(*pict); | |
1679 SkAutoTUnref<SkPicture> aurr(repict); | |
1680 SkBitmap bitmap; | |
1681 gmmain.generate_image_from_picture(gm, compareConfig, gpuTarget, rep
ict, &bitmap); | |
1682 errorsForAllModes.add(gmmain.compare_test_results_to_reference_bitma
p( | |
1683 gm->getName(), compareConfig.fName, renderModeDescriptor, bitmap
, | |
1684 &comparisonBitmap)); | |
1685 } | |
1686 } | |
1687 | |
1688 if ((1 == FLAGS_writePicturePath.count()) && | |
1689 !(gmFlags & GM::kSkipPicture_Flag)) { | |
1690 const char* pictureSuffix = "skp"; | |
1691 // TODO(epoger): Make sure this still works even though the | |
1692 // filename now contains the config name (it used to contain | |
1693 // just the shortName). I think this is actually an | |
1694 // *improvement*, because now runs with different configs will | |
1695 // write out their SkPictures to separate files rather than | |
1696 // overwriting each other. But we should make sure it doesn't | |
1697 // break anybody. | |
1698 SkString path = gmmain.make_filename(FLAGS_writePicturePath[0], gm->getN
ame(), | |
1699 compareConfig.fName, "", pictureSuf
fix); | |
1700 SkFILEWStream stream(path.c_str()); | |
1701 pict->serialize(&stream); | |
1702 } | |
1703 | |
1704 if (FLAGS_rtree) { | |
1705 const char renderModeDescriptor[] = "-rtree"; | |
1706 if ((gmFlags & GM::kSkipPicture_Flag) || (gmFlags & GM::kSkipTiled_Flag)
) { | |
1707 gmmain.RecordTestResults(kIntentionallySkipped_ErrorType, shortNameP
lusConfig, | |
1708 renderModeDescriptor); | |
1709 errorsForAllModes.add(kIntentionallySkipped_ErrorType); | |
1710 } else { | |
1711 SkPicture* pict = gmmain.generate_new_picture(gm, kRTree_BbhType, 0)
; | |
1712 SkAutoTUnref<SkPicture> aur(pict); | |
1713 SkBitmap bitmap; | |
1714 gmmain.generate_image_from_picture(gm, compareConfig, gpuTarget, pic
t, &bitmap); | |
1715 errorsForAllModes.add(gmmain.compare_test_results_to_reference_bitma
p( | |
1716 gm->getName(), compareConfig.fName, renderModeDescriptor, bitmap
, | |
1717 &comparisonBitmap)); | |
1718 } | |
1719 } | |
1720 | |
1721 // run the pipe centric GM steps | |
1722 if (FLAGS_pipe) { | |
1723 errorsForAllModes.add(gmmain.test_pipe_playback(gm, compareConfig, compa
risonBitmap, | |
1724 FLAGS_simulatePipePlayba
ckFailure)); | |
1725 if (FLAGS_tiledPipe) { | |
1726 errorsForAllModes.add(gmmain.test_tiled_pipe_playback(gm, compareCon
fig, | |
1727 comparisonBitm
ap)); | |
1728 } | |
1729 } | |
1730 return errorsForAllModes; | |
1731 } | |
1732 | |
1733 | |
1734 /** | |
1735 * Run this test in a number of different configs (8888, 565, PDF, | |
1736 * etc.), confirming that the resulting bitmaps match expectations | |
1737 * (which may be different for each config). | |
1738 * | |
1739 * Returns all errors encountered while doing so. | |
1740 */ | |
1741 ErrorCombination run_multiple_configs(GMMain &gmmain, GM *gm, | |
1742 const SkTDArray<size_t> &configs, | |
1743 const SkTDArray<const PDFRasterizerData*>
&pdfRasterizers, | |
1744 const SkTDArray<SkScalar> &tileGridReplayS
cales, | |
1745 GrContextFactory *grFactory, | |
1746 GrGLStandard gpuAPI); | |
1747 ErrorCombination run_multiple_configs(GMMain &gmmain, GM *gm, | |
1748 const SkTDArray<size_t> &configs, | |
1749 const SkTDArray<const PDFRasterizerData*>
&pdfRasterizers, | |
1750 const SkTDArray<SkScalar> &tileGridReplayS
cales, | |
1751 GrContextFactory *grFactory, | |
1752 GrGLStandard gpuAPI) { | |
1753 const char renderModeDescriptor[] = ""; | |
1754 ErrorCombination errorsForAllConfigs; | |
1755 uint32_t gmFlags = gm->getFlags(); | |
1756 | |
1757 for (int i = 0; i < configs.count(); i++) { | |
1758 ConfigData config = gRec[configs[i]]; | |
1759 const SkString shortNamePlusConfig = gmmain.make_shortname_plus_config(g
m->getName(), | |
1760 c
onfig.fName); | |
1761 | |
1762 // Skip any tests that we don't even need to try. | |
1763 // If any of these were skipped on a per-GM basis, record them as | |
1764 // kIntentionallySkipped. | |
1765 if (kPDF_Backend == config.fBackend) { | |
1766 if (gmFlags & GM::kSkipPDF_Flag) { | |
1767 gmmain.RecordSkippedTest(shortNamePlusConfig, | |
1768 renderModeDescriptor, | |
1769 config.fBackend); | |
1770 errorsForAllConfigs.add(kIntentionallySkipped_ErrorType); | |
1771 continue; | |
1772 } | |
1773 } | |
1774 if ((gmFlags & GM::kSkip565_Flag) && | |
1775 (kRaster_Backend == config.fBackend) && | |
1776 (kRGB_565_SkColorType == config.fColorType)) { | |
1777 gmmain.RecordSkippedTest(shortNamePlusConfig, | |
1778 renderModeDescriptor, | |
1779 config.fBackend); | |
1780 errorsForAllConfigs.add(kIntentionallySkipped_ErrorType); | |
1781 continue; | |
1782 } | |
1783 if (((gmFlags & GM::kSkipGPU_Flag) && kGPU_Backend == config.fBackend) |
| | |
1784 ((gmFlags & GM::kGPUOnly_Flag) && kGPU_Backend != config.fBackend))
{ | |
1785 gmmain.RecordSkippedTest(shortNamePlusConfig, | |
1786 renderModeDescriptor, | |
1787 config.fBackend); | |
1788 errorsForAllConfigs.add(kIntentionallySkipped_ErrorType); | |
1789 continue; | |
1790 } | |
1791 | |
1792 // Now we know that we want to run this test and record its | |
1793 // success or failure. | |
1794 ErrorCombination errorsForThisConfig; | |
1795 GrSurface* gpuTarget = NULL; | |
1796 #if SK_SUPPORT_GPU | |
1797 SkAutoTUnref<GrSurface> auGpuTarget; | |
1798 if ((errorsForThisConfig.isEmpty()) && (kGPU_Backend == config.fBackend)
) { | |
1799 if (FLAGS_resetGpuContext) { | |
1800 grFactory->destroyContexts(); | |
1801 } | |
1802 GrContext* gr = grFactory->get(config.fGLContextType, gpuAPI); | |
1803 bool grSuccess = false; | |
1804 if (gr) { | |
1805 // create a render target to back the device | |
1806 GrSurfaceDesc desc; | |
1807 desc.fConfig = kSkia8888_GrPixelConfig; | |
1808 desc.fFlags = kRenderTarget_GrSurfaceFlag; | |
1809 desc.fWidth = gm->getISize().width(); | |
1810 desc.fHeight = gm->getISize().height(); | |
1811 desc.fSampleCnt = config.fSampleCnt; | |
1812 auGpuTarget.reset(gr->createUncachedTexture(desc, NULL, 0)); | |
1813 if (auGpuTarget) { | |
1814 gpuTarget = auGpuTarget; | |
1815 grSuccess = true; | |
1816 // Set the user specified cache limits if non-default. | |
1817 size_t bytes; | |
1818 int count; | |
1819 gr->getResourceCacheLimits(&count, &bytes); | |
1820 if (DEFAULT_CACHE_VALUE != gGpuCacheSizeBytes) { | |
1821 bytes = static_cast<size_t>(gGpuCacheSizeBytes); | |
1822 } | |
1823 if (DEFAULT_CACHE_VALUE != gGpuCacheSizeCount) { | |
1824 count = gGpuCacheSizeCount; | |
1825 } | |
1826 gr->setResourceCacheLimits(count, bytes); | |
1827 } | |
1828 } | |
1829 if (!grSuccess) { | |
1830 errorsForThisConfig.add(kNoGpuContext_ErrorType); | |
1831 } | |
1832 } | |
1833 #endif | |
1834 | |
1835 SkBitmap comparisonBitmap; | |
1836 | |
1837 const char* writePath; | |
1838 if (FLAGS_writePath.count() == 1) { | |
1839 writePath = FLAGS_writePath[0]; | |
1840 } else { | |
1841 writePath = NULL; | |
1842 } | |
1843 | |
1844 if (errorsForThisConfig.isEmpty()) { | |
1845 errorsForThisConfig.add(gmmain.test_drawing(gm, config, pdfRasterize
rs, | |
1846 writePath, gpuTarget, | |
1847 &comparisonBitmap)); | |
1848 gmmain.RecordTestResults(errorsForThisConfig, shortNamePlusConfig, "
"); | |
1849 } | |
1850 | |
1851 // TODO: run only if gmmain.test_drawing succeeded. | |
1852 if (kRaster_Backend == config.fBackend) { | |
1853 run_multiple_modes(gmmain, gm, config, gpuTarget, comparisonBitmap,
tileGridReplayScales); | |
1854 } | |
1855 | |
1856 if (FLAGS_deferred && errorsForThisConfig.isEmpty() && | |
1857 (kGPU_Backend == config.fBackend || kRaster_Backend == config.fBacke
nd)) { | |
1858 errorsForThisConfig.add(gmmain.test_deferred_drawing(gm, config, com
parisonBitmap, | |
1859 gpuTarget)); | |
1860 } | |
1861 | |
1862 if (FLAGS_mpd && (kGPU_Backend == config.fBackend || kRaster_Backend ==
config.fBackend)) { | |
1863 | |
1864 if (gmFlags & GM::kSkipPicture_Flag) { | |
1865 gmmain.RecordSkippedTest(shortNamePlusConfig, | |
1866 renderModeDescriptor, | |
1867 config.fBackend); | |
1868 errorsForThisConfig.add(kIntentionallySkipped_ErrorType); | |
1869 } else if (!(gmFlags & GM::kGPUOnly_Flag)) { | |
1870 errorsForThisConfig.add(gmmain.testMPDDrawing(gm, config, | |
1871 writePath, gpuTarg
et, | |
1872 comparisonBitmap))
; | |
1873 } | |
1874 } | |
1875 | |
1876 errorsForAllConfigs.add(errorsForThisConfig); | |
1877 } | |
1878 return errorsForAllConfigs; | |
1879 } | |
1880 | |
1881 | |
1882 /** | |
1883 * Read individual lines from a file, pushing them into the given array. | |
1884 * | |
1885 * @param filename path to the file to read | |
1886 * @param lines array of strings to add the lines to | |
1887 * @returns true if able to read lines from the file | |
1888 */ | |
1889 static bool read_lines_from_file(const char* filename, SkTArray<SkString> &lines
) { | |
1890 SkAutoTUnref<SkStream> streamWrapper(SkStream::NewFromFile(filename)); | |
1891 SkStream *stream = streamWrapper.get(); | |
1892 if (!stream) { | |
1893 SkDebugf("unable to read file '%s'\n", filename); | |
1894 return false; | |
1895 } | |
1896 | |
1897 char c; | |
1898 SkString line; | |
1899 while (1 == stream->read(&c, 1)) { | |
1900 // If we hit either CR or LF, we've completed a line. | |
1901 // | |
1902 // TODO: If the file uses both CR and LF, this will return an extra blan
k | |
1903 // line for each line of the file. Which is OK for current purposes... | |
1904 // | |
1905 // TODO: Does this properly handle unicode? It doesn't matter for | |
1906 // current purposes... | |
1907 if ((c == 0x0d) || (c == 0x0a)) { | |
1908 lines.push_back(line); | |
1909 line.reset(); | |
1910 } else { | |
1911 line.append(&c, 1); | |
1912 } | |
1913 } | |
1914 lines.push_back(line); | |
1915 return true; | |
1916 } | |
1917 | |
1918 /** | |
1919 * Return a list of all entries in an array of strings as a single string | |
1920 * of this form: | |
1921 * "item1", "item2", "item3" | |
1922 */ | |
1923 SkString list_all(const SkTArray<SkString> &stringArray); | |
1924 SkString list_all(const SkTArray<SkString> &stringArray) { | |
1925 SkString total; | |
1926 for (int i = 0; i < stringArray.count(); i++) { | |
1927 if (i > 0) { | |
1928 total.append(", "); | |
1929 } | |
1930 total.append("\""); | |
1931 total.append(stringArray[i]); | |
1932 total.append("\""); | |
1933 } | |
1934 return total; | |
1935 } | |
1936 | |
1937 /** | |
1938 * Return a list of configuration names, as a single string of this form: | |
1939 * "item1", "item2", "item3" | |
1940 * | |
1941 * @param configs configurations, as a list of indices into gRec | |
1942 */ | |
1943 SkString list_all_config_names(const SkTDArray<size_t> &configs); | |
1944 SkString list_all_config_names(const SkTDArray<size_t> &configs) { | |
1945 SkString total; | |
1946 for (int i = 0; i < configs.count(); i++) { | |
1947 if (i > 0) { | |
1948 total.append(", "); | |
1949 } | |
1950 total.append("\""); | |
1951 total.append(gRec[configs[i]].fName); | |
1952 total.append("\""); | |
1953 } | |
1954 return total; | |
1955 } | |
1956 | |
1957 static bool prepare_subdirectories(const char *root, bool useFileHierarchy, | |
1958 const SkTDArray<size_t> &configs, | |
1959 const SkTDArray<const PDFRasterizerData*>& pd
fRasterizers) { | |
1960 if (!sk_mkdir(root)) { | |
1961 return false; | |
1962 } | |
1963 if (useFileHierarchy) { | |
1964 for (int i = 0; i < configs.count(); i++) { | |
1965 ConfigData config = gRec[configs[i]]; | |
1966 SkString subdir; | |
1967 subdir.appendf("%s%c%s", root, SkPATH_SEPARATOR, config.fName); | |
1968 if (!sk_mkdir(subdir.c_str())) { | |
1969 return false; | |
1970 } | |
1971 | |
1972 if (config.fBackend == kPDF_Backend) { | |
1973 for (int j = 0; j < pdfRasterizers.count(); j++) { | |
1974 SkString pdfSubdir = subdir; | |
1975 pdfSubdir.appendf("-%s", pdfRasterizers[j]->fName); | |
1976 if (!sk_mkdir(pdfSubdir.c_str())) { | |
1977 return false; | |
1978 } | |
1979 } | |
1980 } | |
1981 } | |
1982 } | |
1983 return true; | |
1984 } | |
1985 | |
1986 static bool parse_flags_configs(SkTDArray<size_t>* outConfigs, | |
1987 GrContextFactory* grFactory, GrGLStandard gpuAPI) { | |
1988 SkTDArray<size_t> excludeConfigs; | |
1989 | |
1990 for (int i = 0; i < FLAGS_config.count(); i++) { | |
1991 const char* config = FLAGS_config[i]; | |
1992 bool exclude = false; | |
1993 if (*config == kExcludeConfigChar) { | |
1994 exclude = true; | |
1995 config += 1; | |
1996 } | |
1997 int index = findConfig(config); | |
1998 if (index >= 0) { | |
1999 if (exclude) { | |
2000 *excludeConfigs.append() = index; | |
2001 } else { | |
2002 appendUnique<size_t>(outConfigs, index); | |
2003 } | |
2004 } else if (0 == strcmp(kDefaultsConfigStr, config)) { | |
2005 if (exclude) { | |
2006 SkDebugf("%c%s is not allowed.\n", | |
2007 kExcludeConfigChar, kDefaultsConfigStr); | |
2008 return false; | |
2009 } | |
2010 for (size_t c = 0; c < SK_ARRAY_COUNT(gRec); ++c) { | |
2011 if (gRec[c].fRunByDefault) { | |
2012 appendUnique<size_t>(outConfigs, c); | |
2013 } | |
2014 } | |
2015 } else { | |
2016 SkDebugf("unrecognized config %s\n", config); | |
2017 return false; | |
2018 } | |
2019 } | |
2020 | |
2021 for (int i = 0; i < FLAGS_excludeConfig.count(); i++) { | |
2022 int index = findConfig(FLAGS_excludeConfig[i]); | |
2023 if (index >= 0) { | |
2024 *excludeConfigs.append() = index; | |
2025 } else { | |
2026 SkDebugf("unrecognized excludeConfig %s\n", FLAGS_excludeConfig[i]); | |
2027 return false; | |
2028 } | |
2029 } | |
2030 | |
2031 if (outConfigs->count() == 0) { | |
2032 // if no config is specified by user, add the defaults | |
2033 for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); ++i) { | |
2034 if (gRec[i].fRunByDefault) { | |
2035 *outConfigs->append() = i; | |
2036 } | |
2037 } | |
2038 } | |
2039 // now remove any explicitly excluded configs | |
2040 for (int i = 0; i < excludeConfigs.count(); ++i) { | |
2041 int index = outConfigs->find(excludeConfigs[i]); | |
2042 if (index >= 0) { | |
2043 outConfigs->remove(index); | |
2044 // now assert that there was only one copy in configs[] | |
2045 SkASSERT(outConfigs->find(excludeConfigs[i]) < 0); | |
2046 } | |
2047 } | |
2048 | |
2049 for (int i = 0; i < outConfigs->count(); ++i) { | |
2050 size_t index = (*outConfigs)[i]; | |
2051 if (kGPU_Backend == gRec[index].fBackend) { | |
2052 #if SK_SUPPORT_GPU | |
2053 if (!FLAGS_gpu) { | |
2054 outConfigs->remove(i); | |
2055 --i; | |
2056 continue; | |
2057 } | |
2058 #endif | |
2059 } else if (!FLAGS_cpu) { | |
2060 outConfigs->remove(i); | |
2061 --i; | |
2062 continue; | |
2063 } | |
2064 #if SK_SUPPORT_GPU | |
2065 SkASSERT(grFactory != NULL); | |
2066 if (kGPU_Backend == gRec[index].fBackend) { | |
2067 GrContext* ctx = grFactory->get(gRec[index].fGLContextType, gpuAPI); | |
2068 if (NULL == ctx) { | |
2069 SkDebugf("GrContext could not be created for config %s. Config w
ill be skipped.\n", | |
2070 gRec[index].fName); | |
2071 outConfigs->remove(i); | |
2072 --i; | |
2073 continue; | |
2074 } | |
2075 if (gRec[index].fSampleCnt > ctx->getMaxSampleCount()) { | |
2076 SkDebugf("Sample count (%d) of config %s is not supported." | |
2077 " Config will be skipped.\n", | |
2078 gRec[index].fSampleCnt, gRec[index].fName); | |
2079 outConfigs->remove(i); | |
2080 --i; | |
2081 } | |
2082 } | |
2083 #endif | |
2084 } | |
2085 | |
2086 if (outConfigs->isEmpty()) { | |
2087 SkDebugf("No configs to run."); | |
2088 return false; | |
2089 } | |
2090 | |
2091 // now show the user the set of configs that will be run. | |
2092 SkString configStr("These configs will be run:"); | |
2093 // show the user the config that will run. | |
2094 for (int i = 0; i < outConfigs->count(); ++i) { | |
2095 configStr.appendf(" %s", gRec[(*outConfigs)[i]].fName); | |
2096 } | |
2097 SkDebugf("%s\n", configStr.c_str()); | |
2098 | |
2099 return true; | |
2100 } | |
2101 | |
2102 static bool parse_flags_pdf_rasterizers(const SkTDArray<size_t>& configs, | |
2103 SkTDArray<const PDFRasterizerData*>* out
Rasterizers) { | |
2104 // No need to run this check (and display the PDF rasterizers message) | |
2105 // if no PDF backends are in the configs. | |
2106 bool configHasPDF = false; | |
2107 for (int i = 0; i < configs.count(); i++) { | |
2108 if (gRec[configs[i]].fBackend == kPDF_Backend) { | |
2109 configHasPDF = true; | |
2110 break; | |
2111 } | |
2112 } | |
2113 if (!configHasPDF) { | |
2114 return true; | |
2115 } | |
2116 | |
2117 if (FLAGS_pdfRasterizers.count() == 1 && | |
2118 !strcmp(FLAGS_pdfRasterizers[0], "default")) { | |
2119 for (int i = 0; i < (int)SK_ARRAY_COUNT(kPDFRasterizers); ++i) { | |
2120 if (kPDFRasterizers[i].fRunByDefault) { | |
2121 *outRasterizers->append() = &kPDFRasterizers[i]; | |
2122 } | |
2123 } | |
2124 } else { | |
2125 for (int i = 0; i < FLAGS_pdfRasterizers.count(); i++) { | |
2126 const char* rasterizer = FLAGS_pdfRasterizers[i]; | |
2127 const PDFRasterizerData* rasterizerPtr = | |
2128 findPDFRasterizer(rasterizer); | |
2129 if (rasterizerPtr == NULL) { | |
2130 SkDebugf("unrecognized rasterizer %s\n", rasterizer); | |
2131 return false; | |
2132 } | |
2133 appendUnique<const PDFRasterizerData*>(outRasterizers, | |
2134 rasterizerPtr); | |
2135 } | |
2136 } | |
2137 | |
2138 // now show the user the set of configs that will be run. | |
2139 SkString configStr("These PDF rasterizers will be run:"); | |
2140 // show the user the config that will run. | |
2141 for (int i = 0; i < outRasterizers->count(); ++i) { | |
2142 configStr.appendf(" %s", (*outRasterizers)[i]->fName); | |
2143 } | |
2144 SkDebugf("%s\n", configStr.c_str()); | |
2145 | |
2146 return true; | |
2147 } | |
2148 | |
2149 static bool parse_flags_ignore_error_types(ErrorCombination* outErrorTypes) { | |
2150 if (FLAGS_ignoreErrorTypes.count() > 0) { | |
2151 *outErrorTypes = ErrorCombination(); | |
2152 for (int i = 0; i < FLAGS_ignoreErrorTypes.count(); i++) { | |
2153 ErrorType type; | |
2154 const char *name = FLAGS_ignoreErrorTypes[i]; | |
2155 if (!getErrorTypeByName(name, &type)) { | |
2156 SkDebugf("cannot find ErrorType with name '%s'\n", name); | |
2157 return false; | |
2158 } else { | |
2159 outErrorTypes->add(type); | |
2160 } | |
2161 } | |
2162 } | |
2163 return true; | |
2164 } | |
2165 | |
2166 /** | |
2167 * Replace contents of ignoreTestNames with a list of test names, indicating | |
2168 * which tests' failures should be ignored. | |
2169 */ | |
2170 static bool parse_flags_ignore_tests(SkTArray<SkString> &ignoreTestNames) { | |
2171 ignoreTestNames.reset(); | |
2172 | |
2173 // Parse --ignoreFailuresFile | |
2174 for (int i = 0; i < FLAGS_ignoreFailuresFile.count(); i++) { | |
2175 SkTArray<SkString> linesFromFile; | |
2176 if (!read_lines_from_file(FLAGS_ignoreFailuresFile[i], linesFromFile)) { | |
2177 return false; | |
2178 } else { | |
2179 for (int j = 0; j < linesFromFile.count(); j++) { | |
2180 SkString thisLine = linesFromFile[j]; | |
2181 if (thisLine.isEmpty() || thisLine.startsWith('#')) { | |
2182 // skip this line | |
2183 } else { | |
2184 ignoreTestNames.push_back(thisLine); | |
2185 } | |
2186 } | |
2187 } | |
2188 } | |
2189 | |
2190 return true; | |
2191 } | |
2192 | |
2193 static bool parse_flags_modulo(int* moduloRemainder, int* moduloDivisor) { | |
2194 if (FLAGS_modulo.count() == 2) { | |
2195 *moduloRemainder = atoi(FLAGS_modulo[0]); | |
2196 *moduloDivisor = atoi(FLAGS_modulo[1]); | |
2197 if (*moduloRemainder < 0 || *moduloDivisor <= 0 || | |
2198 *moduloRemainder >= *moduloDivisor) { | |
2199 SkDebugf("invalid modulo values."); | |
2200 return false; | |
2201 } | |
2202 } | |
2203 return true; | |
2204 } | |
2205 | |
2206 #if SK_SUPPORT_GPU | |
2207 static bool parse_flags_gpu_cache(int* sizeBytes, int* sizeCount) { | |
2208 if (FLAGS_gpuCacheSize.count() > 0) { | |
2209 if (FLAGS_gpuCacheSize.count() != 2) { | |
2210 SkDebugf("--gpuCacheSize requires two arguments\n"); | |
2211 return false; | |
2212 } | |
2213 *sizeBytes = atoi(FLAGS_gpuCacheSize[0]); | |
2214 *sizeCount = atoi(FLAGS_gpuCacheSize[1]); | |
2215 } else { | |
2216 *sizeBytes = DEFAULT_CACHE_VALUE; | |
2217 *sizeCount = DEFAULT_CACHE_VALUE; | |
2218 } | |
2219 return true; | |
2220 } | |
2221 | |
2222 static bool parse_flags_gl_standard(GrGLStandard* gpuAPI) { | |
2223 if (0 == FLAGS_gpuAPI.count()) { | |
2224 *gpuAPI = kNone_GrGLStandard; | |
2225 return true; | |
2226 } | |
2227 if (1 == FLAGS_gpuAPI.count()) { | |
2228 if (FLAGS_gpuAPI.contains(kGpuAPINameGL)) { | |
2229 *gpuAPI = kGL_GrGLStandard; | |
2230 return true; | |
2231 } | |
2232 if (FLAGS_gpuAPI.contains(kGpuAPINameGLES)) { | |
2233 *gpuAPI = kGLES_GrGLStandard; | |
2234 return true; | |
2235 } | |
2236 } | |
2237 SkDebugf("--gpuAPI invalid api value"); | |
2238 return false; | |
2239 } | |
2240 #endif | |
2241 | |
2242 static bool parse_flags_tile_grid_replay_scales(SkTDArray<SkScalar>* outScales)
{ | |
2243 *outScales->append() = SK_Scalar1; // By default only test at scale 1.0 | |
2244 if (FLAGS_tileGridReplayScales.count() > 0) { | |
2245 outScales->reset(); | |
2246 for (int i = 0; i < FLAGS_tileGridReplayScales.count(); i++) { | |
2247 double val = atof(FLAGS_tileGridReplayScales[i]); | |
2248 if (0 < val) { | |
2249 *outScales->append() = SkDoubleToScalar(val); | |
2250 } | |
2251 } | |
2252 if (0 == outScales->count()) { | |
2253 // Should have at least one scale | |
2254 SkDebugf("--tileGridReplayScales requires at least one scale.\n"); | |
2255 return false; | |
2256 } | |
2257 } | |
2258 return true; | |
2259 } | |
2260 | |
2261 static bool parse_flags_gmmain_paths(GMMain* gmmain) { | |
2262 gmmain->fUseFileHierarchy = FLAGS_hierarchy; | |
2263 gmmain->fWriteChecksumBasedFilenames = FLAGS_writeChecksumBasedFilenames; | |
2264 | |
2265 if (FLAGS_mismatchPath.count() == 1) { | |
2266 gmmain->fMismatchPath = FLAGS_mismatchPath[0]; | |
2267 } | |
2268 | |
2269 if (FLAGS_missingExpectationsPath.count() == 1) { | |
2270 gmmain->fMissingExpectationsPath = FLAGS_missingExpectationsPath[0]; | |
2271 } | |
2272 | |
2273 if (FLAGS_readPath.count() == 1) { | |
2274 const char* readPath = FLAGS_readPath[0]; | |
2275 if (!sk_exists(readPath)) { | |
2276 SkDebugf("readPath %s does not exist!\n", readPath); | |
2277 return false; | |
2278 } | |
2279 if (sk_isdir(readPath)) { | |
2280 if (FLAGS_verbose) { | |
2281 SkDebugf("reading from %s\n", readPath); | |
2282 } | |
2283 gmmain->fExpectationsSource.reset(SkNEW_ARGS( | |
2284 IndividualImageExpectationsSource, (readPath))); | |
2285 } else { | |
2286 if (FLAGS_verbose) { | |
2287 SkDebugf("reading expectations from JSON summary file %s\n", rea
dPath); | |
2288 } | |
2289 gmmain->fExpectationsSource.reset(SkNEW_ARGS(JsonExpectationsSource,
(readPath))); | |
2290 } | |
2291 } | |
2292 return true; | |
2293 } | |
2294 | |
2295 static bool parse_flags_jpeg_quality() { | |
2296 if (FLAGS_pdfJpegQuality < -1 || FLAGS_pdfJpegQuality > 100) { | |
2297 SkDebugf("%s\n", "pdfJpegQuality must be in [-1 .. 100] range."); | |
2298 return false; | |
2299 } | |
2300 return true; | |
2301 } | |
2302 | |
2303 int tool_main(int argc, char** argv); | |
2304 int tool_main(int argc, char** argv) { | |
2305 SetupCrashHandler(); | |
2306 | |
2307 SkString usage; | |
2308 usage.printf("Run the golden master tests.\n"); | |
2309 SkCommandLineFlags::SetUsage(usage.c_str()); | |
2310 SkCommandLineFlags::Parse(argc, argv); | |
2311 | |
2312 #if SK_ENABLE_INST_COUNT | |
2313 if (FLAGS_leaks) { | |
2314 gPrintInstCount = true; | |
2315 } | |
2316 #endif | |
2317 | |
2318 SkAutoGraphics ag; | |
2319 | |
2320 setSystemPreferences(); | |
2321 GMMain gmmain; | |
2322 | |
2323 SkTDArray<size_t> configs; | |
2324 | |
2325 int moduloRemainder = -1; | |
2326 int moduloDivisor = -1; | |
2327 SkTDArray<const PDFRasterizerData*> pdfRasterizers; | |
2328 SkTDArray<SkScalar> tileGridReplayScales; | |
2329 #if SK_SUPPORT_GPU | |
2330 GrGLStandard gpuAPI = kNone_GrGLStandard; | |
2331 GrContext::Options grContextOpts; | |
2332 grContextOpts.fDrawPathToCompressedTexture = FLAGS_gpuCompressAlphaMasks; | |
2333 GrContextFactory* grFactory = new GrContextFactory(grContextOpts); | |
2334 #else | |
2335 GrGLStandard gpuAPI = 0; | |
2336 GrContextFactory* grFactory = NULL; | |
2337 #endif | |
2338 | |
2339 if (FLAGS_dryRun) { | |
2340 SkDebugf( "Doing a dry run; no tests will actually be executed.\n"); | |
2341 } | |
2342 | |
2343 if (!parse_flags_modulo(&moduloRemainder, &moduloDivisor) || | |
2344 !parse_flags_ignore_error_types(&gmmain.fIgnorableErrorTypes) || | |
2345 !parse_flags_ignore_tests(gmmain.fIgnorableTestNames) || | |
2346 #if SK_SUPPORT_GPU | |
2347 !parse_flags_gpu_cache(&gGpuCacheSizeBytes, &gGpuCacheSizeCount) || | |
2348 !parse_flags_gl_standard(&gpuAPI) || | |
2349 #endif | |
2350 !parse_flags_tile_grid_replay_scales(&tileGridReplayScales) || | |
2351 !parse_flags_jpeg_quality() || | |
2352 !parse_flags_configs(&configs, grFactory, gpuAPI) || | |
2353 !parse_flags_pdf_rasterizers(configs, &pdfRasterizers) || | |
2354 !parse_flags_gmmain_paths(&gmmain)) { | |
2355 return -1; | |
2356 } | |
2357 | |
2358 if (FLAGS_verbose) { | |
2359 if (FLAGS_writePath.count() == 1) { | |
2360 SkDebugf("writing to %s\n", FLAGS_writePath[0]); | |
2361 } | |
2362 if (gmmain.fMismatchPath) { | |
2363 SkDebugf("writing mismatches to %s\n", gmmain.fMismatchPath); | |
2364 } | |
2365 if (gmmain.fMissingExpectationsPath) { | |
2366 SkDebugf("writing images without expectations to %s\n", | |
2367 gmmain.fMissingExpectationsPath); | |
2368 } | |
2369 if (FLAGS_writePicturePath.count() == 1) { | |
2370 SkDebugf("writing pictures to %s\n", FLAGS_writePicturePath[0]); | |
2371 } | |
2372 if (!GetResourcePath().isEmpty()) { | |
2373 SkDebugf("reading resources from %s\n", GetResourcePath().c_str()); | |
2374 } | |
2375 } | |
2376 | |
2377 int gmsRun = 0; | |
2378 int gmIndex = -1; | |
2379 SkString moduloStr; | |
2380 | |
2381 if (!FLAGS_dryRun) { | |
2382 // If we will be writing out files, prepare subdirectories. | |
2383 if (FLAGS_writePath.count() == 1) { | |
2384 if (!prepare_subdirectories(FLAGS_writePath[0], gmmain.fUseFileHiera
rchy, | |
2385 configs, pdfRasterizers)) { | |
2386 return -1; | |
2387 } | |
2388 } | |
2389 if (gmmain.fMismatchPath) { | |
2390 if (!prepare_subdirectories(gmmain.fMismatchPath, gmmain.fUseFileHie
rarchy, | |
2391 configs, pdfRasterizers)) { | |
2392 return -1; | |
2393 } | |
2394 } | |
2395 if (gmmain.fMissingExpectationsPath) { | |
2396 if (!prepare_subdirectories(gmmain.fMissingExpectationsPath, gmmain.
fUseFileHierarchy, | |
2397 configs, pdfRasterizers)) { | |
2398 return -1; | |
2399 } | |
2400 } | |
2401 } | |
2402 Iter iter; | |
2403 GM* gm; | |
2404 while ((gm = iter.next()) != NULL) { | |
2405 if (FLAGS_forcePerspectiveMatrix) { | |
2406 SkMatrix perspective; | |
2407 perspective.setIdentity(); | |
2408 perspective.setPerspY(SkScalarDiv(SK_Scalar1, SkIntToScalar(1000))); | |
2409 perspective.setSkewX(SkScalarDiv(SkIntToScalar(8), | |
2410 SkIntToScalar(25))); | |
2411 | |
2412 gm->setStarterMatrix(perspective); | |
2413 } | |
2414 SkAutoTDelete<GM> adgm(gm); | |
2415 ++gmIndex; | |
2416 if (moduloRemainder >= 0) { | |
2417 if ((gmIndex % moduloDivisor) != moduloRemainder) { | |
2418 continue; | |
2419 } | |
2420 moduloStr.printf("[%d.%d] ", gmIndex, moduloDivisor); | |
2421 } | |
2422 | |
2423 const char* shortName = gm->getName(); | |
2424 | |
2425 if (SkCommandLineFlags::ShouldSkip(FLAGS_match, shortName)) { | |
2426 continue; | |
2427 } | |
2428 | |
2429 gmsRun++; | |
2430 SkISize size = gm->getISize(); | |
2431 SkDebugf("%4dM %sdrawing... %s [%d %d]\n", | |
2432 sk_tools::getMaxResidentSetSizeMB(), moduloStr.c_str(), shortNa
me, | |
2433 size.width(), size.height()); | |
2434 if (!FLAGS_dryRun) | |
2435 run_multiple_configs(gmmain, gm, configs, pdfRasterizers, tileGridRe
playScales, | |
2436 grFactory, gpuAPI); | |
2437 } | |
2438 | |
2439 if (FLAGS_dryRun) | |
2440 return 0; | |
2441 | |
2442 SkTArray<SkString> modes; | |
2443 gmmain.GetRenderModesEncountered(modes); | |
2444 int modeCount = modes.count(); | |
2445 | |
2446 // Now that we have run all the tests and thus know the full set of renderMo
des that we | |
2447 // tried to run, we can call RecordTestResults() to record the cases in whic
h we skipped | |
2448 // ALL renderModes. | |
2449 // See http://skbug.com/1994 and https://codereview.chromium.org/129203002/ | |
2450 int testCount = gmmain.fTestsSkippedOnAllRenderModes.count(); | |
2451 for (int testNum = 0; testNum < testCount; ++testNum) { | |
2452 const SkString &shortNamePlusConfig = gmmain.fTestsSkippedOnAllRenderMod
es[testNum]; | |
2453 for (int modeNum = 0; modeNum < modeCount; ++modeNum) { | |
2454 gmmain.RecordTestResults(kIntentionallySkipped_ErrorType, shortNameP
lusConfig, | |
2455 modes[modeNum].c_str()); | |
2456 } | |
2457 } | |
2458 | |
2459 bool reportError = false; | |
2460 if (gmmain.NumSignificantErrors() > 0) { | |
2461 reportError = true; | |
2462 } | |
2463 | |
2464 // We test every GM against every config, and for every raster config also t
est every mode. | |
2465 int rasterConfigs = 0; | |
2466 for (int i = 0; i < configs.count(); i++) { | |
2467 if (gRec[configs[i]].fBackend == kRaster_Backend) { | |
2468 rasterConfigs++; | |
2469 } | |
2470 } | |
2471 // For raster configs, we run all renderModes; for non-raster configs, just
default renderMode | |
2472 const int expectedNumberOfTests = rasterConfigs * gmsRun * modeCount | |
2473 + (configs.count() - rasterConfigs) * gmsRun
; | |
2474 | |
2475 // Output summary to stdout. | |
2476 if (FLAGS_verbose) { | |
2477 SkDebugf("Ran %d GMs\n", gmsRun); | |
2478 SkDebugf("... over %2d configs [%s]\n", configs.count(), | |
2479 list_all_config_names(configs).c_str()); | |
2480 SkDebugf("... and %2d modes [%s]\n", modeCount, list_all(modes).c_str
()); | |
2481 SkDebugf("... so there should be a total of %d tests.\n", expectedNumber
OfTests); | |
2482 } | |
2483 gmmain.ListErrors(FLAGS_verbose); | |
2484 | |
2485 // TODO(epoger): Enable this check for Android, too, once we resolve | |
2486 // https://code.google.com/p/skia/issues/detail?id=1222 | |
2487 // ('GM is unexpectedly skipping tests on Android') | |
2488 #ifndef SK_BUILD_FOR_ANDROID | |
2489 if (expectedNumberOfTests != gmmain.fTestsRun) { | |
2490 SkDebugf("expected %d tests, but ran or skipped %d tests\n", | |
2491 expectedNumberOfTests, gmmain.fTestsRun); | |
2492 reportError = true; | |
2493 } | |
2494 #endif | |
2495 | |
2496 if (FLAGS_writeJsonSummaryPath.count() == 1) { | |
2497 Json::Value root = CreateJsonTree( | |
2498 gmmain.fJsonExpectedResults, | |
2499 gmmain.fJsonActualResults_Failed, gmmain.fJsonActualResults_FailureI
gnored, | |
2500 gmmain.fJsonActualResults_NoComparison, gmmain.fJsonActualResults_Su
cceeded); | |
2501 std::string jsonStdString = root.toStyledString(); | |
2502 SkFILEWStream stream(FLAGS_writeJsonSummaryPath[0]); | |
2503 stream.write(jsonStdString.c_str(), jsonStdString.length()); | |
2504 } | |
2505 | |
2506 #if SK_SUPPORT_GPU | |
2507 | |
2508 #if GR_CACHE_STATS | |
2509 for (int i = 0; i < configs.count(); i++) { | |
2510 ConfigData config = gRec[configs[i]]; | |
2511 | |
2512 if (FLAGS_verbose && (kGPU_Backend == config.fBackend)) { | |
2513 GrContext* gr = grFactory->get(config.fGLContextType, gpuAPI); | |
2514 | |
2515 SkDebugf("config: %s %x\n", config.fName, gr); | |
2516 gr->printCacheStats(); | |
2517 } | |
2518 } | |
2519 #endif | |
2520 | |
2521 #if GR_DUMP_FONT_CACHE | |
2522 for (int i = 0; i < configs.count(); i++) { | |
2523 ConfigData config = gRec[configs[i]]; | |
2524 | |
2525 if (kGPU_Backend == config.fBackend) { | |
2526 GrContext* gr = grFactory->get(config.fGLContextType, gpuAPI); | |
2527 | |
2528 gr->dumpFontCache(); | |
2529 } | |
2530 } | |
2531 #endif | |
2532 | |
2533 delete grFactory; | |
2534 #endif | |
2535 | |
2536 return (reportError) ? -1 : 0; | |
2537 } | |
2538 | |
2539 void GMMain::InstallFilter(SkCanvas* canvas) { | |
2540 if (FLAGS_forceBWtext) { | |
2541 canvas->setDrawFilter(SkNEW(BWTextDrawFilter))->unref(); | |
2542 } | |
2543 } | |
2544 | |
2545 #if !defined(SK_BUILD_FOR_IOS) && !defined(SK_BUILD_FOR_NACL) | |
2546 int main(int argc, char * const argv[]) { | |
2547 return tool_main(argc, (char**) argv); | |
2548 } | |
2549 #endif | |
OLD | NEW |