gm: change ErrorBitfield to ErrorType/ErrorCombination
This will enable future GM changes to report errors more completely/consistently, like skdiff.
Review URL: https://codereview.chromium.org/12992003

git-svn-id: http://skia.googlecode.com/svn/trunk@8344 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gm/gmmain.cpp b/gm/gmmain.cpp
index 42c7889..55e1c2a 100644
--- a/gm/gmmain.cpp
+++ b/gm/gmmain.cpp
@@ -14,6 +14,7 @@
  */
 
 #include "gm.h"
+#include "gm_error.h"
 #include "gm_expectations.h"
 #include "system_preferences.h"
 #include "SkBitmap.h"
@@ -52,6 +53,9 @@
 #include "GrRenderTarget.h"
 #include "SkGpuDevice.h"
 typedef GrContextFactory::GLContextType GLContextType;
+#define DEFAULT_CACHE_VALUE -1
+static int gGpuCacheSizeBytes;
+static int gGpuCacheSizeCount;
 #else
 class GrContextFactory;
 class GrContext;
@@ -80,17 +84,6 @@
     #define CAN_IMAGE_PDF   0
 #endif
 
-typedef int ErrorBitfield;
-// an empty bitfield means no errors:
-const static ErrorBitfield kEmptyErrorBitfield                 = 0x00;
-// individual error types:
-const static ErrorBitfield kNoGpuContext_ErrorBitmask          = 0x01;
-const static ErrorBitfield kImageMismatch_ErrorBitmask         = 0x02;
-const static ErrorBitfield kMissingExpectations_ErrorBitmask   = 0x04;
-const static ErrorBitfield kWritingReferenceImage_ErrorBitmask = 0x08;
-// we typically ignore any errors matching this bitmask:
-const static ErrorBitfield kIgnorable_ErrorBitmask = kMissingExpectations_ErrorBitmask;
-
 using namespace skiagm;
 
 struct FailRec {
@@ -195,6 +188,7 @@
         // Set default values of member variables, which tool_main()
         // may override.
         fUseFileHierarchy = false;
+        fIgnorableErrorCombination.add(kMissingExpectations_ErrorType);
         fMismatchPath = NULL;
     }
 
@@ -248,24 +242,24 @@
                                           SkImageEncoder::kPNG_Type, 100);
     }
 
