Flutter Engine
The Flutter Engine
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();
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 }
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
350}
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
Definition: FontMgrTest.cpp:39
SK_API sk_sp< const GrGLInterface > GrGLMakeNativeInterface()
void(* GrGLFuncPtr)()
Definition: GrGLInterface.h:17
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
Definition: GLTestContext.h:51
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
static std::unique_ptr< skiagm::GM > getGMWithName(std::string name)
Definition: gm_bindings.cpp:55
static std::map< std::string, sk_sp< SkData > > gResources
Definition: gm_bindings.cpp:89
void Init()
static std::set< std::string > gKnownDigests
Definition: gm_bindings.cpp:83
static sk_sp< SkData > getResource(const char *name)
Definition: gm_bindings.cpp:91
static void LoadKnownDigest(std::string md5)
Definition: gm_bindings.cpp:85
static JSArray ListTests()
static sk_sp< GrDirectContext > MakeGrContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
Definition: gm_bindings.cpp:69
static JSArray ListGMs()
Definition: gm_bindings.cpp:44
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_sp< const GrGLInterface >, const GrContextOptions &)
std::string printf(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: SkSLString.cpp:83
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()
Definition: bitmap.py:1
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
GLTestContext * CreatePlatformGLTestContext(GrGLStandard forcedGpuAPI, GLTestContext *shareContext)
GrBackendApi ContextTypeBackend(skgpu::ContextType type)
Definition: ContextType.cpp:92
ContextType
Definition: ContextType.h:19
@ kGLES
OpenGL context.
@ kMock
Dawn on OpenGL ES.
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)
Definition: SkSize.h:16
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
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63