diff options
author | Hal Canary <halcanary@google.com> | 2017-10-11 16:00:31 -0400 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2017-10-17 19:01:13 +0000 |
commit | 754271347a36f2b029637777a5591c9bde34244f (patch) | |
tree | f5f950ba8db924b2d220b82aa6b7ab2966a42fc1 | |
parent | a2ac30da36c80f616c909c671a240f2d468db124 (diff) |
GPU-CTS Program
Add new application, called GPU-CTS (GPU Compatibility Test Suite),
which executes skia gms against OpenGL and Vulkan backends. Makes use
of googletest library for consistancy with Android CTS programs.
Add googletest to DEPS
gm_knowledge.h header as a stub for future work on validating gm output.
gm_runner can be re-used in other programs. Talks to Skia and GM with a
simple API.
gpuctx executable wraps gm_runner and googletest together.
Change-Id: Ie7350b22164fa73e44121c39b0f36da4038a700b
Reviewed-on: https://skia-review.googlesource.com/56601
Reviewed-by: Hal Canary <halcanary@google.com>
Commit-Queue: Hal Canary <halcanary@google.com>
-rw-r--r-- | BUILD.gn | 18 | ||||
-rw-r--r-- | DEPS | 1 | ||||
-rw-r--r-- | third_party/googletest/BUILD.gn | 16 | ||||
-rw-r--r-- | tools/gpucts/gm_knowledge.c | 12 | ||||
-rw-r--r-- | tools/gpucts/gm_knowledge.h | 57 | ||||
-rw-r--r-- | tools/gpucts/gm_runner.cpp | 129 | ||||
-rw-r--r-- | tools/gpucts/gm_runner.h | 76 | ||||
-rw-r--r-- | tools/gpucts/gpucts.cpp | 165 |
8 files changed, 474 insertions, 0 deletions
@@ -1634,6 +1634,24 @@ if (skia_enable_tools) { ] } + if (!is_win) { + test_app("gpucts") { + sources = [ + "dm/DMGpuTestProcs.cpp", + "tools/gpucts/gm_knowledge.c", + "tools/gpucts/gm_runner.cpp", + "tools/gpucts/gpucts.cpp", + ] + deps = [ + ":gm", + ":gpu_tool_utils", + ":skia", + ":tests", + "//third_party/googletest", + ] + } + } + if (skia_enable_gpu) { test_app("viewer") { is_shared_library = is_android @@ -7,6 +7,7 @@ deps = { "third_party/externals/dng_sdk" : "https://android.googlesource.com/platform/external/dng_sdk.git@96443b262250c390b0caefbf3eed8463ba35ecae", "third_party/externals/expat" : "https://android.googlesource.com/platform/external/expat.git@android-6.0.1_r55", "third_party/externals/freetype" : "https://skia.googlesource.com/third_party/freetype2.git@447a0b62634802d8acdb56008cff5ff4e50be244", + "third_party/externals/googletest" : "https://android.googlesource.com/platform/external/googletest@dd43b9998e9a44a579a7aba6c1309407d1a5ed95", "third_party/externals/harfbuzz" : "https://skia.googlesource.com/third_party/harfbuzz.git@1.4.2", "third_party/externals/icu" : "https://chromium.googlesource.com/chromium/deps/icu.git@ec9c1133693148470ffe2e5e53576998e3650c1d", "third_party/externals/imgui" : "https://github.com/ocornut/imgui.git@6384eee34f08cb7eab8d835043e1738e4adcdf75", diff --git a/third_party/googletest/BUILD.gn b/third_party/googletest/BUILD.gn new file mode 100644 index 0000000000..518360bdbe --- /dev/null +++ b/third_party/googletest/BUILD.gn @@ -0,0 +1,16 @@ +# 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("../third_party.gni") + +if (!is_win) { + third_party("googletest") { + public_include_dirs = [ "../externals/googletest/googletest/include" ] + include_dirs = [ "../externals/googletest/googletest" ] + sources = [ + "../externals/googletest/googletest/src/gtest-all.cc", + ] + } +} diff --git a/tools/gpucts/gm_knowledge.c b/tools/gpucts/gm_knowledge.c new file mode 100644 index 0000000000..8dbdd00f09 --- /dev/null +++ b/tools/gpucts/gm_knowledge.c @@ -0,0 +1,12 @@ +/* + * 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 new file mode 100644 index 0000000000..d9d71e05c5 --- /dev/null +++ b/tools/gpucts/gm_knowledge.h @@ -0,0 +1,57 @@ +/* + * 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 <stdint.h> +#include <stdbool.h> + +/** +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 new file mode 100644 index 0000000000..92d918106d --- /dev/null +++ b/tools/gpucts/gm_runner.cpp @@ -0,0 +1,129 @@ +/* + * 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 <algorithm> + +#include "SkGraphics.h" +#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* contextFactory) { + return contextFactory->get(to_context_type(backend)) != nullptr; +} + + +GMK_ImageData Evaluate(SkiaBackend backend, + GMFactory gmFact, + GrContextFactory* contextFactory, + std::vector<uint32_t>* storage) { + SkASSERT(contextFactory); + SkASSERT(gmFact); + SkASSERT(storage); + std::unique_ptr<skiagm::GM> gm(gmFact(nullptr)); + SkASSERT(gm.get()); + int w = SkScalarRoundToInt(gm->width()); + int h = SkScalarRoundToInt(gm->height()); + 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<SkSurface> 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}; +} + +std::unique_ptr<GrContextFactory> make_gr_context_factory() { + GrContextOptions grContextOptions; // TODO: change options? + return std::unique_ptr<GrContextFactory>(new GrContextFactory(grContextOptions)); +} + +SkiaContext::SkiaContext() : fGrContextFactory(make_gr_context_factory()) { + SkGraphics::Init(); +} + +void SkiaContext::resetContextFactory() { + fGrContextFactory->destroyContexts(); +} + +SkiaContext::~SkiaContext() {} + +} // namespace gm_runner + +#else +namespace sk_gpu_test { + class GrContextFactory {}; +} +namespace gm_runner { +SkiaContext::SkiaContext() {} +SkiaContext::~SkiaContext() {} +void SkiaContext::resetContextFactory() {} +bool BackendSupported(SkiaBackend, sk_gpu_test::GrContextFactory*) { return false; } +GMK_ImageData Evaluate(SkiaBackend, GMFactory, + sk_gpu_test::GrContextFactory*, std::vector<uint32_t>*) { + return GMK_ImageData{nullptr, 0, 0}; +} +const char* GetBackendName(SkiaBackend backend) { return "Unknown"; } +} // namespace gm_runner +#endif + +namespace gm_runner { + +std::vector<GMFactory> GetGMFactories() { + std::vector<GMFactory> 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<skiagm::GM> 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 new file mode 100644 index 0000000000..6324914799 --- /dev/null +++ b/tools/gpucts/gm_runner.h @@ -0,0 +1,76 @@ +/* + * 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 <memory> +#include <string> +#include <vector> + +#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, +}; + +/** +This class initializes Skia and a GrContextFactory. +*/ +struct SkiaContext { + SkiaContext(); + ~SkiaContext(); + void resetContextFactory(); + std::unique_ptr<sk_gpu_test::GrContextFactory> fGrContextFactory; +}; + +bool BackendSupported(SkiaBackend, sk_gpu_test::GrContextFactory*); + +/** +@return a list of all Skia GMs in lexicographic order. +*/ +std::vector<GMFactory> 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, + sk_gpu_test::GrContextFactory*, + std::vector<uint32_t>* storage); + +} // namespace gm_runner + +#endif // gm_runner_DEFINED diff --git a/tools/gpucts/gpucts.cpp b/tools/gpucts/gpucts.cpp new file mode 100644 index 0000000000..d244d91e2d --- /dev/null +++ b/tools/gpucts/gpucts.cpp @@ -0,0 +1,165 @@ +/* + * 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 "Test.h" + +//////////////////////////////////////////////////////////////////////////////// + +struct GMTestCase { + gm_runner::GMFactory fGMFactory; + gm_runner::SkiaBackend fBackend; + sk_gpu_test::GrContextFactory* fGrContextFactory; +}; + +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, fTest.fGrContextFactory)); + return; + } + std::vector<uint32_t> pixels; + GMK_ImageData imgData = gm_runner::Evaluate( + fTest.fBackend, fTest.fGMFactory, fTest.fGrContextFactory, &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); } +}; + +//////////////////////////////////////////////////////////////////////////////// + +struct UnitTestData { + gm_runner::SkiaContext* fContext; + skiatest::TestProc fProc; +}; + +struct UnitTest : public testing::Test { + UnitTestData fUnitTestData; + UnitTest(UnitTestData d) : fUnitTestData(d) {} + 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; + fUnitTestData.fContext->resetContextFactory(); + fUnitTestData.fProc(&r, fUnitTestData.fContext->fGrContextFactory.get()); + } +}; + +struct UnitTestFactory : testing::internal::TestFactoryBase { + UnitTestData fUnitTestData; + UnitTestFactory(UnitTestData d) : fUnitTestData(d) {} + testing::Test* CreateTest() override { return new UnitTest(fUnitTestData); } +}; + +std::vector<const skiatest::Test*> GetUnitTests() { + // Unit Tests + std::vector<const skiatest::Test*> 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); + gm_runner::SkiaContext context; + sk_gpu_test::GrContextFactory* grContextFactory = context.fGrContextFactory.get(); + + // 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<gm_runner::GMFactory> 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, grContextFactory})); + + if (!gm_runner::BackendSupported(backend, context.fGrContextFactory.get())) { + 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, grContextFactory})); + } + } + + for (const skiatest::Test* test : GetUnitTests()) { + reg_test("Skia_Unit_Tests", test->name, + new UnitTestFactory(UnitTestData{&context, test->proc})); + } + return RUN_ALL_TESTS(); +} + |