1d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary/*
2d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary * Copyright 2017 Google Inc.
3d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary *
4d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary * Use of this source code is governed by a BSD-style license that can be
5d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary * found in the LICENSE file.
6d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary */
7d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary
8d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#include "gm_knowledge.h"
9d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary
10d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#include <cfloat>
112a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary#include <cstdlib>
12d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#include <fstream>
132a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary#include <mutex>
14d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#include <sstream>
15d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#include <string>
16d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#include <vector>
17d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary
18d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#include "../../src/core/SkStreamPriv.h"
192a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary#include "../../src/core/SkTSort.h"
20d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#include "SkBitmap.h"
21d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#include "SkCodec.h"
22d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#include "SkOSFile.h"
232a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary#include "SkOSPath.h"
24d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#include "SkPngEncoder.h"
25d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#include "SkStream.h"
26d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary
27d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#include "skqp_asset_manager.h"
28d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary
29a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary#define IMAGES_DIRECTORY_PATH "images"
30d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#define PATH_MAX_PNG "max.png"
31d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#define PATH_MIN_PNG "min.png"
32d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#define PATH_IMG_PNG "image.png"
33d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#define PATH_ERR_PNG "errors.png"
34d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary#define PATH_REPORT  "report.html"
35a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary#define PATH_CSV     "out.csv"
36d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary
372331c82e0d10ee519d9afb3f9e85485c6cf0b3c3Hal Canary#ifndef SK_SKQP_GLOBAL_ERROR_TOLERANCE
382331c82e0d10ee519d9afb3f9e85485c6cf0b3c3Hal Canary#define SK_SKQP_GLOBAL_ERROR_TOLERANCE 0
392331c82e0d10ee519d9afb3f9e85485c6cf0b3c3Hal Canary#endif
402331c82e0d10ee519d9afb3f9e85485c6cf0b3c3Hal Canary
41d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary////////////////////////////////////////////////////////////////////////////////
42d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary
43d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canarystatic int get_error(uint32_t value, uint32_t value_max, uint32_t value_min) {
44d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    int error = 0;
45d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    for (int j : {0, 8, 16, 24}) {
46d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        uint8_t    v = (value     >> j) & 0xFF,
47d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary                vmin = (value_min >> j) & 0xFF,
48d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary                vmax = (value_max >> j) & 0xFF;
49d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        if (v > vmax) {
50d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary            error = std::max(v - vmax, error);
51d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        } else if (v < vmin) {
52d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary            error = std::max(vmin - v, error);
53d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        }
54d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    }
552331c82e0d10ee519d9afb3f9e85485c6cf0b3c3Hal Canary    return std::max(0, error - SK_SKQP_GLOBAL_ERROR_TOLERANCE);
56d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary}
57d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary
5851494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canarystatic int get_error_with_nearby(int x, int y, const SkPixmap& pm,
5951494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                                 const SkPixmap& pm_max, const SkPixmap& pm_min) {
6051494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary    struct NearbyPixels {
6151494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary        const int x, y, w, h;
6251494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary        struct Iter {
6351494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary            const int x, y, w, h;
6451494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary            int8_t curr;
6551494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary            SkIPoint operator*() const { return this->get(); }
6651494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary            SkIPoint get() const {
6751494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                switch (curr) {
6851494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                    case 0: return {x - 1, y - 1};
6951494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                    case 1: return {x    , y - 1};
7051494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                    case 2: return {x + 1, y - 1};
7151494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                    case 3: return {x - 1, y    };
7251494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                    case 4: return {x + 1, y    };
7351494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                    case 5: return {x - 1, y + 1};
7451494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                    case 6: return {x    , y + 1};
7551494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                    case 7: return {x + 1, y + 1};
7651494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                    default: SkASSERT(false); return {0, 0};
7751494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                }
7851494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary            }
7951494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary            void skipBad() {
80c126191fd1f540f7318516a965c85a5bff07dad1Hal Canary                while (curr < 8) {
8151494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                    SkIPoint p = this->get();
8251494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                    if (p.x() >= 0 && p.y() >= 0 && p.x() < w && p.y() < h) {
8351494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                        return;
8451494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                    }
8551494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                    ++curr;
86c126191fd1f540f7318516a965c85a5bff07dad1Hal Canary                }
8751494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                curr = -1;
8851494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary            }
8951494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary            void operator++() {
9051494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                if (-1 == curr) { return; }
9151494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                ++curr;
9251494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                this->skipBad();
9351494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary            }
9451494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary            bool operator!=(const Iter& other) const { return curr != other.curr; }
9551494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary        };
9651494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary        Iter begin() const { Iter i{x, y, w, h, 0}; i.skipBad(); return i; }
9751494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary        Iter end() const { return Iter{x, y, w, h, -1}; }
9851494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary    };
9951494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary
10051494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary    uint32_t c = *pm.addr32(x, y);
10151494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary    int error = get_error(c, *pm_max.addr32(x, y), *pm_min.addr32(x, y));
10251494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary    for (SkIPoint p : NearbyPixels{x, y, pm.width(), pm.height()}) {
10351494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary        if (error == 0) {
10451494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary            return 0;
10551494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary        }
10651494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary        error = SkTMin(error, get_error(
10751494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                    c, *pm_max.addr32(p.x(), p.y()), *pm_min.addr32(p.x(), p.y())));
10851494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary    }
10951494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary    return error;
11051494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary}
11151494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary
112d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canarystatic float set_error_code(gmkb::Error* error_out, gmkb::Error error) {
113d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    SkASSERT(error != gmkb::Error::kNone);
114d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    if (error_out) {
115d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        *error_out = error;
116d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    }
117d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    return FLT_MAX;
118d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary}
119d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary
120d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canarystatic bool WritePixmapToFile(const SkPixmap& pixmap, const char* path) {
121d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    SkFILEWStream wStream(path);
122d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    SkPngEncoder::Options options;
123d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    options.fUnpremulBehavior = SkTransferFunctionBehavior::kIgnore;
124d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    return wStream.isValid() && SkPngEncoder::Encode(&wStream, pixmap, options);
125d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary}
126d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary
127d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canaryconstexpr SkColorType kColorType = kRGBA_8888_SkColorType;
128d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canaryconstexpr SkAlphaType kAlphaType = kUnpremul_SkAlphaType;
129d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary
130d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canarystatic SkPixmap rgba8888_to_pixmap(const uint32_t* pixels, int width, int height) {
131d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    SkImageInfo info = SkImageInfo::Make(width, height, kColorType, kAlphaType);
132d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    return SkPixmap(info, pixels, width * sizeof(uint32_t));
133d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary}
134d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary
135d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canarystatic bool copy(skqp::AssetManager* mgr, const char* path, const char* dst) {
136d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    if (mgr) {
137d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        if (auto stream = mgr->open(path)) {
138d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary            SkFILEWStream wStream(dst);
139d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary            return wStream.isValid() && SkStreamCopy(&wStream, stream.get());
140d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        }
141d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    }
142d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    return false;
143d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary}
144d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary
145d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canarystatic SkBitmap ReadPngRgba8888FromFile(skqp::AssetManager* assetManager, const char* path) {
146d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    SkBitmap bitmap;
147d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    if (auto codec = SkCodec::MakeFromStream(assetManager->open(path))) {
148d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        SkISize size = codec->getInfo().dimensions();
149d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        SkASSERT(!size.isEmpty());
150d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        SkImageInfo info = SkImageInfo::Make(size.width(), size.height(), kColorType, kAlphaType);
151d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        bitmap.allocPixels(info);
152d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        SkASSERT(bitmap.rowBytes() == (unsigned)bitmap.width() * sizeof(uint32_t));
153b69a2f66448603c26b0265201cec7161cf6d6f75Hal Canary        if (SkCodec::kSuccess != codec->getPixels(bitmap.pixmap())) {
154d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary            bitmap.reset();
155d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        }
156d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    }
157d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    return bitmap;
158d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary}
159d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary
1602a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canarynamespace {
1612a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canarystruct Run {
1622a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary    SkString fBackend;
1632a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary    SkString fGM;
1642a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary    int fMaxerror;
1652a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary    int fBadpixels;
1662a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary};
1672a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary}  // namespace
1682a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary
1692a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canarystatic std::vector<Run> gErrors;
1702a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canarystatic std::mutex gMutex;
1712a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary
172a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canarystatic SkString make_path(const SkString& images_directory,
173a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary                          const char* backend,
174a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary                          const char* gm_name,
175a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary                          const char* thing) {
176a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary    auto path = SkStringPrintf("%s_%s_%s", backend, gm_name, thing);
177a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary    return SkOSPath::Join(images_directory.c_str(), path.c_str());
178a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary}
179a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary
180a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary
181d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canarynamespace gmkb {
182d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canaryfloat Check(const uint32_t* pixels,
183d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary            int width,
184d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary            int height,
185d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary            const char* name,
186d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary            const char* backend,
187d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary            skqp::AssetManager* assetManager,
188d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary            const char* report_directory_path,
189d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary            Error* error_out) {
190a9de760a217cf48c974d6c51b4ba88f08c269bbeHal Canary    if (report_directory_path && report_directory_path[0]) {
191a9de760a217cf48c974d6c51b4ba88f08c269bbeHal Canary        SkASSERT_RELEASE(sk_isdir(report_directory_path));
192a9de760a217cf48c974d6c51b4ba88f08c269bbeHal Canary    }
193d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    if (width <= 0 || height <= 0) {
194d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        return set_error_code(error_out, Error::kBadInput);
195d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    }
196a9de760a217cf48c974d6c51b4ba88f08c269bbeHal Canary    constexpr char PATH_ROOT[] = "gmkb";
197a9de760a217cf48c974d6c51b4ba88f08c269bbeHal Canary    SkString img_path = SkOSPath::Join(PATH_ROOT, name);
198a9de760a217cf48c974d6c51b4ba88f08c269bbeHal Canary    SkString max_path = SkOSPath::Join(img_path.c_str(), PATH_MAX_PNG);
199a9de760a217cf48c974d6c51b4ba88f08c269bbeHal Canary    SkString min_path = SkOSPath::Join(img_path.c_str(), PATH_MIN_PNG);
200d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    SkBitmap max_image = ReadPngRgba8888FromFile(assetManager, max_path.c_str());
201d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    SkBitmap min_image = ReadPngRgba8888FromFile(assetManager, min_path.c_str());
202a9de760a217cf48c974d6c51b4ba88f08c269bbeHal Canary    if (max_image.isNull() || min_image.isNull()) {
203a9de760a217cf48c974d6c51b4ba88f08c269bbeHal Canary        // No data.
204a9de760a217cf48c974d6c51b4ba88f08c269bbeHal Canary        if (error_out) {
205a9de760a217cf48c974d6c51b4ba88f08c269bbeHal Canary            *error_out = Error::kNone;
206a9de760a217cf48c974d6c51b4ba88f08c269bbeHal Canary        }
207a9de760a217cf48c974d6c51b4ba88f08c269bbeHal Canary        return 0;
208d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    }
209d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    if (max_image.width()  != min_image.width() ||
210d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        max_image.height() != min_image.height())
211d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    {
212d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        return set_error_code(error_out, Error::kBadData);
213d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    }
214d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    if (max_image.width() != width || max_image.height() != height) {
215d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        return set_error_code(error_out, Error::kBadInput);
216d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    }
21751494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary
218d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    int badness = 0;
219d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    int badPixelCount = 0;
22051494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary    SkPixmap pm(SkImageInfo::Make(width, height, kColorType, kAlphaType),
22151494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                pixels, width * sizeof(uint32_t));
22251494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary    SkPixmap pm_max = max_image.pixmap();
22351494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary    SkPixmap pm_min = min_image.pixmap();
22451494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary    for (int y = 0; y < pm.height(); ++y) {
22551494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary        for (int x = 0; x < pm.width(); ++x) {
22651494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary            int error = get_error_with_nearby(x, y, pm, pm_max, pm_min) ;
22751494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary            if (error > 0) {
22851494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                badness = SkTMax(error, badness);
22951494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                ++badPixelCount;
23051494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary            }
231d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        }
232d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    }
23351494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary
2343de24bf20cd9bff6ecbe8d262b596658642ab7c3Hal Canary    if (badness == 0) {
2353de24bf20cd9bff6ecbe8d262b596658642ab7c3Hal Canary        std::lock_guard<std::mutex> lock(gMutex);
2363de24bf20cd9bff6ecbe8d262b596658642ab7c3Hal Canary        gErrors.push_back(Run{SkString(backend), SkString(name), 0, 0});
2373de24bf20cd9bff6ecbe8d262b596658642ab7c3Hal Canary    }
238d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    if (report_directory_path && badness > 0 && report_directory_path[0] != '\0') {
239d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        if (!backend) {
240d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary            backend = "skia";
241d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        }
242a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary        SkString images_directory = SkOSPath::Join(report_directory_path, IMAGES_DIRECTORY_PATH);
243a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary        sk_mkdir(images_directory.c_str());
244a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary
245a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary        SkString image_path   = make_path(images_directory, backend, name, PATH_IMG_PNG);
246a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary        SkString error_path   = make_path(images_directory, backend, name, PATH_ERR_PNG);
247a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary        SkString max_path_out = make_path(images_directory, backend, name, PATH_MAX_PNG);
248a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary        SkString min_path_out = make_path(images_directory, backend, name, PATH_MIN_PNG);
249a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary
250d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        SkAssertResult(WritePixmapToFile(rgba8888_to_pixmap(pixels, width, height),
251a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary                                         image_path.c_str()));
252a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary
253d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        SkBitmap errorBitmap;
254d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        errorBitmap.allocPixels(SkImageInfo::Make(width, height, kColorType, kAlphaType));
25551494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary        for (int y = 0; y < pm.height(); ++y) {
25651494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary            for (int x = 0; x < pm.width(); ++x) {
25751494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                int error = get_error_with_nearby(x, y, pm, pm_max, pm_min);
25851494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                *errorBitmap.getAddr32(x, y) =
25951494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary                         error > 0 ? 0xFF000000 + (unsigned)error : 0xFFFFFFFF;
26051494f6615b8bd637caf0fd6d0d577ea19d50948Hal Canary            }
261d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        }
262b69a2f66448603c26b0265201cec7161cf6d6f75Hal Canary        SkAssertResult(WritePixmapToFile(errorBitmap.pixmap(), error_path.c_str()));
263d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary
264d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        (void)copy(assetManager, max_path.c_str(), max_path_out.c_str());
265d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary        (void)copy(assetManager, min_path.c_str(), min_path_out.c_str());
266d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary
2672a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary        std::lock_guard<std::mutex> lock(gMutex);
2682a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary        gErrors.push_back(Run{SkString(backend), SkString(name), badness, badPixelCount});
2692a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary    }
2702a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary    if (error_out) {
2712a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary        *error_out = Error::kNone;
2722a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary    }
2732a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary    return (float)badness;
2742a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary}
2752a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary
276c341d3829d47e1688677981874c1a27ff00096f7Hal Canarystatic constexpr char kDocHead[] =
277c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "<!doctype html>\n"
278c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "<html lang=\"en\">\n"
279c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "<head>\n"
280c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "<meta charset=\"UTF-8\">\n"
281c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "<title>SkQP Report</title>\n"
282c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "<style>\n"
283b0c427b1f286524aa3bfd497056b0626c84a20b9Hal Canary    "img { max-width:48%; border:1px green solid;\n"
2849c17391cef1b0903a3240a50557e20fe68a7f42dHal Canary    "      image-rendering: pixelated;\n"
285b0c427b1f286524aa3bfd497056b0626c84a20b9Hal Canary    "      background-image:url('data:image/png;base64,iVBORw0KGgoA"
286c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "AAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAAAXNSR0IArs4c6QAAAAJiS0dEAP+H"
287c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "j8y/AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAB3RJTUUH3gUBEi4DGRAQYgAAAB1J"
288c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "REFUGNNjfMoAAVJQmokBDdBHgPE/lPFsYN0BABdaAwN6tehMAAAAAElFTkSuQmCC"
289c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "'); }\n"
290c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "</style>\n"
291c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "<script>\n"
292c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "function ce(t) { return document.createElement(t); }\n"
293c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "function ct(n) { return document.createTextNode(n); }\n"
294c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "function ac(u,v) { return u.appendChild(v); }\n"
295c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "function br(u) { ac(u, ce(\"br\")); }\n"
296c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "function ma(s, c) { var a = ce(\"a\"); a.href = s; ac(a, c); return a; }\n"
297c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "function f(backend, gm, e1, e2) {\n"
298c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  var b = ce(\"div\");\n"
299c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  var x = ce(\"h2\");\n"
300c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  var t = backend + \"/\" + gm;\n"
301c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  ac(x, ct(t));\n"
302c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  ac(b, x);\n"
303c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  ac(b, ct(\"backend: \" + backend));\n"
304c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  br(b);\n"
305c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  ac(b, ct(\"gm name: \" + gm));\n"
306c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  br(b);\n"
307c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  ac(b, ct(\"maximum error: \" + e1));\n"
308c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  br(b);\n"
309c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  ac(b, ct(\"bad pixel counts: \" + e2));\n"
310c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  br(b);\n"
311a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary    "  var q = \"" IMAGES_DIRECTORY_PATH "/\" + backend + \"_\" + gm + \"_\";\n"
312c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  var i = ce(\"img\");\n"
313a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary    "  i.src = q + \"" PATH_IMG_PNG "\";\n"
314c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  i.alt = \"img\";\n"
315c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  ac(b, ma(i.src, i));\n"
316c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  i = ce(\"img\");\n"
317a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary    "  i.src = q + \"" PATH_ERR_PNG "\";\n"
318a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary    "  i.alt = \"err\";\n"
319c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  ac(b, ma(i.src, i));\n"
320c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  br(b);\n"
321b0c427b1f286524aa3bfd497056b0626c84a20b9Hal Canary    "  ac(b, ct(\"Expectation: \"));\n"
322a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary    "  ac(b, ma(q + \"" PATH_MAX_PNG "\", ct(\"max\")));\n"
323181ec2f02f2efa822b0fba35feb74fc0ba3945f1Hal Canary    "  ac(b, ct(\" | \"));\n"
324a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary    "  ac(b, ma(q + \"" PATH_MIN_PNG "\", ct(\"min\")));\n"
325c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  ac(b, ce(\"hr\"));\n"
32675454f58f53fcd52dad64147dcb688d45ad04895Hal Canary    "  b.id = backend + \":\" + gm;\n"
327c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "  ac(document.body, b);\n"
32875454f58f53fcd52dad64147dcb688d45ad04895Hal Canary    "  l = ce(\"li\");\n"
329a3ca5f857a36ec16ce666c56adfa5c5800bb979aHal Canary    "  ac(l, ct(\"[\" + e1 + \"] \"));\n"
33075454f58f53fcd52dad64147dcb688d45ad04895Hal Canary    "  ac(l, ma(\"#\" + backend +\":\"+ gm , ct(t)));\n"
33175454f58f53fcd52dad64147dcb688d45ad04895Hal Canary    "  ac(document.getElementById(\"toc\"), l);\n"
332c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "}\n"
333181ec2f02f2efa822b0fba35feb74fc0ba3945f1Hal Canary    "function main() {\n";
334c341d3829d47e1688677981874c1a27ff00096f7Hal Canary
33575454f58f53fcd52dad64147dcb688d45ad04895Hal Canarystatic constexpr char kDocMiddle[] =
336c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "}\n"
337c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "</script>\n"
338c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "</head>\n"
339181ec2f02f2efa822b0fba35feb74fc0ba3945f1Hal Canary    "<body onload=\"main()\">\n"
34075454f58f53fcd52dad64147dcb688d45ad04895Hal Canary    "<h1>SkQP Report</h1>\n";
34175454f58f53fcd52dad64147dcb688d45ad04895Hal Canary
34275454f58f53fcd52dad64147dcb688d45ad04895Hal Canarystatic constexpr char kDocTail[] =
34375454f58f53fcd52dad64147dcb688d45ad04895Hal Canary    "<ul id=\"toc\"></ul>\n"
34475454f58f53fcd52dad64147dcb688d45ad04895Hal Canary    "<hr>\n"
345b0c427b1f286524aa3bfd497056b0626c84a20b9Hal Canary    "<p>Left image: test result<br>\n"
346b0c427b1f286524aa3bfd497056b0626c84a20b9Hal Canary    "Right image: errors (white = no error, black = smallest error, red = biggest error)</p>\n"
347c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "<hr>\n"
348c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "</body>\n"
349c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    "</html>\n";
350c341d3829d47e1688677981874c1a27ff00096f7Hal Canary
351c341d3829d47e1688677981874c1a27ff00096f7Hal Canarystatic void write(SkWStream* wStream, const SkString& text) {
352c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    wStream->write(text.c_str(), text.size());
353c341d3829d47e1688677981874c1a27ff00096f7Hal Canary}
354c341d3829d47e1688677981874c1a27ff00096f7Hal Canary
35575454f58f53fcd52dad64147dcb688d45ad04895Hal Canaryenum class Backend {
35675454f58f53fcd52dad64147dcb688d45ad04895Hal Canary    kUnknown,
35775454f58f53fcd52dad64147dcb688d45ad04895Hal Canary    kGLES,
35875454f58f53fcd52dad64147dcb688d45ad04895Hal Canary    kVulkan,
35975454f58f53fcd52dad64147dcb688d45ad04895Hal Canary};
36075454f58f53fcd52dad64147dcb688d45ad04895Hal Canary
36175454f58f53fcd52dad64147dcb688d45ad04895Hal Canarystatic Backend get_backend(const SkString& s) {
36275454f58f53fcd52dad64147dcb688d45ad04895Hal Canary    if (s.equals("gles")) {
36375454f58f53fcd52dad64147dcb688d45ad04895Hal Canary        return Backend::kGLES;
36475454f58f53fcd52dad64147dcb688d45ad04895Hal Canary    } else if (s.equals("vk")) {
36575454f58f53fcd52dad64147dcb688d45ad04895Hal Canary        return Backend::kVulkan;
36675454f58f53fcd52dad64147dcb688d45ad04895Hal Canary    }
36775454f58f53fcd52dad64147dcb688d45ad04895Hal Canary    return Backend::kUnknown;
36875454f58f53fcd52dad64147dcb688d45ad04895Hal Canary}
36975454f58f53fcd52dad64147dcb688d45ad04895Hal Canary
37075454f58f53fcd52dad64147dcb688d45ad04895Hal Canary
3712a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canarybool MakeReport(const char* report_directory_path) {
37275454f58f53fcd52dad64147dcb688d45ad04895Hal Canary    int glesErrorCount = 0, vkErrorCount = 0, gles = 0, vk = 0;
37375454f58f53fcd52dad64147dcb688d45ad04895Hal Canary
374a9de760a217cf48c974d6c51b4ba88f08c269bbeHal Canary    SkASSERT_RELEASE(sk_isdir(report_directory_path));
3753de24bf20cd9bff6ecbe8d262b596658642ab7c3Hal Canary    std::lock_guard<std::mutex> lock(gMutex);
376c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    SkFILEWStream csvOut(SkOSPath::Join(report_directory_path, PATH_CSV).c_str());
377c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    SkFILEWStream htmOut(SkOSPath::Join(report_directory_path, PATH_REPORT).c_str());
378c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    SkASSERT_RELEASE(csvOut.isValid());
379c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    if (!csvOut.isValid() || !htmOut.isValid()) {
3802a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary        return false;
3812a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary    }
382c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    htmOut.writeText(kDocHead);
3832a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary    for (const Run& run : gErrors) {
38475454f58f53fcd52dad64147dcb688d45ad04895Hal Canary        auto backend = get_backend(run.fBackend);
38575454f58f53fcd52dad64147dcb688d45ad04895Hal Canary        switch (backend) {
38675454f58f53fcd52dad64147dcb688d45ad04895Hal Canary            case Backend::kGLES: ++gles; break;
38775454f58f53fcd52dad64147dcb688d45ad04895Hal Canary            case Backend::kVulkan: ++vk; break;
38875454f58f53fcd52dad64147dcb688d45ad04895Hal Canary            default: break;
38975454f58f53fcd52dad64147dcb688d45ad04895Hal Canary        }
390c341d3829d47e1688677981874c1a27ff00096f7Hal Canary        write(&csvOut, SkStringPrintf("\"%s\",\"%s\",%d,%d\n",
391c341d3829d47e1688677981874c1a27ff00096f7Hal Canary                                      run.fBackend.c_str(), run.fGM.c_str(),
392c341d3829d47e1688677981874c1a27ff00096f7Hal Canary                                      run.fMaxerror, run.fBadpixels));
393c341d3829d47e1688677981874c1a27ff00096f7Hal Canary        if (run.fMaxerror == 0 && run.fBadpixels == 0) {
3943de24bf20cd9bff6ecbe8d262b596658642ab7c3Hal Canary            continue;
3953de24bf20cd9bff6ecbe8d262b596658642ab7c3Hal Canary        }
396c341d3829d47e1688677981874c1a27ff00096f7Hal Canary        write(&htmOut, SkStringPrintf("  f(\"%s\", \"%s\", %d, %d);\n",
397c341d3829d47e1688677981874c1a27ff00096f7Hal Canary                                      run.fBackend.c_str(), run.fGM.c_str(),
398c341d3829d47e1688677981874c1a27ff00096f7Hal Canary                                      run.fMaxerror, run.fBadpixels));
39975454f58f53fcd52dad64147dcb688d45ad04895Hal Canary        switch (backend) {
40075454f58f53fcd52dad64147dcb688d45ad04895Hal Canary            case Backend::kGLES: ++glesErrorCount; break;
40175454f58f53fcd52dad64147dcb688d45ad04895Hal Canary            case Backend::kVulkan: ++vkErrorCount; break;
40275454f58f53fcd52dad64147dcb688d45ad04895Hal Canary            default: break;
40375454f58f53fcd52dad64147dcb688d45ad04895Hal Canary        }
404d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary    }
40575454f58f53fcd52dad64147dcb688d45ad04895Hal Canary    htmOut.writeText(kDocMiddle);
40675454f58f53fcd52dad64147dcb688d45ad04895Hal Canary    write(&htmOut, SkStringPrintf("<p>gles errors: %d (of %d)</br>\n"
40775454f58f53fcd52dad64147dcb688d45ad04895Hal Canary                                  "vk errors: %d (of %d)</p>\n",
40875454f58f53fcd52dad64147dcb688d45ad04895Hal Canary                                  glesErrorCount, gles, vkErrorCount, vk));
409c341d3829d47e1688677981874c1a27ff00096f7Hal Canary    htmOut.writeText(kDocTail);
4102a7f0aa9ebfd66c902dcf1a0ca86ced9fde60d5aHal Canary    return true;
411d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary}
412d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0Hal Canary}  // namespace gmkb
413