-    // Records an error in fFailedTests, if we want to record errors
-    // of this type.
-    void RecordError(ErrorBitfield errorType, const SkString& name,
+    /**
+     * Records the errors encountered in fFailedTests, except for any error
+     * types we want to ignore.
+     */
+    void RecordError(const ErrorCombination& errorCombination, const SkString& name,
                      const char renderModeDescriptor []) {
         // The common case: no error means nothing to record.
-        if (kEmptyErrorBitfield == errorType) {
+        if (errorCombination.isEmpty()) {
             return;
         }
 
         // If only certain error type(s) were reported, we know we can ignore them.
-        if (errorType == (errorType & kIgnorable_ErrorBitmask)) {
+        if (errorCombination.minus(fIgnorableErrorCombination).isEmpty()) {
             return;
         }
 
-        FailRec& rec = fFailedTests.push_back(make_name(
-            name.c_str(), renderModeDescriptor));
-        rec.fIsPixelError =
-            (kEmptyErrorBitfield != (errorType & kImageMismatch_ErrorBitmask));
+        FailRec& rec = fFailedTests.push_back(make_name(name.c_str(), renderModeDescriptor));
+        rec.fIsPixelError = errorCombination.includes(kImageMismatch_ErrorType);
     }
 
     // List contents of fFailedTests via SkDebug.
@@ -378,11 +372,11 @@
         canvas->setDrawFilter(NULL);
     }
 
-    static ErrorBitfield generate_image(GM* gm, const ConfigData& gRec,
-                                        GrContext* context,
-                                        GrRenderTarget* rt,
-                                        SkBitmap* bitmap,
-                                        bool deferred) {
+    static ErrorCombination generate_image(GM* gm, const ConfigData& gRec,
+                                           GrContext* context,
+                                           GrRenderTarget* rt,
+                                           SkBitmap* bitmap,
+                                           bool deferred) {
         SkISize size (gm->getISize());
         setup_bitmap(gRec, size, bitmap);
 
@@ -401,7 +395,7 @@
 #if SK_SUPPORT_GPU
         else {  // GPU
             if (NULL == context) {
-                return kNoGpuContext_ErrorBitmask;
+                return ErrorCombination(kNoGpuContext_ErrorType);
             }
             SkAutoTUnref<SkDevice> device(new SkGpuDevice(context, rt));
             if (deferred) {
@@ -419,7 +413,7 @@
         }
 #endif
         complete_bitmap(bitmap);
-        return kEmptyErrorBitfield;
+        return kEmpty_ErrorCombination;
     }
 
     static void generate_image_from_picture(GM* gm, const ConfigData& gRec,
@@ -488,10 +482,9 @@
 #endif
     }
 
-    ErrorBitfield write_reference_image(
-      const ConfigData& gRec, const char writePath [],
-      const char renderModeDescriptor [], const SkString& name,
-        SkBitmap& bitmap, SkDynamicMemoryWStream* document) {
+    ErrorCombination write_reference_image(const ConfigData& gRec, const char writePath [],
+                                           const char renderModeDescriptor [], const SkString& name,
+                                           SkBitmap& bitmap, SkDynamicMemoryWStream* document) {
         SkString path;
         bool success = false;
         if (gRec.fBackend == kRaster_Backend ||
@@ -513,12 +506,12 @@
             success = write_document(path, *document);
         }
         if (success) {
-            return kEmptyErrorBitfield;
+            return kEmpty_ErrorCombination;
         } else {
             gm_fprintf(stderr, "FAILED to write %s\n", path.c_str());
-            RecordError(kWritingReferenceImage_ErrorBitmask, name,
-                        renderModeDescriptor);
-            return kWritingReferenceImage_ErrorBitmask;
+            ErrorCombination errors(kWritingReferenceImage_ErrorType);
+            RecordError(errors, name, renderModeDescriptor);
+            return errors;
         }
     }
 
@@ -579,9 +572,8 @@
     }
 
     /**
-     * Compares actual checksum to expectations.  Returns
-     * kEmptyErrorBitfield if they match, or some combination of
-     * _ErrorBitmask values otherwise.
+     * Compares actual checksum to expectations, returning the set of errors
+     * (if any) that we saw along the way.
      *
      * If fMismatchPath has been set, and there are pixel diffs, then the
      * actual bitmap will be written out to a file within fMismatchPath.
@@ -598,23 +590,21 @@
      * in-memory comparisons (Rtree vs regular, etc.) are not written to the
      * JSON summary.  We may wish to change that.
      */
-    ErrorBitfield compare_to_expectations(Expectations expectations,
-                                          const SkBitmap& actualBitmap,
-                                          const SkString& baseNameString,
-                                          const char renderModeDescriptor[],
-                                          bool addToJsonSummary=false) {
-        ErrorBitfield retval;
+    ErrorCombination compare_to_expectations(Expectations expectations,
+                                             const SkBitmap& actualBitmap,
+                                             const SkString& baseNameString,
+                                             const char renderModeDescriptor[],
+                                             bool addToJsonSummary=false) {
+        ErrorCombination errors;
         Checksum actualChecksum = SkBitmapChecksummer::Compute64(actualBitmap);
         SkString completeNameString = baseNameString;
         completeNameString.append(renderModeDescriptor);
         const char* completeName = completeNameString.c_str();
 
         if (expectations.empty()) {
-            retval = kMissingExpectations_ErrorBitmask;
-        } else if (expectations.match(actualChecksum)) {
-            retval = kEmptyErrorBitfield;
-        } else {
-            retval = kImageMismatch_ErrorBitmask;
+            errors.add(kMissingExpectations_ErrorType);
+        } else if (!expectations.match(actualChecksum)) {
+            errors.add(kImageMismatch_ErrorType);
 
             // Write out the "actuals" for any mismatches, if we have
             // been directed to do so.
@@ -632,16 +622,15 @@
                 report_bitmap_diffs(*expectedBitmapPtr, actualBitmap, completeName);
             }
         }
-        RecordError(retval, baseNameString, renderModeDescriptor);
+        RecordError(errors, baseNameString, renderModeDescriptor);
 
         if (addToJsonSummary) {
-            add_actual_results_to_json_summary(completeName, actualChecksum,
-                                               retval,
+            add_actual_results_to_json_summary(completeName, actualChecksum, errors,
                                                expectations.ignoreFailure());
             add_expected_results_to_json_summary(completeName, expectations);
         }
 
-        return retval;
+        return errors;
     }
 
     /**
@@ -650,12 +639,12 @@
      */
     void add_actual_results_to_json_summary(const char testName[],
                                             Checksum actualChecksum,
-                                            ErrorBitfield result,
+                                            ErrorCombination result,
                                             bool ignoreFailure) {
         Json::Value actualResults;
         actualResults[kJsonKey_ActualResults_AnyStatus_Checksum] =
             asJsonValue(actualChecksum);
-        if (kEmptyErrorBitfield == result) {
+        if (result.isEmpty()) {
             this->fJsonActualResults_Succeeded[testName] = actualResults;
         } else {
             if (ignoreFailure) {
@@ -663,12 +652,12 @@
                 // actual results against expectations in a JSON file
                 // (where we can set ignore-failure to either true or
                 // false), add test cases that exercise ignored
-                // failures (both for kMissingExpectations_ErrorBitmask
-                // and kImageMismatch_ErrorBitmask).
+                // failures (both for kMissingExpectations_ErrorType
+                // and kImageMismatch_ErrorType).
                 this->fJsonActualResults_FailureIgnored[testName] =
                     actualResults;
             } else {
-                if (kEmptyErrorBitfield != (result & kMissingExpectations_ErrorBitmask)) {
+                if (result.includes(kMissingExpectations_ErrorType)) {
                     // TODO: What about the case where there IS an
                     // expected image checksum, but that gm test
                     // doesn't actually run?  For now, those cases
@@ -683,7 +672,7 @@
                     this->fJsonActualResults_NoComparison[testName] =
                         actualResults;
                 }
-                if (kEmptyErrorBitfield != (result & kImageMismatch_ErrorBitmask)) {
+                if (result.includes(kImageMismatch_ErrorType)) {
                     this->fJsonActualResults_Failed[testName] = actualResults;
                 }
             }
@@ -716,15 +705,14 @@
      * @param actualBitmap bitmap generated by this run
      * @param pdf
      */
-    ErrorBitfield compare_test_results_to_stored_expectations(
+    ErrorCombination compare_test_results_to_stored_expectations(
         GM* gm, const ConfigData& gRec, const char writePath[],
         SkBitmap& actualBitmap, SkDynamicMemoryWStream* pdf) {
 
         SkString name = make_name(gm->shortName(), gRec.fName);
-        ErrorBitfield retval = kEmptyErrorBitfield;
+        ErrorCombination errors;
 
-        ExpectationsSource *expectationsSource =
-            this->fExpectationsSource.get();
+        ExpectationsSource *expectationsSource = this->fExpectationsSource.get();
         if (expectationsSource && (gRec.fFlags & kRead_ConfigFlag)) {
             /*
              * Get the expected results for this test, as one or more allowed
@@ -740,15 +728,15 @@
              * See comments above complete_bitmap() for more detail.
              */
             Expectations expectations = expectationsSource->get(name.c_str());
-            retval |= compare_to_expectations(expectations, actualBitmap,
-                                              name, "", true);
+            errors.add(compare_to_expectations(expectations, actualBitmap,
+                                               name, "", true));
         } else {
             // If we are running without expectations, we still want to
             // record the actual results.
             Checksum actualChecksum =
                 SkBitmapChecksummer::Compute64(actualBitmap);
             add_actual_results_to_json_summary(name.c_str(), actualChecksum,
-                                               kMissingExpectations_ErrorBitmask,
+                                               ErrorCombination(kMissingExpectations_ErrorType),
                                                false);
         }
 
@@ -757,11 +745,11 @@
         // we don't want to write out the actual bitmaps for all
         // renderModes of all tests!  That would be a lot of files.
         if (writePath && (gRec.fFlags & kWrite_ConfigFlag)) {
-            retval |= write_reference_image(gRec, writePath, "",
-                                            name, actualBitmap, pdf);
+            errors.add(write_reference_image(gRec, writePath, "",
+                                             name, actualBitmap, pdf));
         }
 
-        return retval;
+        return errors;
     }
 
     /**
@@ -773,7 +761,7 @@
      * @param actualBitmap actual bitmap generated by this run
      * @param referenceBitmap bitmap we expected to be generated
      */
-    ErrorBitfield compare_test_results_to_reference_bitmap(
+    ErrorCombination compare_test_results_to_reference_bitmap(
         GM* gm, const ConfigData& gRec, const char renderModeDescriptor [],
         SkBitmap& actualBitmap, const SkBitmap* referenceBitmap) {
 
@@ -835,20 +823,19 @@
 
     // Test: draw into a bitmap or pdf.
     // Depending on flags, possibly compare to an expected image.
-    ErrorBitfield test_drawing(GM* gm,
-                               const ConfigData& gRec,
-                               const char writePath [],
-                               GrContext* context,
-                               GrRenderTarget* rt,
-                               SkBitmap* bitmap) {
+    ErrorCombination test_drawing(GM* gm,
+                                  const ConfigData& gRec,
+                                  const char writePath [],
+                                  GrContext* context,
+                                  GrRenderTarget* rt,
+                                  SkBitmap* bitmap) {
         SkDynamicMemoryWStream document;
 
         if (gRec.fBackend == kRaster_Backend ||
             gRec.fBackend == kGPU_Backend) {
             // Early exit if we can't generate the image.
-            ErrorBitfield errors = generate_image(gm, gRec, context, rt, bitmap,
-                                                  false);
-            if (kEmptyErrorBitfield != errors) {
+            ErrorCombination errors = generate_image(gm, gRec, context, rt, bitmap, false);
+            if (!errors.isEmpty()) {
                 // TODO: Add a test to exercise what the stdout and
                 // JSON look like if we get an "early error" while
                 // trying to generate the image.
@@ -868,11 +855,11 @@
             gm, gRec, writePath, *bitmap, &document);
     }
 
-    ErrorBitfield test_deferred_drawing(GM* gm,
-                                        const ConfigData& gRec,
-                                        const SkBitmap& referenceBitmap,
-                                        GrContext* context,
-                                        GrRenderTarget* rt) {
+    ErrorCombination test_deferred_drawing(GM* gm,
+                                           const ConfigData& gRec,
+                                           const SkBitmap& referenceBitmap,
+                                           GrContext* context,
+                                           GrRenderTarget* rt) {
         SkDynamicMemoryWStream document;
 
         if (gRec.fBackend == kRaster_Backend ||
@@ -880,19 +867,31 @@
             SkBitmap bitmap;
             // Early exit if we can't generate the image, but this is
             // expected in some cases, so don't report a test failure.
-            if (!generate_image(gm, gRec, context, rt, &bitmap, true)) {
-                return kEmptyErrorBitfield;
+            ErrorCombination errors = generate_image(gm, gRec, context, rt, &bitmap, true);
+            // TODO(epoger): This logic is the opposite of what is
+            // described above... if we succeeded in generating the
+            // -deferred image, we exit early!  We should fix this
+            // ASAP, because it is hiding -deferred errors... but for
+            // now, I'm leaving the logic as it is so that the
+            // refactoring change
+            // https://codereview.chromium.org/12992003/ is unblocked.
+            //
+            // Filed as https://code.google.com/p/skia/issues/detail?id=1180
+            // ('image-surface gm test is failing in "deferred" mode,
+            // and gm is not reporting the failure')
+            if (errors.isEmpty()) {
+                return kEmpty_ErrorCombination;
             }
             return compare_test_results_to_reference_bitmap(
                 gm, gRec, "-deferred", bitmap, &referenceBitmap);
         }
-        return kEmptyErrorBitfield;
+        return kEmpty_ErrorCombination;
     }
 
-    ErrorBitfield test_pipe_playback(GM* gm,
-                                     const ConfigData& gRec,
-                                     const SkBitmap& referenceBitmap) {
-        ErrorBitfield errors = kEmptyErrorBitfield;
+    ErrorCombination test_pipe_playback(GM* gm,
+                                        const ConfigData& gRec,
+                                        const SkBitmap& referenceBitmap) {
+        ErrorCombination errors;
         for (size_t i = 0; i < SK_ARRAY_COUNT(gPipeWritingFlagCombos); ++i) {
             SkBitmap bitmap;
             SkISize size = gm->getISize();
@@ -908,18 +907,18 @@
             writer.endRecording();
             SkString string("-pipe");
             string.append(gPipeWritingFlagCombos[i].name);
-            errors |= compare_test_results_to_reference_bitmap(
-                gm, gRec, string.c_str(), bitmap, &referenceBitmap);
-            if (errors != kEmptyErrorBitfield) {
+            errors.add(compare_test_results_to_reference_bitmap(
+                gm, gRec, string.c_str(), bitmap, &referenceBitmap));
+            if (!errors.isEmpty()) {
                 break;
             }
         }
         return errors;
     }
 
-    ErrorBitfield test_tiled_pipe_playback(
-      GM* gm, const ConfigData& gRec, const SkBitmap& referenceBitmap) {
-        ErrorBitfield errors = kEmptyErrorBitfield;
+    ErrorCombination test_tiled_pipe_playback(GM* gm, const ConfigData& gRec,
+                                              const SkBitmap& referenceBitmap) {
+        ErrorCombination errors;
         for (size_t i = 0; i < SK_ARRAY_COUNT(gPipeWritingFlagCombos); ++i) {
             SkBitmap bitmap;
             SkISize size = gm->getISize();
@@ -935,9 +934,9 @@
             writer.endRecording();
             SkString string("-tiled pipe");
             string.append(gPipeWritingFlagCombos[i].name);
-            errors |= compare_test_results_to_reference_bitmap(
-                gm, gRec, string.c_str(), bitmap, &referenceBitmap);
-            if (errors != kEmptyErrorBitfield) {
+            errors.add(compare_test_results_to_reference_bitmap(
+                gm, gRec, string.c_str(), bitmap, &referenceBitmap));
+            if (!errors.isEmpty()) {
                 break;
             }
         }
@@ -950,6 +949,7 @@
     //
 
     bool fUseFileHierarchy;
+    ErrorCombination fIgnorableErrorCombination;
 
     const char* fMismatchPath;
 
@@ -1033,6 +1033,12 @@
     return result;
 }
 
+// Macro magic to convert a numeric preprocessor token into a string.
+// Adapted from http://stackoverflow.com/questions/240353/convert-a-preprocessor-token-to-a-string
+// This should probably be moved into one of our common headers...
+#define TOSTRING_INTERNAL(x) #x
+#define TOSTRING(x) TOSTRING_INTERNAL(x)
+
 // Alphabetized ignoring "no" prefix ("readPath", "noreplay", "resourcePath").
 DEFINE_string(config, "", configUsage().c_str());
 DEFINE_bool(deferred, true, "Exercise the deferred rendering test pass.");
@@ -1042,8 +1048,8 @@
 DEFINE_bool(forceBWtext, false, "Disable text anti-aliasing.");
 #if SK_SUPPORT_GPU
 DEFINE_string(gpuCacheSize, "", "<bytes> <count>: Limit the gpu cache to byte size or "
-              "object count. -1 for either value means use the default. 0 for either "
-              "disables the cache.");
+              "object count. " TOSTRING(DEFAULT_CACHE_VALUE) " for either value means "
+              "use the default. 0 for either disables the cache.");
 #endif
 DEFINE_bool(hierarchy, false, "Whether to use multilevel directory structure "
             "when reading/writing files.");
@@ -1149,29 +1155,13 @@
  *
  * Returns all errors encountered while doing so.
  */
-ErrorBitfield run_multiple_configs(GMMain &gmmain, GM *gm, const SkTDArray<size_t> &configs,
-                                   GrContextFactory *grFactory);
-ErrorBitfield run_multiple_configs(GMMain &gmmain, GM *gm, const SkTDArray<size_t> &configs,
-                                   GrContextFactory *grFactory) {
-    ErrorBitfield errorsForAllConfigs = kEmptyErrorBitfield;
+ErrorCombination run_multiple_configs(GMMain &gmmain, GM *gm, const SkTDArray<size_t> &configs,
+                                      GrContextFactory *grFactory);
+ErrorCombination run_multiple_configs(GMMain &gmmain, GM *gm, const SkTDArray<size_t> &configs,
+                                      GrContextFactory *grFactory) {
+    ErrorCombination errorsForAllConfigs;
     uint32_t gmFlags = gm->getFlags();
 
-#if SK_SUPPORT_GPU
-    struct {
-        int     fBytes;
-        int     fCount;
-    } gpuCacheSize = { -1, -1 }; // -1s mean use the default
-
-    if (FLAGS_gpuCacheSize.count() > 0) {
-        if (FLAGS_gpuCacheSize.count() != 2) {
-            gm_fprintf(stderr, "--gpuCacheSize requires two arguments\n");
-            return -1;
-        }
-        gpuCacheSize.fBytes = atoi(FLAGS_gpuCacheSize[0]);
-        gpuCacheSize.fCount = atoi(FLAGS_gpuCacheSize[1]);
-    }
-#endif
-
     for (int i = 0; i < configs.count(); i++) {
         ConfigData config = gRec[configs[i]];
 
@@ -1192,12 +1182,12 @@
 
         // Now we know that we want to run this test and record its
         // success or failure.
-        ErrorBitfield errorsForThisConfig = kEmptyErrorBitfield;
+        ErrorCombination errorsForThisConfig;
         GrRenderTarget* renderTarget = NULL;
 #if SK_SUPPORT_GPU
         SkAutoTUnref<GrRenderTarget> rt;
         AutoResetGr autogr;
-        if ((kEmptyErrorBitfield == errorsForThisConfig) && (kGPU_Backend == config.fBackend)) {
+        if ((errorsForThisConfig.isEmpty()) && (kGPU_Backend == config.fBackend)) {
             GrContext* gr = grFactory->get(config.fGLContextType);
             bool grSuccess = false;
             if (gr) {
@@ -1221,16 +1211,16 @@
                 size_t bytes;
                 int count;
                 gr->getTextureCacheLimits(&count, &bytes);
-                if (-1 != gpuCacheSize.fBytes) {
-                    bytes = static_cast<size_t>(gpuCacheSize.fBytes);
+                if (DEFAULT_CACHE_VALUE != gGpuCacheSizeBytes) {
+                    bytes = static_cast<size_t>(gGpuCacheSizeBytes);
                 }
-                if (-1 != gpuCacheSize.fCount) {
-                    count = gpuCacheSize.fCount;
+                if (DEFAULT_CACHE_VALUE != gGpuCacheSizeCount) {
+                    count = gGpuCacheSizeCount;
                 }
                 gr->setTextureCacheLimits(count, bytes);
             }
             if (!grSuccess) {
-                errorsForThisConfig |= kNoGpuContext_ErrorBitmask;
+                errorsForThisConfig.add(kNoGpuContext_ErrorType);
             }
         }
 #endif
@@ -1243,19 +1233,18 @@
         } else {
             writePath = NULL;
         }
-        if (kEmptyErrorBitfield == errorsForThisConfig) {
-            errorsForThisConfig |= gmmain.test_drawing(gm, config, writePath, GetGr(),
-                                                       renderTarget, &comparisonBitmap);
+        if (errorsForThisConfig.isEmpty()) {
+            errorsForThisConfig.add(gmmain.test_drawing(gm, config, writePath, GetGr(),
+                                                        renderTarget, &comparisonBitmap));
         }
 
-        if (FLAGS_deferred && !errorsForThisConfig &&
-            (kGPU_Backend == config.fBackend ||
-             kRaster_Backend == config.fBackend)) {
-            errorsForThisConfig |= gmmain.test_deferred_drawing(gm, config, comparisonBitmap,
-                                                                GetGr(), renderTarget);
+        if (FLAGS_deferred && errorsForThisConfig.isEmpty() &&
+            (kGPU_Backend == config.fBackend || kRaster_Backend == config.fBackend)) {
+            errorsForThisConfig.add(gmmain.test_deferred_drawing(gm, config, comparisonBitmap,
+                                                                 GetGr(), renderTarget));
         }
 
-        errorsForAllConfigs |= errorsForThisConfig;
+        errorsForAllConfigs.add(errorsForThisConfig);
     }
     return errorsForAllConfigs;
 }
@@ -1267,34 +1256,19 @@
  *
  * Returns all errors encountered while doing so.
  */
-ErrorBitfield run_multiple_modes(GMMain &gmmain, GM *gm, const ConfigData &compareConfig,
-                                 const SkBitmap &comparisonBitmap);
-ErrorBitfield run_multiple_modes(GMMain &gmmain, GM *gm, const ConfigData &compareConfig,
-                                 const SkBitmap &comparisonBitmap) {
-    SkTDArray<SkScalar> tileGridReplayScales;
-    *tileGridReplayScales.append() = SK_Scalar1; // By default only test at scale 1.0
-    if (FLAGS_tileGridReplayScales.count() > 0) {
-        tileGridReplayScales.reset();
-        for (int i = 0; i < FLAGS_tileGridReplayScales.count(); i++) {
-            double val = atof(FLAGS_tileGridReplayScales[i]);
-            if (0 < val) {
-                *tileGridReplayScales.append() = SkDoubleToScalar(val);
-            }
-        }
-        if (0 == tileGridReplayScales.count()) {
-            // Should have at least one scale
-            gm_fprintf(stderr, "--tileGridReplayScales requires at least one scale.\n");
-            return -1;
-        }
-    }
-
-    ErrorBitfield errorsForAllModes = kEmptyErrorBitfield;
+ErrorCombination run_multiple_modes(GMMain &gmmain, GM *gm, const ConfigData &compareConfig,
+                                    const SkBitmap &comparisonBitmap,
+                                    const SkTDArray<SkScalar> &tileGridReplayScales);
+ErrorCombination run_multiple_modes(GMMain &gmmain, GM *gm, const ConfigData &compareConfig,
+                                    const SkBitmap &comparisonBitmap,
+                                    const SkTDArray<SkScalar> &tileGridReplayScales) {
+    ErrorCombination errorsForAllModes;
     uint32_t gmFlags = gm->getFlags();
 
     // run the picture centric GM steps
     if (!(gmFlags & GM::kSkipPicture_Flag)) {
 
-        ErrorBitfield pictErrors = kEmptyErrorBitfield;
+        ErrorCombination pictErrors;
 
         //SkAutoTUnref<SkPicture> pict(generate_new_picture(gm));
         SkPicture* pict = gmmain.generate_new_picture(gm, kNone_BbhType, 0);
@@ -1303,18 +1277,18 @@
         if (FLAGS_replay) {
             SkBitmap bitmap;
             gmmain.generate_image_from_picture(gm, compareConfig, pict, &bitmap);
-            pictErrors |= gmmain.compare_test_results_to_reference_bitmap(
-                gm, compareConfig, "-replay", bitmap, &comparisonBitmap);
+            pictErrors.add(gmmain.compare_test_results_to_reference_bitmap(
+                gm, compareConfig, "-replay", bitmap, &comparisonBitmap));
         }
 
-        if ((kEmptyErrorBitfield == pictErrors) && FLAGS_serialize) {
+        if ((pictErrors.isEmpty()) && FLAGS_serialize) {
             SkPicture* repict = gmmain.stream_to_new_picture(*pict);
             SkAutoUnref aurr(repict);
 
             SkBitmap bitmap;
             gmmain.generate_image_from_picture(gm, compareConfig, repict, &bitmap);
-            pictErrors |= gmmain.compare_test_results_to_reference_bitmap(
-                gm, compareConfig, "-serialize", bitmap, &comparisonBitmap);
+            pictErrors.add(gmmain.compare_test_results_to_reference_bitmap(
+                gm, compareConfig, "-serialize", bitmap, &comparisonBitmap));
         }
 
         if (FLAGS_writePicturePath.count() == 1) {
@@ -1325,7 +1299,7 @@
             pict->serialize(&stream);
         }
 
-        errorsForAllModes |= pictErrors;
+        errorsForAllModes.add(pictErrors);
     }
 
     // TODO: add a test in which the RTree rendering results in a
@@ -1338,8 +1312,8 @@
         SkAutoUnref aur(pict);
         SkBitmap bitmap;
         gmmain.generate_image_from_picture(gm, compareConfig, pict, &bitmap);
-        errorsForAllModes |= gmmain.compare_test_results_to_reference_bitmap(
-            gm, compareConfig, "-rtree", bitmap, &comparisonBitmap);
+        errorsForAllModes.add(gmmain.compare_test_results_to_reference_bitmap(
+            gm, compareConfig, "-rtree", bitmap, &comparisonBitmap));
     }
 
     if (!(gmFlags & GM::kSkipPicture_Flag) && FLAGS_tileGrid) {
@@ -1361,26 +1335,26 @@
                 suffix += "-scale-";
                 suffix.appendScalar(replayScale);
             }
-            errorsForAllModes |= gmmain.compare_test_results_to_reference_bitmap(
-                gm, compareConfig, suffix.c_str(), bitmap, &comparisonBitmap);
+            errorsForAllModes.add(gmmain.compare_test_results_to_reference_bitmap(
+                gm, compareConfig, suffix.c_str(), bitmap, &comparisonBitmap));
         }
     }
 
     // run the pipe centric GM steps
     if (!(gmFlags & GM::kSkipPipe_Flag)) {
 
-        ErrorBitfield pipeErrors = kEmptyErrorBitfield;
+        ErrorCombination pipeErrors;
 
         if (FLAGS_pipe) {
-            pipeErrors |= gmmain.test_pipe_playback(gm, compareConfig, comparisonBitmap);
+            pipeErrors.add(gmmain.test_pipe_playback(gm, compareConfig, comparisonBitmap));
         }
 
-        if ((kEmptyErrorBitfield == pipeErrors) &&
+        if ((pipeErrors.isEmpty()) &&
             FLAGS_tiledPipe && !(gmFlags & GM::kSkipTiled_Flag)) {
-            pipeErrors |= gmmain.test_tiled_pipe_playback(gm, compareConfig, comparisonBitmap);
+            pipeErrors.add(gmmain.test_tiled_pipe_playback(gm, compareConfig, comparisonBitmap));
         }
 
-        errorsForAllModes |= pipeErrors;
+        errorsForAllModes.add(pipeErrors);
     }
     return errorsForAllModes;
 }
@@ -1446,6 +1420,37 @@
         }
     }
 
+#if SK_SUPPORT_GPU
+    if (FLAGS_gpuCacheSize.count() > 0) {
+        if (FLAGS_gpuCacheSize.count() != 2) {
+            gm_fprintf(stderr, "--gpuCacheSize requires two arguments\n");
+            return -1;
+        }
+        gGpuCacheSizeBytes = atoi(FLAGS_gpuCacheSize[0]);
+        gGpuCacheSizeCount = atoi(FLAGS_gpuCacheSize[1]);
+    } else {
+        gGpuCacheSizeBytes = DEFAULT_CACHE_VALUE;
+        gGpuCacheSizeCount = DEFAULT_CACHE_VALUE;
+    }
+#endif
+
+    SkTDArray<SkScalar> tileGridReplayScales;
+    *tileGridReplayScales.append() = SK_Scalar1; // By default only test at scale 1.0
+    if (FLAGS_tileGridReplayScales.count() > 0) {
+        tileGridReplayScales.reset();
+        for (int i = 0; i < FLAGS_tileGridReplayScales.count(); i++) {
+            double val = atof(FLAGS_tileGridReplayScales[i]);
+            if (0 < val) {
+                *tileGridReplayScales.append() = SkDoubleToScalar(val);
+            }
+        }
+        if (0 == tileGridReplayScales.count()) {
+            // Should have at least one scale
+            gm_fprintf(stderr, "--tileGridReplayScales requires at least one scale.\n");
+            return -1;
+        }
+    }
+
     if (!userConfig) {
         // if no config is specified by user, add the defaults
         for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); ++i) {
@@ -1584,17 +1589,19 @@
         gm_fprintf(stdout, "%sdrawing... %s [%d %d]\n", moduloStr.c_str(), shortName,
                    size.width(), size.height());
 
-        ErrorBitfield testErrors = kEmptyErrorBitfield;
-        testErrors |= run_multiple_configs(gmmain, gm, configs, grFactory);
+        ErrorCombination testErrors;
+        testErrors.add(run_multiple_configs(gmmain, gm, configs, grFactory));
 
         SkBitmap comparisonBitmap;
         const ConfigData compareConfig =
             { SkBitmap::kARGB_8888_Config, kRaster_Backend, kDontCare_GLContextType, 0, kRW_ConfigFlag, "comparison", false };
-        testErrors |= gmmain.generate_image(gm, compareConfig, NULL, NULL, &comparisonBitmap, false);
+        testErrors.add(gmmain.generate_image(
+            gm, compareConfig, NULL, NULL, &comparisonBitmap, false));
 
         // TODO(epoger): only run this if gmmain.generate_image() succeeded?
         // Otherwise, what are we comparing against?
-        testErrors |= run_multiple_modes(gmmain, gm, compareConfig, comparisonBitmap);
+        testErrors.add(run_multiple_modes(gmmain, gm, compareConfig, comparisonBitmap,
+                                          tileGridReplayScales));
 
         // Update overall results.
         // We only tabulate the particular error types that we currently
@@ -1602,10 +1609,10 @@
         // want to also tabulate other error types, we can do so.
         testsRun++;
         if (!gmmain.fExpectationsSource.get() ||
-            (kEmptyErrorBitfield != (kMissingExpectations_ErrorBitmask & testErrors))) {
+            (testErrors.includes(kMissingExpectations_ErrorType))) {
             testsMissingReferenceImages++;
         }
-        if (testErrors == (testErrors & kIgnorable_ErrorBitmask)) {
+        if (testErrors.minus(gmmain.fIgnorableErrorCombination).isEmpty()) {
             testsPassed++;
         } else {
             testsFailed++;