1/* 2 * Copyright 2017 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#include "gm_knowledge.h" 9 10#include <cfloat> 11#include <cstdlib> 12#include <fstream> 13#include <mutex> 14#include <sstream> 15#include <string> 16#include <vector> 17 18#include "../../src/core/SkStreamPriv.h" 19#include "../../src/core/SkTSort.h" 20#include "SkBitmap.h" 21#include "SkCodec.h" 22#include "SkOSFile.h" 23#include "SkOSPath.h" 24#include "SkPngEncoder.h" 25#include "SkStream.h" 26 27#include "skqp_asset_manager.h" 28 29#define IMAGES_DIRECTORY_PATH "images" 30#define PATH_MAX_PNG "max.png" 31#define PATH_MIN_PNG "min.png" 32#define PATH_IMG_PNG "image.png" 33#define PATH_ERR_PNG "errors.png" 34#define PATH_REPORT "report.html" 35#define PATH_CSV "out.csv" 36 37#ifndef SK_SKQP_GLOBAL_ERROR_TOLERANCE 38#define SK_SKQP_GLOBAL_ERROR_TOLERANCE 0 39#endif 40 41//////////////////////////////////////////////////////////////////////////////// 42 43static int get_error(uint32_t value, uint32_t value_max, uint32_t value_min) { 44 int error = 0; 45 for (int j : {0, 8, 16, 24}) { 46 uint8_t v = (value >> j) & 0xFF, 47 vmin = (value_min >> j) & 0xFF, 48 vmax = (value_max >> j) & 0xFF; 49 if (v > vmax) { 50 error = std::max(v - vmax, error); 51 } else if (v < vmin) { 52 error = std::max(vmin - v, error); 53 } 54 } 55 return std::max(0, error - SK_SKQP_GLOBAL_ERROR_TOLERANCE); 56} 57 58static int get_error_with_nearby(int x, int y, const SkPixmap& pm, 59 const SkPixmap& pm_max, const SkPixmap& pm_min) { 60 struct NearbyPixels { 61 const int x, y, w, h; 62 struct Iter { 63 const int x, y, w, h; 64 int8_t curr; 65 SkIPoint operator*() const { return this->get(); } 66 SkIPoint get() const { 67 switch (curr) { 68 case 0: return {x - 1, y - 1}; 69 case 1: return {x , y - 1}; 70 case 2: return {x + 1, y - 1}; 71 case 3: return {x - 1, y }; 72 case 4: return {x + 1, y }; 73 case 5: return {x - 1, y + 1}; 74 case 6: return {x , y + 1}; 75 case 7: return {x + 1, y + 1}; 76 default: SkASSERT(false); return {0, 0}; 77 } 78 } 79 void skipBad() { 80 while (curr < 8) { 81 SkIPoint p = this->get(); 82 if (p.x() >= 0 && p.y() >= 0 && p.x() < w && p.y() < h) { 83 return; 84 } 85 ++curr; 86 } 87 curr = -1; 88 } 89 void operator++() { 90 if (-1 == curr) { return; } 91 ++curr; 92 this->skipBad(); 93 } 94 bool operator!=(const Iter& other) const { return curr != other.curr; } 95 }; 96 Iter begin() const { Iter i{x, y, w, h, 0}; i.skipBad(); return i; } 97 Iter end() const { return Iter{x, y, w, h, -1}; } 98 }; 99 100 uint32_t c = *pm.addr32(x, y); 101 int error = get_error(c, *pm_max.addr32(x, y), *pm_min.addr32(x, y)); 102 for (SkIPoint p : NearbyPixels{x, y, pm.width(), pm.height()}) { 103 if (error == 0) { 104 return 0; 105 } 106 error = SkTMin(error, get_error( 107 c, *pm_max.addr32(p.x(), p.y()), *pm_min.addr32(p.x(), p.y()))); 108 } 109 return error; 110} 111 112static float set_error_code(gmkb::Error* error_out, gmkb::Error error) { 113 SkASSERT(error != gmkb::Error::kNone); 114 if (error_out) { 115 *error_out = error; 116 } 117 return FLT_MAX; 118} 119 120static bool WritePixmapToFile(const SkPixmap& pixmap, const char* path) { 121 SkFILEWStream wStream(path); 122 SkPngEncoder::Options options; 123 options.fUnpremulBehavior = SkTransferFunctionBehavior::kIgnore; 124 return wStream.isValid() && SkPngEncoder::Encode(&wStream, pixmap, options); 125} 126 127constexpr SkColorType kColorType = kRGBA_8888_SkColorType; 128constexpr SkAlphaType kAlphaType = kUnpremul_SkAlphaType; 129 130static SkPixmap rgba8888_to_pixmap(const uint32_t* pixels, int width, int height) { 131 SkImageInfo info = SkImageInfo::Make(width, height, kColorType, kAlphaType); 132 return SkPixmap(info, pixels, width * sizeof(uint32_t)); 133} 134 135static bool copy(skqp::AssetManager* mgr, const char* path, const char* dst) { 136 if (mgr) { 137 if (auto stream = mgr->open(path)) { 138 SkFILEWStream wStream(dst); 139 return wStream.isValid() && SkStreamCopy(&wStream, stream.get()); 140 } 141 } 142 return false; 143} 144 145static SkBitmap ReadPngRgba8888FromFile(skqp::AssetManager* assetManager, const char* path) { 146 SkBitmap bitmap; 147 if (auto codec = SkCodec::MakeFromStream(assetManager->open(path))) { 148 SkISize size = codec->getInfo().dimensions(); 149 SkASSERT(!size.isEmpty()); 150 SkImageInfo info = SkImageInfo::Make(size.width(), size.height(), kColorType, kAlphaType); 151 bitmap.allocPixels(info); 152 SkASSERT(bitmap.rowBytes() == (unsigned)bitmap.width() * sizeof(uint32_t)); 153 if (SkCodec::kSuccess != codec->getPixels(bitmap.pixmap())) { 154 bitmap.reset(); 155 } 156 } 157 return bitmap; 158} 159 160namespace { 161struct Run { 162 SkString fBackend; 163 SkString fGM; 164 int fMaxerror; 165 int fBadpixels; 166}; 167} // namespace 168 169static std::vector<Run> gErrors; 170static std::mutex gMutex; 171 172static SkString make_path(const SkString& images_directory, 173 const char* backend, 174 const char* gm_name, 175 const char* thing) { 176 auto path = SkStringPrintf("%s_%s_%s", backend, gm_name, thing); 177 return SkOSPath::Join(images_directory.c_str(), path.c_str()); 178} 179 180 181namespace gmkb { 182float Check(const uint32_t* pixels, 183 int width, 184 int height, 185 const char* name, 186 const char* backend, 187 skqp::AssetManager* assetManager, 188 const char* report_directory_path, 189 Error* error_out) { 190 if (report_directory_path && report_directory_path[0]) { 191 SkASSERT_RELEASE(sk_isdir(report_directory_path)); 192 } 193 if (width <= 0 || height <= 0) { 194 return set_error_code(error_out, Error::kBadInput); 195 } 196 constexpr char PATH_ROOT[] = "gmkb"; 197 SkString img_path = SkOSPath::Join(PATH_ROOT, name); 198 SkString max_path = SkOSPath::Join(img_path.c_str(), PATH_MAX_PNG); 199 SkString min_path = SkOSPath::Join(img_path.c_str(), PATH_MIN_PNG); 200 SkBitmap max_image = ReadPngRgba8888FromFile(assetManager, max_path.c_str()); 201 SkBitmap min_image = ReadPngRgba8888FromFile(assetManager, min_path.c_str()); 202 if (max_image.isNull() || min_image.isNull()) { 203 // No data. 204 if (error_out) { 205 *error_out = Error::kNone; 206 } 207 return 0; 208 } 209 if (max_image.width() != min_image.width() || 210 max_image.height() != min_image.height()) 211 { 212 return set_error_code(error_out, Error::kBadData); 213 } 214 if (max_image.width() != width || max_image.height() != height) { 215 return set_error_code(error_out, Error::kBadInput); 216 } 217 218 int badness = 0; 219 int badPixelCount = 0; 220 SkPixmap pm(SkImageInfo::Make(width, height, kColorType, kAlphaType), 221 pixels, width * sizeof(uint32_t)); 222 SkPixmap pm_max = max_image.pixmap(); 223 SkPixmap pm_min = min_image.pixmap(); 224 for (int y = 0; y < pm.height(); ++y) { 225 for (int x = 0; x < pm.width(); ++x) { 226 int error = get_error_with_nearby(x, y, pm, pm_max, pm_min) ; 227 if (error > 0) { 228 badness = SkTMax(error, badness); 229 ++badPixelCount; 230 } 231 } 232 } 233 234 if (badness == 0) { 235 std::lock_guard<std::mutex> lock(gMutex); 236 gErrors.push_back(Run{SkString(backend), SkString(name), 0, 0}); 237 } 238 if (report_directory_path && badness > 0 && report_directory_path[0] != '\0') { 239 if (!backend) { 240 backend = "skia"; 241 } 242 SkString images_directory = SkOSPath::Join(report_directory_path, IMAGES_DIRECTORY_PATH); 243 sk_mkdir(images_directory.c_str()); 244 245 SkString image_path = make_path(images_directory, backend, name, PATH_IMG_PNG); 246 SkString error_path = make_path(images_directory, backend, name, PATH_ERR_PNG); 247 SkString max_path_out = make_path(images_directory, backend, name, PATH_MAX_PNG); 248 SkString min_path_out = make_path(images_directory, backend, name, PATH_MIN_PNG); 249 250 SkAssertResult(WritePixmapToFile(rgba8888_to_pixmap(pixels, width, height), 251 image_path.c_str())); 252 253 SkBitmap errorBitmap; 254 errorBitmap.allocPixels(SkImageInfo::Make(width, height, kColorType, kAlphaType)); 255 for (int y = 0; y < pm.height(); ++y) { 256 for (int x = 0; x < pm.width(); ++x) { 257 int error = get_error_with_nearby(x, y, pm, pm_max, pm_min); 258 *errorBitmap.getAddr32(x, y) = 259 error > 0 ? 0xFF000000 + (unsigned)error : 0xFFFFFFFF; 260 } 261 } 262 SkAssertResult(WritePixmapToFile(errorBitmap.pixmap(), error_path.c_str())); 263 264 (void)copy(assetManager, max_path.c_str(), max_path_out.c_str()); 265 (void)copy(assetManager, min_path.c_str(), min_path_out.c_str()); 266 267 std::lock_guard<std::mutex> lock(gMutex); 268 gErrors.push_back(Run{SkString(backend), SkString(name), badness, badPixelCount}); 269 } 270 if (error_out) { 271 *error_out = Error::kNone; 272 } 273 return (float)badness; 274} 275 276static constexpr char kDocHead[] = 277 "<!doctype html>\n" 278 "<html lang=\"en\">\n" 279 "<head>\n" 280 "<meta charset=\"UTF-8\">\n" 281 "<title>SkQP Report</title>\n" 282 "<style>\n" 283 "img { max-width:48%; border:1px green solid;\n" 284 " image-rendering: pixelated;\n" 285 " background-image:url('data:image/png;base64,iVBORw0KGgoA" 286 "AAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAAAXNSR0IArs4c6QAAAAJiS0dEAP+H" 287 "j8y/AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAB3RJTUUH3gUBEi4DGRAQYgAAAB1J" 288 "REFUGNNjfMoAAVJQmokBDdBHgPE/lPFsYN0BABdaAwN6tehMAAAAAElFTkSuQmCC" 289 "'); }\n" 290 "</style>\n" 291 "<script>\n" 292 "function ce(t) { return document.createElement(t); }\n" 293 "function ct(n) { return document.createTextNode(n); }\n" 294 "function ac(u,v) { return u.appendChild(v); }\n" 295 "function br(u) { ac(u, ce(\"br\")); }\n" 296 "function ma(s, c) { var a = ce(\"a\"); a.href = s; ac(a, c); return a; }\n" 297 "function f(backend, gm, e1, e2) {\n" 298 " var b = ce(\"div\");\n" 299 " var x = ce(\"h2\");\n" 300 " var t = backend + \"/\" + gm;\n" 301 " ac(x, ct(t));\n" 302 " ac(b, x);\n" 303 " ac(b, ct(\"backend: \" + backend));\n" 304 " br(b);\n" 305 " ac(b, ct(\"gm name: \" + gm));\n" 306 " br(b);\n" 307 " ac(b, ct(\"maximum error: \" + e1));\n" 308 " br(b);\n" 309 " ac(b, ct(\"bad pixel counts: \" + e2));\n" 310 " br(b);\n" 311 " var q = \"" IMAGES_DIRECTORY_PATH "/\" + backend + \"_\" + gm + \"_\";\n" 312 " var i = ce(\"img\");\n" 313 " i.src = q + \"" PATH_IMG_PNG "\";\n" 314 " i.alt = \"img\";\n" 315 " ac(b, ma(i.src, i));\n" 316 " i = ce(\"img\");\n" 317 " i.src = q + \"" PATH_ERR_PNG "\";\n" 318 " i.alt = \"err\";\n" 319 " ac(b, ma(i.src, i));\n" 320 " br(b);\n" 321 " ac(b, ct(\"Expectation: \"));\n" 322 " ac(b, ma(q + \"" PATH_MAX_PNG "\", ct(\"max\")));\n" 323 " ac(b, ct(\" | \"));\n" 324 " ac(b, ma(q + \"" PATH_MIN_PNG "\", ct(\"min\")));\n" 325 " ac(b, ce(\"hr\"));\n" 326 " b.id = backend + \":\" + gm;\n" 327 " ac(document.body, b);\n" 328 " l = ce(\"li\");\n" 329 " ac(l, ct(\"[\" + e1 + \"] \"));\n" 330 " ac(l, ma(\"#\" + backend +\":\"+ gm , ct(t)));\n" 331 " ac(document.getElementById(\"toc\"), l);\n" 332 "}\n" 333 "function main() {\n"; 334 335static constexpr char kDocMiddle[] = 336 "}\n" 337 "</script>\n" 338 "</head>\n" 339 "<body onload=\"main()\">\n" 340 "<h1>SkQP Report</h1>\n"; 341 342static constexpr char kDocTail[] = 343 "<ul id=\"toc\"></ul>\n" 344 "<hr>\n" 345 "<p>Left image: test result<br>\n" 346 "Right image: errors (white = no error, black = smallest error, red = biggest error)</p>\n" 347 "<hr>\n" 348 "</body>\n" 349 "</html>\n"; 350 351static void write(SkWStream* wStream, const SkString& text) { 352 wStream->write(text.c_str(), text.size()); 353} 354 355enum class Backend { 356 kUnknown, 357 kGLES, 358 kVulkan, 359}; 360 361static Backend get_backend(const SkString& s) { 362 if (s.equals("gles")) { 363 return Backend::kGLES; 364 } else if (s.equals("vk")) { 365 return Backend::kVulkan; 366 } 367 return Backend::kUnknown; 368} 369 370 371bool MakeReport(const char* report_directory_path) { 372 int glesErrorCount = 0, vkErrorCount = 0, gles = 0, vk = 0; 373 374 SkASSERT_RELEASE(sk_isdir(report_directory_path)); 375 std::lock_guard<std::mutex> lock(gMutex); 376 SkFILEWStream csvOut(SkOSPath::Join(report_directory_path, PATH_CSV).c_str()); 377 SkFILEWStream htmOut(SkOSPath::Join(report_directory_path, PATH_REPORT).c_str()); 378 SkASSERT_RELEASE(csvOut.isValid()); 379 if (!csvOut.isValid() || !htmOut.isValid()) { 380 return false; 381 } 382 htmOut.writeText(kDocHead); 383 for (const Run& run : gErrors) { 384 auto backend = get_backend(run.fBackend); 385 switch (backend) { 386 case Backend::kGLES: ++gles; break; 387 case Backend::kVulkan: ++vk; break; 388 default: break; 389 } 390 write(&csvOut, SkStringPrintf("\"%s\",\"%s\",%d,%d\n", 391 run.fBackend.c_str(), run.fGM.c_str(), 392 run.fMaxerror, run.fBadpixels)); 393 if (run.fMaxerror == 0 && run.fBadpixels == 0) { 394 continue; 395 } 396 write(&htmOut, SkStringPrintf(" f(\"%s\", \"%s\", %d, %d);\n", 397 run.fBackend.c_str(), run.fGM.c_str(), 398 run.fMaxerror, run.fBadpixels)); 399 switch (backend) { 400 case Backend::kGLES: ++glesErrorCount; break; 401 case Backend::kVulkan: ++vkErrorCount; break; 402 default: break; 403 } 404 } 405 htmOut.writeText(kDocMiddle); 406 write(&htmOut, SkStringPrintf("<p>gles errors: %d (of %d)</br>\n" 407 "vk errors: %d (of %d)</p>\n", 408 glesErrorCount, gles, vkErrorCount, vk)); 409 htmOut.writeText(kDocTail); 410 return true; 411} 412} // namespace gmkb 413