From d7b3845f3d3f3498c2adc542b4b20003ac7d3ab0 Mon Sep 17 00:00:00 2001 From: Hal Canary Date: Mon, 11 Dec 2017 17:46:26 -0500 Subject: SkQP: make_gmkb, gm_knowledge (GM Knowledgebase) Add a real implementation for gm_knowledge.h This depends on the presence of files in the form $GMK_DIR/foo/{max,min}.png The implementation also writes out failures in a report directory. Add a utility: experimental/make_gmkb which is a stand-alone go executable that generates the foo/{max,min}.png data. tools/skqp/README.md has instructions on running SkQP. Also: add SkFontMgrPriv.h Change-Id: Ibe1e9a7e7de143d14eee3877f5f2d2d8713f7f49 Reviewed-on: https://skia-review.googlesource.com/65380 Reviewed-by: Yuqian Li Commit-Queue: Hal Canary --- BUILD.gn | 21 +++- dm/DM.cpp | 6 +- platform_tools/android/.gitignore | 1 + src/core/SkFontMgrPriv.h | 14 +++ tools/gpucts/gm_knowledge.c | 12 -- tools/gpucts/gm_knowledge.h | 57 ---------- tools/gpucts/gm_runner.cpp | 109 ------------------ tools/gpucts/gm_runner.h | 65 ----------- tools/gpucts/gpucts.cpp | 159 -------------------------- tools/ok_vias.cpp | 3 +- tools/skqp/README.md | 66 +++++++++++ tools/skqp/gm_knowledge.cpp | 230 ++++++++++++++++++++++++++++++++++++++ tools/skqp/gm_knowledge.h | 71 ++++++++++++ tools/skqp/gm_runner.cpp | 213 +++++++++++++++++++++++++++++++++++ tools/skqp/gm_runner.h | 98 ++++++++++++++++ tools/skqp/make_gmkb.go | 223 ++++++++++++++++++++++++++++++++++++ tools/skqp/make_report.py | 41 +++++++ tools/skqp/skqp.cpp | 140 +++++++++++++++++++++++ tools/skqp/skqp_asset_manager.h | 21 ++++ tools/skqp/sysopen.py | 21 ++++ 20 files changed, 1158 insertions(+), 413 deletions(-) create mode 100644 src/core/SkFontMgrPriv.h delete mode 100644 tools/gpucts/gm_knowledge.c delete mode 100644 tools/gpucts/gm_knowledge.h delete mode 100644 tools/gpucts/gm_runner.cpp delete mode 100644 tools/gpucts/gm_runner.h delete mode 100644 tools/gpucts/gpucts.cpp create mode 100644 tools/skqp/README.md create mode 100644 tools/skqp/gm_knowledge.cpp create mode 100644 tools/skqp/gm_knowledge.h create mode 100644 tools/skqp/gm_runner.cpp create mode 100644 tools/skqp/gm_runner.h create mode 100644 tools/skqp/make_gmkb.go create mode 100755 tools/skqp/make_report.py create mode 100644 tools/skqp/skqp.cpp create mode 100644 tools/skqp/skqp_asset_manager.h create mode 100755 tools/skqp/sysopen.py diff --git a/BUILD.gn b/BUILD.gn index 4d1850a09c..b03f1117d9 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1701,19 +1701,30 @@ if (skia_enable_tools) { ] } - if (!is_win) { - test_app("gpucts") { + if (!is_win && skia_enable_gpu) { + test_lib("skqp_lib") { + public_include_dirs = [ "tools/skqp" ] sources = [ + "dm/DMFontMgr.cpp", "dm/DMGpuTestProcs.cpp", - "tools/gpucts/gm_knowledge.c", - "tools/gpucts/gm_runner.cpp", - "tools/gpucts/gpucts.cpp", + "tools/skqp/gm_knowledge.cpp", + "tools/skqp/gm_runner.cpp", ] deps = [ ":gm", ":gpu_tool_utils", ":skia", ":tests", + ":tool_utils", + ] + } + test_app("skqp") { + sources = [ + "tools/skqp/skqp.cpp", + ] + deps = [ + ":skia", + ":skqp_lib", "//third_party/googletest", ] } diff --git a/dm/DM.cpp b/dm/DM.cpp index 4deaa2fbb7..058fb9da03 100644 --- a/dm/DM.cpp +++ b/dm/DM.cpp @@ -22,10 +22,11 @@ #include "SkCommonFlagsGpuThreads.h" #include "SkCommonFlagsPathRenderer.h" #include "SkData.h" -#include "SkDocument.h" #include "SkDebugfTracer.h" +#include "SkDocument.h" #include "SkEventTracingPriv.h" #include "SkFontMgr.h" +#include "SkFontMgrPriv.h" #include "SkGraphics.h" #include "SkHalf.h" #include "SkLeanWindows.h" @@ -1299,9 +1300,6 @@ static sk_sp create_from_name(const char familyName[], SkFontStyle s extern sk_sp (*gCreateTypefaceDelegate)(const char [], SkFontStyle ); -extern sk_sp (*gSkFontMgr_DefaultFactory)(); - - int main(int argc, char** argv) { SkCommandLineFlags::Parse(argc, argv); diff --git a/platform_tools/android/.gitignore b/platform_tools/android/.gitignore index cb3f2d6237..26e14aa29f 100644 --- a/platform_tools/android/.gitignore +++ b/platform_tools/android/.gitignore @@ -9,3 +9,4 @@ app/.project app/bin app/gen app/lint.xml +/apps/skqp/src/main/assets diff --git a/src/core/SkFontMgrPriv.h b/src/core/SkFontMgrPriv.h new file mode 100644 index 0000000000..ba370157f6 --- /dev/null +++ b/src/core/SkFontMgrPriv.h @@ -0,0 +1,14 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkFontMgrPriv_DEFINED +#define SkFontMgrPriv_DEFINED + +#include "SkFontMgr.h" + +extern sk_sp (*gSkFontMgr_DefaultFactory)(); + +#endif // SkFontMgrPriv_DEFINED diff --git a/tools/gpucts/gm_knowledge.c b/tools/gpucts/gm_knowledge.c deleted file mode 100644 index 8dbdd00f09..0000000000 --- a/tools/gpucts/gm_knowledge.c +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#include "gm_knowledge.h" - -// placeholder function definitions: -float GMK_Check(GMK_ImageData d, const char* n) { return 0; } -bool GMK_IsGoodGM(const char* n) { return true; } diff --git a/tools/gpucts/gm_knowledge.h b/tools/gpucts/gm_knowledge.h deleted file mode 100644 index d9d71e05c5..0000000000 --- a/tools/gpucts/gm_knowledge.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ -#ifndef gm_knowledge_DEFINED -#define gm_knowledge_DEFINED - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -/** -A structure representing an image. pix should either be nullptr (representing -a missing image) or point to a block of memory width*height in size. - -Each pixel is an un-pre-multiplied RGBA color: - void set_color(GMK_ImageData* data, int x, int y, - unsigned char r, unsigned char g, unsigned char b, unsigned char a) { - data->pix[x + data->width * y] = (r << 0) | (g << 8) | (b << 16) | (a << 24); - } - */ -typedef struct { - const uint32_t* pix; - int width; - int height; -} GMK_ImageData; - -/** -Check if the given test image matches the expected results. - -@param data the image -@param gm_name the name of the rendering test that produced the image - -@return 0 if the test passes, otherwise a positive number representing how - badly it failed. - */ -float GMK_Check(GMK_ImageData data, const char* gm_name); - -/** -Check to see if the given test has expected results. - -@param gm_name the name of a rendering test. - -@return true of expected results are known for the given test. -*/ -bool GMK_IsGoodGM(const char* gm_name); - -#ifdef __cplusplus -} -#endif - -#endif // gm_knowledge_DEFINED diff --git a/tools/gpucts/gm_runner.cpp b/tools/gpucts/gm_runner.cpp deleted file mode 100644 index a18c457d1b..0000000000 --- a/tools/gpucts/gm_runner.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#include "gm_runner.h" - -#include - -#include "SkSurface.h" -#include "gm.h" - -#if SK_SUPPORT_GPU - -#include "GrContextFactory.h" - -using sk_gpu_test::GrContextFactory; - -namespace gm_runner { - -static GrContextFactory::ContextType to_context_type(SkiaBackend backend) { - switch (backend) { - case SkiaBackend::kGL: return GrContextFactory::kGL_ContextType; - case SkiaBackend::kGLES: return GrContextFactory::kGLES_ContextType; - case SkiaBackend::kVulkan: return GrContextFactory::kVulkan_ContextType; - } - SkDEBUGFAIL(""); return (GrContextFactory::ContextType)0; -} - -const char* GetBackendName(SkiaBackend backend) { - return GrContextFactory::ContextTypeName(to_context_type(backend)); -} - -bool BackendSupported(SkiaBackend backend) { - GrContextFactory factory; - return factory.get(to_context_type(backend)) != nullptr; -} - - -GMK_ImageData Evaluate(SkiaBackend backend, - GMFactory gmFact, - std::vector* storage) { - SkASSERT(gmFact); - SkASSERT(storage); - std::unique_ptr gm(gmFact(nullptr)); - SkASSERT(gm.get()); - int w = SkScalarRoundToInt(gm->width()); - int h = SkScalarRoundToInt(gm->height()); - GrContextFactory contextFactory; - GrContext* context = contextFactory.get(to_context_type(backend)); - if (!context) { - return GMK_ImageData{nullptr, w, h}; - } - SkASSERT(context); - constexpr SkColorType ct = kRGBA_8888_SkColorType; - - sk_sp s = SkSurface::MakeRenderTarget( - context, SkBudgeted::kNo, SkImageInfo::Make(w, h, ct, kPremul_SkAlphaType)); - if (!s) { - return GMK_ImageData{nullptr, w, h}; - } - gm->draw(s->getCanvas()); - - storage->resize(w * h); - uint32_t* pix = storage->data(); - SkASSERT(SkColorTypeBytesPerPixel(ct) == sizeof(uint32_t)); - SkAssertResult(s->readPixels(SkImageInfo::Make(w, h, ct, kUnpremul_SkAlphaType), - pix, w * sizeof(uint32_t), 0, 0)); - return GMK_ImageData{pix, w, h}; -} - -} // namespace gm_runner - -#else -namespace sk_gpu_test { - class GrContextFactory {}; -} -namespace gm_runner { -bool BackendSupported(SkiaBackend) { return false; } -GMK_ImageData Evaluate(SkiaBackend, GMFactory, std::vector*) { - return GMK_ImageData{nullptr, 0, 0}; -} -const char* GetBackendName(SkiaBackend backend) { return "Unknown"; } -} // namespace gm_runner -#endif - -namespace gm_runner { - -std::vector GetGMFactories() { - std::vector result; - for (const skiagm::GMRegistry* r = skiagm::GMRegistry::Head(); r; r = r->next()) { - result.push_back(r->factory()); - } - struct { - bool operator()(GMFactory u, GMFactory v) const { return GetGMName(u) < GetGMName(v); } - } less; - std::sort(result.begin(), result.end(), less); - return result; -} - -std::string GetGMName(GMFactory gmFactory) { - SkASSERT(gmFactory); - std::unique_ptr gm(gmFactory(nullptr)); - SkASSERT(gm); - return std::string(gm->getName()); -} -} // namespace gm_runner diff --git a/tools/gpucts/gm_runner.h b/tools/gpucts/gm_runner.h deleted file mode 100644 index cd0d7d32bb..0000000000 --- a/tools/gpucts/gm_runner.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ -#ifndef gm_runner_DEFINED -#define gm_runner_DEFINED - -#include -#include -#include - -#include "gm_knowledge.h" - -/** -A Skia GM is a single rendering test that can be executed on any Skia backend Canvas. -*/ -namespace skiagm { - class GM; -} - -namespace sk_gpu_test { - class GrContextFactory; -} - -namespace gm_runner { - -using GMFactory = skiagm::GM* (*)(void*); - -enum class SkiaBackend { - kGL, - kGLES, - kVulkan, -}; - -bool BackendSupported(SkiaBackend); - -/** -@return a list of all Skia GMs in lexicographic order. -*/ -std::vector GetGMFactories(); - -/** -@return a descriptive name for the GM. -*/ -std::string GetGMName(GMFactory); -/** -@return a descriptive name for the backend. -*/ -const char* GetBackendName(SkiaBackend); - -/** -Execute the given GM on the given Skia backend. Then copy the pixels into the -storage (overwriting existing contents of storage). - -@return the rendered image. Return a null ImageData on error. -*/ -GMK_ImageData Evaluate(SkiaBackend, - GMFactory, - std::vector* storage); - -} // namespace gm_runner - -#endif // gm_runner_DEFINED diff --git a/tools/gpucts/gpucts.cpp b/tools/gpucts/gpucts.cpp deleted file mode 100644 index c50519cc5b..0000000000 --- a/tools/gpucts/gpucts.cpp +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2017 Google Inc. - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#include "SkGraphics.h" -#include "gm_runner.h" - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wused-but-marked-unused" -#endif - -#include "gtest/gtest.h" - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -#include "Test.h" - -//////////////////////////////////////////////////////////////////////////////// - -struct GMTestCase { - gm_runner::GMFactory fGMFactory; - gm_runner::SkiaBackend fBackend; -}; - -struct GMTest : public testing::Test { - GMTestCase fTest; - GMTest(GMTestCase t) : fTest(t) {} - void TestBody() override { - if (!fTest.fGMFactory) { - EXPECT_TRUE(gm_runner::BackendSupported(fTest.fBackend)); - return; - } - std::vector pixels; - GMK_ImageData imgData = gm_runner::Evaluate(fTest.fBackend, fTest.fGMFactory, &pixels); - EXPECT_TRUE(imgData.pix); - if (!imgData.pix) { - return; - } - std::string gmName = gm_runner::GetGMName(fTest.fGMFactory); - float result = GMK_Check(imgData, gmName.c_str()); - EXPECT_EQ(result, 0); - } -}; - -struct GMTestFactory : public testing::internal::TestFactoryBase { - GMTestCase fTest; - GMTestFactory(GMTestCase t) : fTest(t) {} - testing::Test* CreateTest() override { return new GMTest(fTest); } -}; - -//////////////////////////////////////////////////////////////////////////////// - -#if !SK_SUPPORT_GPU -struct GrContextOptions {}; -#endif - -struct UnitTest : public testing::Test { - skiatest::TestProc fProc; - UnitTest(skiatest::TestProc proc) : fProc(proc) {} - void TestBody() override { - struct : skiatest::Reporter { - void reportFailed(const skiatest::Failure& failure) override { - SkString desc = failure.toString(); - SK_ABORT(""); - GTEST_NONFATAL_FAILURE_(desc.c_str()); - } - } r; - fProc(&r, GrContextOptions()); - } -}; - -struct UnitTestFactory : testing::internal::TestFactoryBase { - skiatest::TestProc fProc; - UnitTestFactory(skiatest::TestProc proc) : fProc(proc) {} - testing::Test* CreateTest() override { return new UnitTest(fProc); } -}; - -std::vector GetUnitTests() { - // Unit Tests - std::vector tests; - for (const skiatest::TestRegistry* r = skiatest::TestRegistry::Head(); r; r = r->next()) { - tests.push_back(&r->factory()); - } - struct { - bool operator()(const skiatest::Test* u, const skiatest::Test* v) const { - return strcmp(u->name, v->name) < 0; - } - } less; - std::sort(tests.begin(), tests.end(), less); - return tests; -} - -//////////////////////////////////////////////////////////////////////////////// - -static void reg_test(const char* test, const char* testCase, - testing::internal::TestFactoryBase* fact) { - testing::internal::MakeAndRegisterTestInfo( - test, - testCase, - nullptr, - nullptr, - testing::internal::CodeLocation(__FILE__, __LINE__), - testing::internal::GetTestTypeId(), - testing::Test::SetUpTestCase, - testing::Test::TearDownTestCase, - fact); -} - -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - SkGraphics::Init(); - - // Rendering Tests - gm_runner::SkiaBackend backends[] = { - #ifndef SK_BUILD_FOR_ANDROID - gm_runner::SkiaBackend::kGL, // Used for testing on desktop machines. - #endif - gm_runner::SkiaBackend::kGLES, - gm_runner::SkiaBackend::kVulkan, - }; - std::vector gms = gm_runner::GetGMFactories(); - for (auto backend : backends) { - const char* backendName = GetBackendName(backend); - std::string test = std::string("SkiaGM_") + backendName; - reg_test(test.c_str(), "BackendSupported", new GMTestFactory(GMTestCase{nullptr, backend})); - - if (!gm_runner::BackendSupported(backend)) { - continue; - } - for (auto gmFactory : gms) { - std::string gmName = gm_runner::GetGMName(gmFactory); - if (!GMK_IsGoodGM(gmName.c_str())) { - continue; - } - #ifdef SK_DEBUG - // The following test asserts on my phone. - // TODO(halcanary): fix this. - if(gmName == std::string("complexclip3_simple") && - backend == gm_runner::SkiaBackend::kGLES) { - continue; - } - #endif - reg_test(test.c_str(), gmName.c_str(), - new GMTestFactory(GMTestCase{gmFactory, backend})); - } - } - - for (const skiatest::Test* test : GetUnitTests()) { - reg_test("Skia_Unit_Tests", test->name, new UnitTestFactory(test->proc)); - } - return RUN_ALL_TESTS(); -} - diff --git a/tools/ok_vias.cpp b/tools/ok_vias.cpp index 7c246644ee..c1f8d9365e 100644 --- a/tools/ok_vias.cpp +++ b/tools/ok_vias.cpp @@ -6,6 +6,7 @@ */ #include "../dm/DMFontMgr.h" +#include "../src/core/SkFontMgrPriv.h" #include "ProcStats.h" #include "SkColorFilter.h" #include "SkEventTracingPriv.h" @@ -292,8 +293,6 @@ static Register trace{"trace", "enable tracing in mode=atrace, mode=debugf, or mode=trace.json", Trace::Create}; -extern sk_sp (*gSkFontMgr_DefaultFactory)(); - struct PortableFonts : Dst { std::unique_ptr target; diff --git a/tools/skqp/README.md b/tools/skqp/README.md new file mode 100644 index 0000000000..beac911ba0 --- /dev/null +++ b/tools/skqp/README.md @@ -0,0 +1,66 @@ + +SkQP +==== + +**Motivation**: Test an Android device’s GPU and OpenGLES & Vulkan drivers with +Skia and Skia’s existing unit & rendering tests. + +How To Use SkQP on your Android device: + +1. To build SkQP you need to install the + [Android NDK](https://developer.android.com/ndk/). + +2. Checkout Skia, then go to the source directory: + + cd $SKIA_SOURCE_DIRECTORY + +3. Configure and build Skia for your device's architecture: + + arch='arm64' # Also valid: 'arm', 'x68', 'x64' + android_ndk="${HOME}/ndk" # Or wherever you installed the NDK. + mkdir -p out/${arch}-rel + cat > out/${arch}-rel/args.gn << EOF + ndk = "$android_ndk" + ndk_api = 24 + target_cpu = "$arch" + skia_embed_resources = true + is_debug = false + EOF + tools/git-sync-deps + bin/gn gen out/${arch}-rel + ninja -C out/${arch}-rel skqp_lib + +4. Download meta.json from [https://goo.gl/jBw3Dd](https://goo.gl/jBw3Dd) . + This is the data used to build the validation model. + +5. Generate the validation model data: + + rm -rf platform_tools/android/apps/skqp/src/main/assets/gmkb + go get go.skia.org/infra/golden/go/search + go run tools/skqp/make_gmkb.go ~/Downloads/meta.json \ + platform_tools/android/apps/skqp/src/main/assets/gmkb + +Run as an executable +-------------------- + +1. Build the SkQP program, load files on the device, and run skqp: + + ninja -C out/${arch}-rel skqp + adb shell "cd /data/local/tmp; rm -rf gmkb report" + adb push platform_tools/android/apps/skqp/src/main/assets/gmkb \ + /data/local/tmp/ + adb push out/${arch}-rel/skqp /data/local/tmp/ + adb shell "cd /data/local/tmp; ./skqp gmkb report" + +2. Produce a one-page error report if there are errors: + + rm -rf /tmp/report + if adb shell test -d /data/local/tmp/report; then + adb pull /data/local/tmp/report /tmp/ + tools/skqp/make_report.py /tmp/report + fi + +Run as an APK +------------- + +[TODO] diff --git a/tools/skqp/gm_knowledge.cpp b/tools/skqp/gm_knowledge.cpp new file mode 100644 index 0000000000..bc2ca8201a --- /dev/null +++ b/tools/skqp/gm_knowledge.cpp @@ -0,0 +1,230 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "gm_knowledge.h" + +#include +#include +#include +#include +#include + +#include "../../src/core/SkStreamPriv.h" +#include "SkBitmap.h" +#include "SkCodec.h" +#include "SkOSFile.h" +#include "SkPngEncoder.h" +#include "SkStream.h" + +#include "skqp_asset_manager.h" + +#define PATH_MAX_PNG "max.png" +#define PATH_MIN_PNG "min.png" +#define PATH_IMG_PNG "image.png" +#define PATH_ERR_PNG "errors.png" +#define PATH_REPORT "report.html" + +//////////////////////////////////////////////////////////////////////////////// +inline void path_join_append(std::ostringstream* o) { } + +template +void path_join_append(std::ostringstream* o, const char* v, Types... args) { + constexpr char kPathSeparator[] = "/"; + *o << kPathSeparator << v; + path_join_append(o, args...); +} + +template +std::string path_join(const char* v, Types... args) { + std::ostringstream o; + o << v; + path_join_append(&o, args...); + return o.str(); +} +template +std::string path_join(const std::string& v, Types... args) { + return path_join(v.c_str(), args...); +} +//////////////////////////////////////////////////////////////////////////////// + +static int get_error(uint32_t value, uint32_t value_max, uint32_t value_min) { + int error = 0; + for (int j : {0, 8, 16, 24}) { + uint8_t v = (value >> j) & 0xFF, + vmin = (value_min >> j) & 0xFF, + vmax = (value_max >> j) & 0xFF; + if (v > vmax) { + error = std::max(v - vmax, error); + } else if (v < vmin) { + error = std::max(vmin - v, error); + } + } + return error; +} + +static float set_error_code(gmkb::Error* error_out, gmkb::Error error) { + SkASSERT(error != gmkb::Error::kNone); + if (error_out) { + *error_out = error; + } + return FLT_MAX; +} + +static SkPixmap to_pixmap(const SkBitmap& bitmap) { + SkPixmap pixmap; + SkAssertResult(bitmap.peekPixels(&pixmap)); + return pixmap; +} + + +static bool WritePixmapToFile(const SkPixmap& pixmap, const char* path) { + SkFILEWStream wStream(path); + SkPngEncoder::Options options; + options.fUnpremulBehavior = SkTransferFunctionBehavior::kIgnore; + return wStream.isValid() && SkPngEncoder::Encode(&wStream, pixmap, options); +} + +constexpr SkColorType kColorType = kRGBA_8888_SkColorType; +constexpr SkAlphaType kAlphaType = kUnpremul_SkAlphaType; + +static SkPixmap rgba8888_to_pixmap(const uint32_t* pixels, int width, int height) { + SkImageInfo info = SkImageInfo::Make(width, height, kColorType, kAlphaType); + return SkPixmap(info, pixels, width * sizeof(uint32_t)); +} + +static bool asset_exists(skqp::AssetManager* mgr, const char* path) { + return mgr && nullptr != mgr->open(path); +} + +static bool copy(skqp::AssetManager* mgr, const char* path, const char* dst) { + if (mgr) { + if (auto stream = mgr->open(path)) { + SkFILEWStream wStream(dst); + return wStream.isValid() && SkStreamCopy(&wStream, stream.get()); + } + } + return false; +} + +static SkBitmap ReadPngRgba8888FromFile(skqp::AssetManager* assetManager, const char* path) { + SkBitmap bitmap; + if (auto codec = SkCodec::MakeFromStream(assetManager->open(path))) { + SkISize size = codec->getInfo().dimensions(); + SkASSERT(!size.isEmpty()); + SkImageInfo info = SkImageInfo::Make(size.width(), size.height(), kColorType, kAlphaType); + bitmap.allocPixels(info); + SkASSERT(bitmap.rowBytes() == (unsigned)bitmap.width() * sizeof(uint32_t)); + if (SkCodec::kSuccess != codec->getPixels(to_pixmap(bitmap))) { + bitmap.reset(); + } + } + return bitmap; +} + +namespace gmkb { +// Assumes that for each GM foo, asset_manager has files foo/{max,min}.png +float Check(const uint32_t* pixels, + int width, + int height, + const char* name, + const char* backend, + skqp::AssetManager* assetManager, + const char* report_directory_path, + Error* error_out) { + using std::string; + if (width <= 0 || height <= 0) { + return set_error_code(error_out, Error::kBadInput); + } + size_t N = (unsigned)width * (unsigned)height; + string max_path = path_join(name, PATH_MAX_PNG); + string min_path = path_join(name, PATH_MIN_PNG); + SkBitmap max_image = ReadPngRgba8888FromFile(assetManager, max_path.c_str()); + if (max_image.isNull()) { + return set_error_code(error_out, Error::kBadData); + } + SkBitmap min_image = ReadPngRgba8888FromFile(assetManager, min_path.c_str()); + if (min_image.isNull()) { + return set_error_code(error_out, Error::kBadData); + } + if (max_image.width() != min_image.width() || + max_image.height() != min_image.height()) + { + return set_error_code(error_out, Error::kBadData); + } + if (max_image.width() != width || max_image.height() != height) { + return set_error_code(error_out, Error::kBadInput); + } + int badness = 0; + int badPixelCount = 0; + const uint32_t* max_pixels = (uint32_t*)max_image.getPixels(); + const uint32_t* min_pixels = (uint32_t*)min_image.getPixels(); + + for (size_t i = 0; i < N; ++i) { + int error = get_error(pixels[i], max_pixels[i], min_pixels[i]); + if (error > 0) { + badness = SkTMax(error, badness); + ++badPixelCount; + } + } + if (report_directory_path && badness > 0 && report_directory_path[0] != '\0') { + sk_mkdir(report_directory_path); + if (!backend) { + backend = "skia"; + } + string report_directory = path_join(report_directory_path, backend); + sk_mkdir(report_directory.c_str()); + string report_subdirectory = path_join(report_directory, name); + sk_mkdir(report_subdirectory.c_str()); + string error_path = path_join(report_subdirectory, PATH_IMG_PNG); + SkAssertResult(WritePixmapToFile(rgba8888_to_pixmap(pixels, width, height), + error_path.c_str())); + SkBitmap errorBitmap; + errorBitmap.allocPixels(SkImageInfo::Make(width, height, kColorType, kAlphaType)); + uint32_t* errors = (uint32_t*)errorBitmap.getPixels(); + for (size_t i = 0; i < N; ++i) { + int error = get_error(pixels[i], max_pixels[i], min_pixels[i]); + errors[i] = error > 0 ? 0xFF000000 + (unsigned)error : 0x00000000; + } + error_path = path_join(report_subdirectory, PATH_ERR_PNG); + SkAssertResult(WritePixmapToFile(to_pixmap(errorBitmap), error_path.c_str())); + + auto report_path = path_join(report_subdirectory, PATH_REPORT); + auto rdir = path_join("..", "..", backend, name); + + auto max_path_out = path_join(report_subdirectory, PATH_MAX_PNG); + auto min_path_out = path_join(report_subdirectory, PATH_MIN_PNG); + (void)copy(assetManager, max_path.c_str(), max_path_out.c_str()); + (void)copy(assetManager, min_path.c_str(), min_path_out.c_str()); + + SkString text = SkStringPrintf( + "backend: %s\n
\n" + "gm name: %s\n
\n" + "maximum error: %d\n
\n" + "bad pixel counts: %d\n
\n" + "" + "img\n" + "" + "err\n
\n" + "max\n
\n" + "min\n
\n", + backend, name, badness, badPixelCount, + rdir.c_str(), rdir.c_str(), rdir.c_str(), rdir.c_str(), rdir.c_str(), rdir.c_str()); + SkFILEWStream(report_path.c_str()).write(text.c_str(), text.size()); + } + if (error_out) { + *error_out = Error::kNone; + } + return (float)badness; +} + +bool IsGoodGM(const char* name, skqp::AssetManager* assetManager) { + std::string max_path = path_join(name, PATH_MAX_PNG); + std::string min_path = path_join(name, PATH_MIN_PNG); + return asset_exists(assetManager, max_path.c_str()) + && asset_exists(assetManager, min_path.c_str()); +} +} // namespace gmkb diff --git a/tools/skqp/gm_knowledge.h b/tools/skqp/gm_knowledge.h new file mode 100644 index 0000000000..4aca00ec30 --- /dev/null +++ b/tools/skqp/gm_knowledge.h @@ -0,0 +1,71 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef gm_knowledge_DEFINED +#define gm_knowledge_DEFINED + +#include + +namespace skqp { +class AssetManager; +} + +namespace gmkb { + +enum class Error { + kNone, /**< No error. */ + kBadInput, /**< Error with the given image data. */ + kBadData, /**< Error with the given gmkb data directory. */ +}; + +/** +Check if the given test image matches the expected results. + +Each pixel is an un-pre-multiplied RGBA color: + uint32_t make_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + return (r << 0) | (g << 8) | (b << 16) | (a << 24); + } + +The image's rowBytes is width*sizeof(uint32_t): + uint32_t* get_pixel_addr(uint32_t* pixels, int width, int height, int x, int y) { + assert(x >= 0 && x < width); + assert(y >= 0 && y < height); + return &pixels[x + (width * y)]; + } + +@param pixels, width, height the image +@param gm_name the name of the rendering test that produced the image +@param backend (optional) name of the backend +@param asset_manager GM KnowledgeBase data files +@param report_directory_path (optional) locatation to write report to. +@param error_out (optional) error return code. + +@return 0 if the test passes, otherwise a positive number representing how + badly it failed. Return FLT_MAX on error. + */ + +float Check(const uint32_t* pixels, + int width, + int height, + const char* name, + const char* backend, + skqp::AssetManager* asset_manager, + const char* report_directory_path, + Error* error_out); + +/** +Check to see if the given test has expected results. + +@param name The name of a rendering test. +@param assetManager GM KnowledgeBase data files + +@return true of expected results are known for the given test. +*/ +bool IsGoodGM(const char* name, skqp::AssetManager* assetManager); + +} // namespace gmkb + +#endif // gm_knowledge_DEFINED diff --git a/tools/skqp/gm_runner.cpp b/tools/skqp/gm_runner.cpp new file mode 100644 index 0000000000..3c3885ef5c --- /dev/null +++ b/tools/skqp/gm_runner.cpp @@ -0,0 +1,213 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "gm_runner.h" + +#include + +#include "../dm/DMFontMgr.h" +#include "GrContext.h" +#include "GrContextOptions.h" +#include "SkFontMgrPriv.h" +#include "SkFontStyle.h" +#include "SkGraphics.h" +#include "SkSurface.h" +#include "Test.h" +#include "gl/GLTestContext.h" +#include "gm.h" +#include "gm_knowledge.h" +#include "vk/VkTestContext.h" + +namespace gm_runner { + +const char* GetErrorString(Error e) { + switch (e) { + case Error::None: return ""; + case Error::BadSkiaOutput: return "Bad Skia Output"; + case Error::BadGMKBData: return "Bad GMKB Data"; + case Error::SkiaFailure: return "Skia Failure"; + default: SkASSERT(false); + return "unknown"; + } +} + +std::vector ExecuteTest(UnitTest test) { + struct : public skiatest::Reporter { + std::vector fErrors; + void reportFailed(const skiatest::Failure& failure) override { + SkString desc = failure.toString(); + fErrors.push_back(std::string(desc.c_str(), desc.size())); + } + } r; + GrContextOptions options; + if (test->fContextOptionsProc) { + test->fContextOptionsProc(&options); + } + test->proc(&r, options); + return std::move(r.fErrors); +} + +const char* GetUnitTestName(UnitTest test) { return test->name; } + +std::vector GetUnitTests() { + std::vector tests; + for (const skiatest::TestRegistry* r = skiatest::TestRegistry::Head(); r; r = r->next()) { + const skiatest::Test& test = r->factory(); + if (test.needsGpu) { + tests.push_back(&test); + } + } + return tests; +} + +const char* GetBackendName(SkiaBackend backend) { + switch (backend) { + case SkiaBackend::kGL: return "gl"; + case SkiaBackend::kGLES: return "gles"; + case SkiaBackend::kVulkan: return "vk"; + default: SkASSERT(false); + return "error"; + } +} + +static std::unique_ptr make_test_context(SkiaBackend backend) { + using U = std::unique_ptr; + switch (backend) { + case SkiaBackend::kGL: + return U(sk_gpu_test::CreatePlatformGLTestContext(kGL_GrGLStandard, nullptr)); + case SkiaBackend::kGLES: + return U(sk_gpu_test::CreatePlatformGLTestContext(kGLES_GrGLStandard, nullptr)); +#ifdef SK_VULKAN + case SkiaBackend::kVulkan: + return U(sk_gpu_test::CreatePlatformVkTestContext(nullptr)); +#endif + default: + return nullptr; + } +} + +static GrContextOptions context_options(skiagm::GM* gm = nullptr) { + GrContextOptions grContextOptions; + grContextOptions.fAllowPathMaskCaching = true; + grContextOptions.fSuppressPathRendering = true; + if (gm) { + gm->modifyGrContextOptions(&grContextOptions); + } + return grContextOptions; +} + +std::vector GetSupportedBackends() { + std::vector result; + SkiaBackend backends[] = { + #ifndef SK_BUILD_FOR_ANDROID + SkiaBackend::kGL, // Used for testing on desktop machines. + #endif + SkiaBackend::kGLES, + SkiaBackend::kVulkan, + }; + for (SkiaBackend backend : backends) { + std::unique_ptr testCtx = make_test_context(backend); + if (testCtx) { + testCtx->makeCurrent(); + if (nullptr != testCtx->makeGrContext(context_options())) { + result.push_back(backend); + } + } + } + return result; +} + +static bool evaluate_gm(SkiaBackend backend, + skiagm::GM* gm, + int* width, + int* height, + std::vector* storage) { + constexpr SkColorType ct = kRGBA_8888_SkColorType; + SkASSERT(storage); + SkASSERT(gm); + SkASSERT(width); + SkASSERT(height); + SkISize size = gm->getISize(); + int w = size.width(), + h = size.height(); + *width = w; + *height = h; + SkImageInfo info = SkImageInfo::Make(w, h, ct, kPremul_SkAlphaType, nullptr); + SkSurfaceProps props(0, SkSurfaceProps::kLegacyFontHost_InitType); + + std::unique_ptr testCtx = make_test_context(backend); + if (!testCtx) { + return false; + } + testCtx->makeCurrent(); + sk_sp surf = SkSurface::MakeRenderTarget( + testCtx->makeGrContext(context_options(gm)).get(), SkBudgeted::kNo, info, 0, &props); + if (!surf) { + return false; + } + gm->draw(surf->getCanvas()); + + storage->resize(w * h); + uint32_t* pix = storage->data(); + size_t rb = w * sizeof(uint32_t); + SkASSERT(SkColorTypeBytesPerPixel(ct) == sizeof(uint32_t)); + if (!surf->readPixels(SkImageInfo::Make(w, h, ct, kUnpremul_SkAlphaType), pix, rb, 0, 0)) { + storage->resize(0); + return false; + } + return true; +} + +std::tuple EvaluateGM(SkiaBackend backend, + GMFactory gmFact, + skqp::AssetManager* assetManager, + const char* reportDirectoryPath) { + std::vector pixels; + std::unique_ptr gm(gmFact(nullptr)); + int width = 0, height = 0; + if (!evaluate_gm(backend, gm.get(), &width, &height, &pixels)) { + return std::make_tuple(FLT_MAX, Error::SkiaFailure); + } + gmkb::Error e; + float value = gmkb::Check(pixels.data(), width, height, + gm->getName(), GetBackendName(backend), assetManager, + reportDirectoryPath, &e); + Error error = gmkb::Error::kBadInput == e ? Error::BadSkiaOutput + : gmkb::Error::kBadData == e ? Error::BadGMKBData + : Error::None; + return std::make_tuple(value, error); +} + +void InitSkia() { + SkGraphics::Init(); + gSkFontMgr_DefaultFactory = &DM::MakeFontMgr; +} + +std::vector GetGMFactories(skqp::AssetManager* assetManager) { + std::vector result; + for (const skiagm::GMRegistry* r = skiagm::GMRegistry::Head(); r; r = r->next()) { + GMFactory f = r->factory(); + + if (gmkb::IsGoodGM(GetGMName(f).c_str(), assetManager)) { + result.push_back(r->factory()); + SkASSERT(result.back()); + } + } + struct { + bool operator()(GMFactory u, GMFactory v) const { return GetGMName(u) < GetGMName(v); } + } less; + std::sort(result.begin(), result.end(), less); + return result; +} + +std::string GetGMName(GMFactory gmFactory) { + SkASSERT(gmFactory); + std::unique_ptr gm(gmFactory(nullptr)); + SkASSERT(gm); + return std::string(gm->getName()); +} +} // namespace gm_runner diff --git a/tools/skqp/gm_runner.h b/tools/skqp/gm_runner.h new file mode 100644 index 0000000000..690b3714e6 --- /dev/null +++ b/tools/skqp/gm_runner.h @@ -0,0 +1,98 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef gm_runner_DEFINED +#define gm_runner_DEFINED + +#include +#include +#include +#include + +#include "skqp_asset_manager.h" + +/** +A Skia GM is a single rendering test that can be executed on any Skia backend Canvas. +*/ +namespace skiagm { +class GM; +} + +namespace skiatest { +struct Test; +} + +namespace gm_runner { + +using GMFactory = skiagm::GM* (*)(void*); + +using UnitTest = const skiatest::Test*; + +enum class SkiaBackend { + kGL, + kGLES, + kVulkan, +}; + +/** +Initialize Skia +*/ +void InitSkia(); + +std::vector GetSupportedBackends(); + +/** +@return a list of all Skia GMs in lexicographic order. +*/ +std::vector GetGMFactories(skqp::AssetManager*); + +/** +@return a list of all Skia GPU unit tests in lexicographic order. +*/ +std::vector GetUnitTests(); + +/** +@return a descriptive name for the GM. +*/ +std::string GetGMName(GMFactory); + +/** +@return a descriptive name for the unit test. +*/ +const char* GetUnitTestName(UnitTest); + +/** +@return a descriptive name for the backend. +*/ +const char* GetBackendName(SkiaBackend); + +enum class Error { + None = 0, + BadSkiaOutput = 1, + BadGMKBData = 2, + SkiaFailure = 3, +}; + +const char* GetErrorString(Error); + +/** +@return A non-negative float representing how badly the GM failed (or zero for + success). Any error running or evaluating the GM will result in a non-zero + error code. +*/ +std::tuple EvaluateGM(SkiaBackend backend, + GMFactory gmFact, + skqp::AssetManager* assetManager, + const char* reportDirectoryPath); + +/** +@return a (hopefully empty) list of errors produced by this unit test. +*/ +std::vector ExecuteTest(UnitTest); + +} // namespace gm_runner + +#endif // gm_runner_DEFINED diff --git a/tools/skqp/make_gmkb.go b/tools/skqp/make_gmkb.go new file mode 100644 index 0000000000..b8b67a3634 --- /dev/null +++ b/tools/skqp/make_gmkb.go @@ -0,0 +1,223 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "image" + "image/draw" + "image/png" + "log" + "net/http" + "os" + "path" + "sort" + "strings" + "sync" + + "go.skia.org/infra/golden/go/search" +) + +const ( + min_png = "min.png" + max_png = "max.png" +) + +type ExportTestRecordArray []search.ExportTestRecord + +func (a ExportTestRecordArray) Len() int { return len(a) } +func (a ExportTestRecordArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ExportTestRecordArray) Less(i, j int) bool { return a[i].TestName < a[j].TestName } + +func in(v string, a []string) bool { + for _, u := range a { + if u == v { + return true + } + } + return false +} + +// TODO(halcanary): clean up this blacklist. +var blacklist = []string{ + "circular-clips", + "colorcomposefilter_wacky", + "coloremoji_blendmodes", + "colormatrix", + "complexclip_bw", + "complexclip_bw_invert", + "complexclip_bw_layer", + "complexclip_bw_layer_invert", + "convex-lineonly-paths-stroke-and-fill", + "dftext", + "downsamplebitmap_image_high_mandrill_512.png", + "downsamplebitmap_image_medium_mandrill_512.png", + "filterbitmap_image_mandrill_16.png", + "filterbitmap_image_mandrill_64.png", + "filterbitmap_image_mandrill_64.png_g8", + "gradients_degenerate_2pt", + "gradients_degenerate_2pt_nodither", + "gradients_local_perspective", + "gradients_local_perspective_nodither", + "imagefilterstransformed", + "image_scale_aligned", + "lattice", + "linear_gradient", + "mipmap_srgb", + "mixedtextblobs", + "OverStroke", + "simple-offsetimagefilter", + "strokerect", + "textblobmixedsizes", + "textblobmixedsizes_df"} + +func processTest(testName string, imgUrls []string, output string) error { + if strings.ContainsRune(testName, '/') { + return nil + } + output_directory := path.Join(output, testName) + var img_max image.NRGBA + var img_min image.NRGBA + for _, url := range imgUrls { + resp, err := http.Get(url) + if err != nil { + return err + } + img, err := png.Decode(resp.Body) + resp.Body.Close() + if err != nil { + return err + } + if img_max.Rect.Max.X == 0 { + // N.B. img_max.Pix may alias img.Pix (if they're already NRGBA). + img_max = toNrgba(img) + img_min = copyNrgba(img_max) + continue + } + w := img.Bounds().Max.X - img.Bounds().Min.X + h := img.Bounds().Max.Y - img.Bounds().Min.Y + if img_max.Rect.Max.X != w || img_max.Rect.Max.Y != h { + return errors.New("size mismatch") + } + img_nrgba := toNrgba(img) + for i, value := range img_nrgba.Pix { + if value > img_max.Pix[i] { + img_max.Pix[i] = value + } else if value < img_min.Pix[i] { + img_min.Pix[i] = value + } + } + } + if img_max.Rect.Max.X == 0 { + return nil + } + if err := os.Mkdir(output_directory, os.ModePerm); err != nil && !os.IsExist(err) { + return err + } + if err := writePngToFile(path.Join(output_directory, min_png), &img_min); err != nil { + return err + } + if err := writePngToFile(path.Join(output_directory, max_png), &img_max); err != nil { + return err + } + return nil + +} + +func readMetaJsonFile(filename string) ([]search.ExportTestRecord, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + dec := json.NewDecoder(file) + var records []search.ExportTestRecord + err = dec.Decode(&records) + return records, err +} + +func writePngToFile(path string, img image.Image) error { + file, err := os.Create(path) + if err != nil { + return err + } + defer file.Close() + return png.Encode(file, img) +} + +// to_nrgb() may return a shallow copy of img if it's already NRGBA. +func toNrgba(img image.Image) image.NRGBA { + switch v := img.(type) { + case *image.NRGBA: + return *v + } + nimg := *image.NewNRGBA(img.Bounds()) + draw.Draw(&nimg, img.Bounds(), img, image.Point{0, 0}, draw.Src) + return nimg +} + +func copyNrgba(src image.NRGBA) image.NRGBA { + dst := image.NRGBA{make([]uint8, len(src.Pix)), src.Stride, src.Rect} + copy(dst.Pix, src.Pix) + return dst +} + +func main() { + if len(os.Args) != 3 { + log.Printf("Usage:\n %s INPUT.json OUTPUT_DIRECTORY\n\n", os.Args[0]) + os.Exit(1) + } + input := os.Args[1] + output := os.Args[2] + err := os.MkdirAll(output, os.ModePerm) + if err != nil && !os.IsExist(err) { + log.Fatal(err) + } + + records, err := readMetaJsonFile(input) + if err != nil { + log.Fatal(err) + } + sort.Sort(ExportTestRecordArray(records)) + + index, err := os.Create(path.Join(output, "index.txt")) + if err != nil { + log.Fatal(err) + } + defer index.Close() + + var wg sync.WaitGroup + for _, record := range records { + if in(record.TestName, blacklist) { + continue + } + var goodUrls []string + for _, digest := range record.Digests { + if (in("vk", digest.ParamSet["config"]) || + in("gles", digest.ParamSet["config"])) && + digest.Status == "positive" { + goodUrls = append(goodUrls, digest.URL) + } + } + wg.Add(1) + go func(testName string, imgUrls []string, output string) { + defer wg.Done() + if err := processTest(testName, imgUrls, output); err != nil { + log.Fatal(err) + } + fmt.Printf("\r%-60s", testName) + }(record.TestName, goodUrls, output) + + _, err = fmt.Fprintf(index, "%s\n", record.TestName) + if err != nil { + log.Fatal(err) + } + } + wg.Wait() + fmt.Printf("\r%60s\n", "") +} diff --git a/tools/skqp/make_report.py b/tools/skqp/make_report.py new file mode 100755 index 0000000000..434557572a --- /dev/null +++ b/tools/skqp/make_report.py @@ -0,0 +1,41 @@ +#! /usr/bin/env python + +# Copyright 2017 Google Inc. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import glob +import os +import re +import sys + +import sysopen + +if len(sys.argv) == 2: + if not os.path.isdir(sys.argv[1]): + exit(1) + os.chdir(sys.argv[1]) + +head = ''' + + + +SkQP Report + + + +

