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:
parent
a2ac30da36
commit
754271347a
18
BUILD.gn
18
BUILD.gn
@ -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
1
DEPS
@ -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
16
third_party/googletest/BUILD.gn
vendored
Normal 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",
|
||||
]
|
||||
}
|
||||
}
|
12
tools/gpucts/gm_knowledge.c
Normal file
12
tools/gpucts/gm_knowledge.c
Normal 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; }
|
57
tools/gpucts/gm_knowledge.h
Normal file
57
tools/gpucts/gm_knowledge.h
Normal 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
129
tools/gpucts/gm_runner.cpp
Normal 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
76
tools/gpucts/gm_runner.h
Normal 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
165
tools/gpucts/gpucts.cpp
Normal 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();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user