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>
This commit is contained in:
Hal Canary 2017-10-11 16:00:31 -04:00 committed by Skia Commit-Bot
parent a2ac30da36
commit 754271347a
8 changed files with 474 additions and 0 deletions

View File

@ -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

1
DEPS
View File

@ -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",

16
third_party/googletest/BUILD.gn vendored Normal file
View File

@ -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",
]
}
}

View File

@ -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; }

View File

@ -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

129
tools/gpucts/gm_runner.cpp Normal file
View File

@ -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

76
tools/gpucts/gm_runner.h Normal file
View File

@ -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

165
tools/gpucts/gpucts.cpp Normal file
View File

@ -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();
}