SkQP Report

+
+''' + +reg = re.compile('="../../') +with open('report.html', 'w') as o: + o.write(head) + for x in glob.iglob('*/*/report.html'): + with open(x, 'r') as f: + o.write(reg.sub('="', f.read())) + o.write('\n\n') + +sysopen.sysopen('report.html') diff --git a/tools/skqp/skqp.cpp b/tools/skqp/skqp.cpp new file mode 100644 index 0000000000..b9864cd1d1 --- /dev/null +++ b/tools/skqp/skqp.cpp @@ -0,0 +1,140 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "gm_runner.h" + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wused-but-marked-unused" +#endif + +#include "gtest/gtest.h" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include "SkStream.h" +#include "SkString.h" + +//////////////////////////////////////////////////////////////////////////////// + +static std::string gReportDirectoryPath; +static std::unique_ptr gAssetMgr; + +//////////////////////////////////////////////////////////////////////////////// + +struct GMTestCase { + gm_runner::GMFactory fGMFactory; + gm_runner::SkiaBackend fBackend; +}; + +struct GMTest : public testing::Test { + GMTestCase fTest; + GMTest(GMTestCase t) : fTest(t) {} + void TestBody() override { + float result; + gm_runner::Error error; + std::tie(result, error) = + gm_runner::EvaluateGM(fTest.fBackend, fTest.fGMFactory, + gAssetMgr.get(), gReportDirectoryPath.c_str()); + EXPECT_EQ(error, gm_runner::Error::None); + if (gm_runner::Error::None == error) { + EXPECT_EQ(result, 0); + } + } +}; + +struct GMTestFactory : public testing::internal::TestFactoryBase { + GMTestCase fTest; + GMTestFactory(GMTestCase t) : fTest(t) {} + testing::Test* CreateTest() override { return new GMTest(fTest); } +}; + +//////////////////////////////////////////////////////////////////////////////// + +struct UnitTestTest : public testing::Test { + gm_runner::UnitTest fTest; + UnitTestTest(gm_runner::UnitTest test) : fTest(test) {} + void TestBody() override { + std::vector errors = gm_runner::ExecuteTest(fTest); + for (const std::string& error : errors) { + GTEST_NONFATAL_FAILURE_(error.c_str()); + } + } +}; + +struct UnitTestFactory : public testing::internal::TestFactoryBase { + gm_runner::UnitTest fTest; + UnitTestFactory(gm_runner::UnitTest test) : fTest(test) {} + testing::Test* CreateTest() override { return new UnitTestTest(fTest); } +}; + +//////////////////////////////////////////////////////////////////////////////// + +static void reg_test(const char* test, const char* testCase, + testing::internal::TestFactoryBase* fact) { + testing::internal::MakeAndRegisterTestInfo(test, + testCase, + nullptr, + nullptr, + testing::internal::CodeLocation(__FILE__, __LINE__), + testing::internal::GetTestTypeId(), + testing::Test::SetUpTestCase, + testing::Test::TearDownTestCase, + fact); +} + + +void register_skia_tests() { + gm_runner::InitSkia(); + + // Rendering Tests + std::vector backends = gm_runner::GetSupportedBackends(); + std::vector gms = gm_runner::GetGMFactories(gAssetMgr.get()); + for (auto backend : backends) { + const char* backendName = GetBackendName(backend); + std::string test = std::string("SkiaGM_") + backendName; + for (auto gmFactory : gms) { + std::string gmName = gm_runner::GetGMName(gmFactory); + reg_test(test.c_str(), gmName.c_str(), + new GMTestFactory(GMTestCase{gmFactory, backend})); + } + } + + for (gm_runner::UnitTest test : gm_runner::GetUnitTests()) { + reg_test("Skia_Unit_Tests", gm_runner::GetUnitTestName(test), new UnitTestFactory{test}); + } +} + +namespace { +struct StdAssetManager : public skqp::AssetManager { + SkString fPrefix; + StdAssetManager(const char* p) : fPrefix(p) {} + std::unique_ptr open(const char* path) override { + SkString fullPath = fPrefix.isEmpty() + ? SkString(path) + : SkStringPrintf("%s/%s", fPrefix.c_str(), path); + return SkStream::MakeFromFile(fullPath.c_str()); + } +}; +} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + if (argc < 2) { + std::cerr << "Usage:\n " << argv[0] + << " [GTEST_ARGUMENTS] GMKB_DIRECTORY_PATH GMKB_REPORT_PATH\n\n"; + return 1; + } + gAssetMgr.reset(new StdAssetManager(argv[1])); + if (argc > 2) { + gReportDirectoryPath = argv[2]; + } + register_skia_tests(); + return RUN_ALL_TESTS(); +} diff --git a/tools/skqp/skqp_asset_manager.h b/tools/skqp/skqp_asset_manager.h new file mode 100644 index 0000000000..b9bd4d6c81 --- /dev/null +++ b/tools/skqp/skqp_asset_manager.h @@ -0,0 +1,21 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef skqp_asset_manager_DEFINED +#define skqp_asset_manager_DEFINED + +#include + +class SkStreamAsset; + +namespace skqp { +class AssetManager { +public: + virtual ~AssetManager() {} + virtual std::unique_ptr open(const char* path) = 0; +}; +} // namespace skqp +#endif // skqp_asset_manager_DEFINED diff --git a/tools/skqp/sysopen.py b/tools/skqp/sysopen.py new file mode 100755 index 0000000000..f104ab9fa4 --- /dev/null +++ b/tools/skqp/sysopen.py @@ -0,0 +1,21 @@ +#! /usr/bin/env python2 +# Copyright 2017 Google Inc. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os +import subprocess +import sys + +def sysopen(arg): + plat = sys.platform + if plat.startswith('darwin'): + subprocess.call(["open", arg]) + elif plat.startswith('win'): + os.startfile(arg) + else: + subprocess.call(["xdg-open", arg]) + +if __name__ == '__main__': + for a in sys.argv[1:]: + sysopen(a) -- cgit v1.2.3