Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
gm_bindings.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2020 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include <set>
9#include <string>
10#include <emscripten.h>
11#include <emscripten/bind.h>
12#include <emscripten/html5.h>
13
14#include "gm/gm.h"
17#include "include/core/SkData.h"
28#include "src/core/SkMD5.h"
29#include "tests/Test.h"
30#include "tests/TestHarness.h"
31#include "tools/HashAndEncode.h"
36
37using namespace emscripten;
38
39/**
40 * Returns a JS array of strings containing the names of the registered GMs. GMs are only registered
41 * when their source is included in the "link" step, not if they are in something like libgm.a.
42 * The names are also logged to the console.
43 */
44static JSArray ListGMs() {
45 SkDebugf("Listing GMs\n");
46 JSArray gms = emscripten::val::array();
47 for (const skiagm::GMFactory& fact : skiagm::GMRegistry::Range()) {
48 std::unique_ptr<skiagm::GM> gm(fact());
49 SkDebugf("gm %s\n", gm->getName().c_str());
50 gms.call<void>("push", std::string(gm->getName().c_str()));
51 }
52 return gms;
53}
54
55static std::unique_ptr<skiagm::GM> getGMWithName(std::string name) {
56 for (const skiagm::GMFactory& fact : skiagm::GMRegistry::Range()) {
57 std::unique_ptr<skiagm::GM> gm(fact());
58 if (gm->getName().c_str() == name) {
59 return gm;
60 }
61 }
62 return nullptr;
63}
64
65/**
66 * Sets the given WebGL context to be "current" and then creates a GrDirectContext from that
67 * context.
68 */
69static sk_sp<GrDirectContext> MakeGrContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
70{
71 EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context);
72 if (r < 0) {
73 printf("failed to make webgl context current %d\n", r);
74 return nullptr;
75 }
76 // setup GrDirectContext
77 auto interface = GrGLMakeNativeInterface();
78 // setup contexts
80 return dContext;
81}
82
83static std::set<std::string> gKnownDigests;
84
85static void LoadKnownDigest(std::string md5) {
86 gKnownDigests.insert(md5);
87}
88
89static std::map<std::string, sk_sp<SkData>> gResources;
90
91static sk_sp<SkData> getResource(const char* name) {
92 auto it = gResources.find(name);
93 if (it == gResources.end()) {
94 SkDebugf("Resource %s not found\n", name);
95 return nullptr;
96 }
97 return it->second;
98}
99
100static void LoadResource(std::string name, WASMPointerU8 bPtr, size_t len) {
101 const uint8_t* bytes = reinterpret_cast<const uint8_t*>(bPtr);
102 auto data = SkData::MakeFromMalloc(bytes, len);
103 gResources[name] = std::move(data);
104
105 if (!gResourceFactory) {
107 }
108}
109
110/**
111 * Runs the given GM and returns a JS object. If the GM was successful, the object will have the
112 * following properties:
113 * "png" - a Uint8Array of the PNG data extracted from the surface.
114 * "hash" - a string which is the md5 hash of the pixel contents and the metadata.
115 */
116static JSObject RunGM(sk_sp<GrDirectContext> ctx, std::string name) {
117 JSObject result = emscripten::val::object();
118 auto gm = getGMWithName(name);
119 if (!gm) {
120 SkDebugf("Could not find gm with name %s\n", name.c_str());
121 return result;
122 }
123 // TODO(kjlubick) make these configurable somehow. This probably makes sense to do as function
124 // parameters.
125 auto alphaType = SkAlphaType::kPremul_SkAlphaType;
126 auto colorType = SkColorType::kN32_SkColorType;
127 SkISize size = gm->getISize();
128 SkImageInfo info = SkImageInfo::Make(size, colorType, alphaType);
130 ctx.get(), skgpu::Budgeted::kYes, info, 0, kBottomLeft_GrSurfaceOrigin, nullptr, true));
131 if (!surface) {
132 SkDebugf("Could not make surface\n");
133 return result;
134 }
135 auto canvas = surface->getCanvas();
136
137 gm->onceBeforeDraw();
138 SkString msg;
139 // Based on GMSrc::draw from DM.
140 auto gpuSetupResult = gm->gpuSetup(canvas, &msg);
141 if (gpuSetupResult == skiagm::DrawResult::kFail) {
142 SkDebugf("Error with gpu setup for gm %s: %s\n", name.c_str(), msg.c_str());
143 return result;
144 } else if (gpuSetupResult == skiagm::DrawResult::kSkip) {
145 return result;
146 }
147
148 auto drawResult = gm->draw(canvas, &msg);
149 if (drawResult == skiagm::DrawResult::kFail) {
150 SkDebugf("Error with gm %s: %s\n", name.c_str(), msg.c_str());
151 return result;
152 } else if (drawResult == skiagm::DrawResult::kSkip) {
153 return result;
154 }
155 ctx->flushAndSubmit(surface.get(), GrSyncCpu::kYes);
156
157 // Based on GPUSink::readBack
159 bitmap.allocPixels(info);
160 if (!canvas->readPixels(bitmap, 0, 0)) {
161 SkDebugf("Could not read pixels back\n");
162 return result;
163 }
164
165 // Now we need to encode to PNG and get the md5 hash of the pixels (and colorspace and stuff).
166 // This is based on Task::Run from DM.cpp
167 std::unique_ptr<HashAndEncode> hashAndEncode = std::make_unique<HashAndEncode>(bitmap);
169 SkMD5 hash;
170 hashAndEncode->feedHash(&hash);
171 SkMD5::Digest digest = hash.finish();
172 for (int i = 0; i < 16; i++) {
173 md5.appendf("%02x", digest.data[i]);
174 }
175
176 auto ok = gKnownDigests.find(md5.c_str());
177 if (ok == gKnownDigests.end()) {
178 // We only need to decode the image if it is "interesting", that is, we have not written it
179 // before to disk and uploaded it to gold.
181 // We do not need to include the keys because they are optional - they are not read by Gold.
183 hashAndEncode->encodePNG(&stream, md5.c_str(), empty, empty);
184
185 auto data = stream.detachAsData();
186
187 // This is the cleanest way to create a new Uint8Array with a copy of the data that is not
188 // in the WASM heap. kjlubick tried returning a pointer inside an SkData, but that lead to
189 // some use after free issues. By making the copy using the JS transliteration, we don't
190 // risk the SkData object being cleaned up before we make the copy.
191 Uint8Array pngData = emscripten::val(
192 // https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#memory-views
193 typed_memory_view(data->size(), data->bytes())
194 ).call<Uint8Array>("slice"); // slice with no args makes a copy of the memory view.
195
196 result.set("png", pngData);
197 gKnownDigests.emplace(md5.c_str());
198 }
199 result.set("hash", md5.c_str());
200 return result;
201}
202
204 SkDebugf("Listing Tests\n");
205 JSArray tests = emscripten::val::array();
206 for (auto test : skiatest::TestRegistry::Range()) {
207 SkDebugf("test %s\n", test.fName);
208 tests.call<void>("push", std::string(test.fName));
209 }
210 return tests;
211}
212
213static skiatest::Test getTestWithName(std::string name, bool* ok) {
214 for (auto test : skiatest::TestRegistry::Range()) {
215 if (name == test.fName) {
216 *ok = true;
217 return test;
218 }
219 }
220 *ok = false;
221 return skiatest::Test::MakeCPU(nullptr, nullptr);
222}
223
224// Based on DM.cpp:run_test
227
228 void reportFailed(const skiatest::Failure& failure) override {
229 SkDebugf("Test %s failed: %s\n", fName.c_str(), failure.toString().c_str());
230 fResult.set("result", "failed");
231 fResult.set("msg", failure.toString().c_str());
232 }
233 std::string fName;
235};
236
237/**
238 * Runs the given Test and returns a JS object. If the Test was located, the object will have the
239 * following properties:
240 * "result" : One of "passed", "failed", "skipped".
241 * "msg": May be non-empty on failure
242 */
243static JSObject RunTest(std::string name) {
244 JSObject result = emscripten::val::object();
245 bool ok = false;
246 auto test = getTestWithName(name, &ok);
247 if (!ok) {
248 SkDebugf("Could not find test with name %s\n", name.c_str());
249 return result;
250 }
251 GrContextOptions grOpts;
252 if (test.fTestType == skiatest::TestType::kGanesh) {
253 result.set("result", "passed"); // default to passing - the reporter will mark failed.
255 test.modifyGrContextOptions(&grOpts);
256 test.ganesh(&reporter, grOpts);
257 return result;
258 } else if (test.fTestType == skiatest::TestType::kGraphite) {
259 SkDebugf("Graphite test %s not yet supported\n", name.c_str());
260 return result;
261 }
262
263 result.set("result", "passed"); // default to passing - the reporter will mark failed.
265 test.cpu(&reporter);
266 return result;
267}
268
269namespace skiatest {
270
272
273// These are the supported ContextTypeFilterFn. They are defined in Test.h and implemented here.
276}
278 return ct == skgpu::ContextType::kMock;
279}
280// These are not supported
281bool IsVulkanContextType(ContextType) { return false; }
282bool IsMetalContextType(ContextType) { return false; }
283bool IsDirect3DContextType(ContextType) { return false; }
284bool IsDawnContextType(ContextType) { return false; }
285
287 Reporter* reporter, const GrContextOptions& options) {
288 for (auto contextType : {skgpu::ContextType::kGLES, skgpu::ContextType::kMock}) {
289 if (filter && !(*filter)(contextType)) {
290 continue;
291 }
292
294 sk_gpu_test::ContextInfo ctxInfo = factory.getContextInfo(contextType);
295
296 REPORTER_ASSERT(reporter, ctxInfo.directContext() != nullptr);
297 if (!ctxInfo.directContext()) {
298 return;
299 }
300 ctxInfo.testContext()->makeCurrent();
301 // From DMGpuTestProcs.cpp
302 (*testFn)(reporter, ctxInfo);
303 // Sync so any release/finished procs get called.
305 }
306}
307} // namespace skiatest
308
309namespace {
310
311// A GLtestContext that we can return from CreatePlatformGLTestContext below.
312// It doesn't have to do anything WebGL-specific that I know of but we can't return
313// a GLTestContext because it has pure virtual methods that need to be implemented.
314class WasmWebGlTestContext : public sk_gpu_test::GLTestContext {
315public:
316 WasmWebGlTestContext() {}
317 ~WasmWebGlTestContext() override {
318 this->teardown();
319 }
320 // We assume WebGL only has one context and that it is always current.
321 // Therefore these context related functions return null intentionally.
322 // It's possible that more tests will pass if these were correctly implemented.
323 std::unique_ptr<GLTestContext> makeNew() const override {
324 // This is supposed to create a new GL context in a new GLTestContext.
325 // Specifically for tests that do not want to re-use the existing one.
326 return nullptr;
327 }
328 void onPlatformMakeNotCurrent() const override { }
329 void onPlatformMakeCurrent() const override { }
330 std::function<void()> onPlatformGetAutoContextRestore() const override {
331 return nullptr;
332 }
333 GrGLFuncPtr onPlatformGetProcAddress(const char* procName) const override {
334 return nullptr;
335 }
336};
337} // namespace
338
339namespace sk_gpu_test {
341 GLTestContext *shareContext) {
342 return new WasmWebGlTestContext();
343}
344} // namespace sk_gpu_test
345
347
351
353 function("Init", &Init);
354 function("ListGMs", &ListGMs);
355 function("ListTests", &ListTests);
356 function("LoadKnownDigest", &LoadKnownDigest);
357 function("_LoadResource", &LoadResource);
358 function("MakeGrContext", &MakeGrContext);
359 function("RunGM", &RunGM);
360 function("RunTest", &RunTest);
361
362 class_<GrDirectContext>("GrDirectContext")
363 .smart_ptr<sk_sp<GrDirectContext>>("sk_sp<GrDirectContext>");
364}
static BlurTest tests[]
Definition BlurTest.cpp:84
static SkMD5::Digest md5(const SkBitmap &bm)
Definition CodecTest.cpp:77
const char * options
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition DM.cpp:213
#define test(name)
reporter
SK_API sk_sp< const GrGLInterface > GrGLMakeNativeInterface()
void(* GrGLFuncPtr)()
GrGLStandard
Definition GrGLTypes.h:19
@ kBottomLeft_GrSurfaceOrigin
Definition GrTypes.h:149
sk_sp< SkData >(* gResourceFactory)(const char *)
Definition Resources.cpp:21
@ kPremul_SkAlphaType
pixel components are premultiplied by alpha
Definition SkAlphaType.h:29
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
static SkColorType colorType(AImageDecoder *decoder, const AImageDecoderHeaderInfo *headerInfo)
static bool ok(int result)
static uint32_t hash(const SkShaderBase::GradientInfo &v)
TestHarness
Definition TestHarness.h:14
#define REPORTER_ASSERT(r, cond,...)
Definition Test.h:286
emscripten::val JSObject
Definition WasmCommon.h:28
uintptr_t WASMPointerU8
Definition WasmCommon.h:46
emscripten::val Uint8Array
Definition WasmCommon.h:32
emscripten::val JSArray
Definition WasmCommon.h:27
void flushAndSubmit(GrSyncCpu sync=GrSyncCpu::kNo)
static sk_sp< SkData > MakeFromMalloc(const void *data, size_t length)
Definition SkData.cpp:107
Definition SkMD5.h:19
const char * c_str() const
Definition SkString.h:133
GrDirectContext * directContext() const
TestContext * testContext() const
virtual std::unique_ptr< GLTestContext > makeNew() const
virtual GrGLFuncPtr onPlatformGetProcAddress(const char *) const =0
virtual std::function< void()> onPlatformGetAutoContextRestore() const =0
virtual void onPlatformMakeNotCurrent() const =0
virtual void onPlatformMakeCurrent() const =0
T * get() const
Definition SkRefCnt.h:303
VkSurfaceKHR surface
Definition main.cc:49
EMSCRIPTEN_KEEPALIVE void empty()
GAsyncResult * result
Dart_NativeFunction function
Definition fuchsia.cc:51
const char * name
Definition fuchsia.cc:50
static std::unique_ptr< skiagm::GM > getGMWithName(std::string name)
static std::map< std::string, sk_sp< SkData > > gResources
void Init()
static std::set< std::string > gKnownDigests
static sk_sp< SkData > getResource(const char *name)
static void LoadKnownDigest(std::string md5)
static JSArray ListTests()
static sk_sp< GrDirectContext > MakeGrContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
static JSArray ListGMs()
TestHarness CurrentTestHarness()
static skiatest::Test getTestWithName(std::string name, bool *ok)
EMSCRIPTEN_BINDINGS(GMs)
static JSObject RunGM(sk_sp< GrDirectContext > ctx, std::string name)
static JSObject RunTest(std::string name)
static void LoadResource(std::string name, WASMPointerU8 bPtr, size_t len)
SK_API sk_sp< GrDirectContext > MakeGL()
SK_API sk_sp< SkSurface > RenderTarget(GrRecordingContext *context, skgpu::Budgeted budgeted, const SkImageInfo &imageInfo, int sampleCount, GrSurfaceOrigin surfaceOrigin, const SkSurfaceProps *surfaceProps, bool shouldCreateWithMips=false, bool isProtected=false)
void UsePortableFontMgr()
GLTestContext * CreatePlatformGLTestContext(GrGLStandard forcedGpuAPI, GLTestContext *shareContext)
GrBackendApi ContextTypeBackend(skgpu::ContextType type)
std::function< std::unique_ptr< skiagm::GM >()> GMFactory
Definition gm.h:239
void RunWithGaneshTestContexts(GrContextTestFn *testFn, ContextTypeFilterFn *filter, Reporter *reporter, const GrContextOptions &options)
bool IsMetalContextType(skgpu::ContextType type)
bool IsGLContextType(skgpu::ContextType type)
bool ContextTypeFilterFn(GpuContextType)
Definition Test.h:214
bool IsMockContextType(skgpu::ContextType type)
bool IsDirect3DContextType(skgpu::ContextType type)
void GrContextTestFn(Reporter *, const sk_gpu_test::ContextInfo &)
Definition Test.h:213
bool IsDawnContextType(skgpu::ContextType type)
bool IsVulkanContextType(skgpu::ContextType type)
static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at)
uint8_t data[16]
Definition SkMD5.h:39
std::string fName
JSObject fResult
void reportFailed(const skiatest::Failure &failure) override
WasmReporter(std::string name, JSObject result)
SkString toString() const
Definition Test.cpp:41
static Test MakeCPU(const char *name, CPUTestProc proc)
Definition Test.h:120