blob: 4aa6ad86687afb8f3bf2681066d33365240713d8 [file] [log] [blame]
Kevin Lubick00398742020-10-08 10:05:07 -04001/*
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
Kevin Lubicka25c0612020-10-19 15:18:45 -04008#include <set>
Kevin Lubick00398742020-10-08 10:05:07 -04009#include <string>
10#include <emscripten.h>
11#include <emscripten/bind.h>
12#include <emscripten/html5.h>
13
14#include "gm/gm.h"
15#include "include/core/SkBitmap.h"
16#include "include/core/SkCanvas.h"
Kevin Lubickbffc1162020-10-21 15:36:35 -040017#include "include/core/SkData.h"
Kevin Lubick00398742020-10-08 10:05:07 -040018#include "include/core/SkImageInfo.h"
19#include "include/core/SkStream.h"
20#include "include/core/SkSurface.h"
Kevin Lubickdffd20e2020-11-02 10:32:05 -050021#include "include/gpu/GrContextOptions.h"
Kevin Lubick00398742020-10-08 10:05:07 -040022#include "include/gpu/GrDirectContext.h"
23#include "include/gpu/gl/GrGLInterface.h"
24#include "include/gpu/gl/GrGLTypes.h"
Kevin Lubickacdc2832020-10-23 11:28:57 -040025#include "modules/canvaskit/WasmCommon.h"
26#include "src/core/SkFontMgrPriv.h"
Kevin Lubick00398742020-10-08 10:05:07 -040027#include "src/core/SkMD5.h"
Kevin Lubickdffd20e2020-11-02 10:32:05 -050028#include "tests/Test.h"
Kevin Lubick00398742020-10-08 10:05:07 -040029#include "tools/HashAndEncode.h"
Kevin Lubickbffc1162020-10-21 15:36:35 -040030#include "tools/ResourceFactory.h"
Kevin Lubick00398742020-10-08 10:05:07 -040031#include "tools/flags/CommandLineFlags.h"
Kevin Lubickacdc2832020-10-23 11:28:57 -040032#include "tools/fonts/TestFontMgr.h"
Kevin Lubick00398742020-10-08 10:05:07 -040033
34using namespace emscripten;
35
36/**
37 * Returns a JS array of strings containing the names of the registered GMs. GMs are only registered
38 * when their source is included in the "link" step, not if they are in something like libgm.a.
39 * The names are also logged to the console.
40 */
41static JSArray ListGMs() {
42 SkDebugf("Listing GMs\n");
43 JSArray gms = emscripten::val::array();
44 for (skiagm::GMFactory fact : skiagm::GMRegistry::Range()) {
45 std::unique_ptr<skiagm::GM> gm(fact());
46 SkDebugf("gm %s\n", gm->getName());
47 gms.call<void>("push", std::string(gm->getName()));
48 }
49 return gms;
50}
51
52static std::unique_ptr<skiagm::GM> getGMWithName(std::string name) {
53 for (skiagm::GMFactory fact : skiagm::GMRegistry::Range()) {
54 std::unique_ptr<skiagm::GM> gm(fact());
55 if (gm->getName() == name) {
56 return gm;
57 }
58 }
59 return nullptr;
60}
61
62/**
63 * Sets the given WebGL context to be "current" and then creates a GrDirectContext from that
64 * context.
65 */
66static sk_sp<GrDirectContext> MakeGrContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
67{
68 EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context);
69 if (r < 0) {
70 printf("failed to make webgl context current %d\n", r);
71 return nullptr;
72 }
73 // setup GrDirectContext
74 auto interface = GrGLMakeNativeInterface();
75 // setup contexts
76 sk_sp<GrDirectContext> dContext(GrDirectContext::MakeGL(interface));
77 return dContext;
78}
79
Kevin Lubicka25c0612020-10-19 15:18:45 -040080static std::set<std::string> gKnownDigests;
81
82static void LoadKnownDigest(std::string md5) {
83 gKnownDigests.insert(md5);
84}
85
Kevin Lubickbffc1162020-10-21 15:36:35 -040086static std::map<std::string, sk_sp<SkData>> gResources;
87
88static sk_sp<SkData> getResource(const char* name) {
89 auto it = gResources.find(name);
90 if (it == gResources.end()) {
91 SkDebugf("Resource %s not found\n", name);
92 return nullptr;
93 }
94 return it->second;
95}
96
97static void LoadResource(std::string name, uintptr_t /* byte* */ bPtr, size_t len) {
98 const uint8_t* bytes = reinterpret_cast<const uint8_t*>(bPtr);
99 auto data = SkData::MakeFromMalloc(bytes, len);
100 gResources[name] = std::move(data);
101
102 if (!gResourceFactory) {
103 gResourceFactory = getResource;
104 }
105}
106
Kevin Lubick00398742020-10-08 10:05:07 -0400107/**
108 * Runs the given GM and returns a JS object. If the GM was successful, the object will have the
109 * following properties:
110 * "png" - a Uint8Array of the PNG data extracted from the surface.
111 * "hash" - a string which is the md5 hash of the pixel contents and the metadata.
112 */
113static JSObject RunGM(sk_sp<GrDirectContext> ctx, std::string name) {
114 JSObject result = emscripten::val::object();
115 auto gm = getGMWithName(name);
116 if (!gm) {
117 SkDebugf("Could not find gm with name %s\n", name.c_str());
118 return result;
119 }
120 // TODO(kjlubick) make these configurable somehow. This probably makes sense to do as function
121 // parameters.
122 auto alphaType = SkAlphaType::kPremul_SkAlphaType;
123 auto colorType = SkColorType::kN32_SkColorType;
124 SkISize size = gm->getISize();
125 SkImageInfo info = SkImageInfo::Make(size, colorType, alphaType);
126 sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(ctx.get(),
127 SkBudgeted::kYes,
128 info, 0,
129 kBottomLeft_GrSurfaceOrigin,
130 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(ctx.get(), 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 surface->flushAndSubmit(true);
156
157 // Based on GPUSink::readBack
158 SkBitmap bitmap;
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);
168 SkString md5;
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
Kevin Lubicka25c0612020-10-19 15:18:45 -0400176 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.
180 SkDynamicMemoryWStream stream;
181 // We do not need to include the keys because they are optional - they are not read by Gold.
182 CommandLineFlags::StringArray empty;
183 hashAndEncode->encodePNG(&stream, md5.c_str(), empty, empty);
Kevin Lubick00398742020-10-08 10:05:07 -0400184
Kevin Lubicka25c0612020-10-19 15:18:45 -0400185 auto data = stream.detachAsData();
Kevin Lubick00398742020-10-08 10:05:07 -0400186
Kevin Lubicka25c0612020-10-19 15:18:45 -0400187 // 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.
Kevin Lubick00398742020-10-08 10:05:07 -0400195
Kevin Lubicka25c0612020-10-19 15:18:45 -0400196 result.set("png", pngData);
197 gKnownDigests.emplace(md5.c_str());
198 }
Kevin Lubick00398742020-10-08 10:05:07 -0400199 result.set("hash", md5.c_str());
200 return result;
201}
202
Kevin Lubickdffd20e2020-11-02 10:32:05 -0500203static JSArray ListTests() {
204 SkDebugf("Listing Tests\n");
205 JSArray tests = emscripten::val::array();
206 for (auto test : skiatest::TestRegistry::Range()) {
207 SkDebugf("test %s\n", test.name);
208 tests.call<void>("push", std::string(test.name));
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.name) {
216 *ok = true;
217 return test;
218 }
219 }
220 *ok = false;
221 return skiatest::Test(nullptr, false, nullptr);
222}
223
224// Based on DM.cpp:run_test
225struct WasmReporter : public skiatest::Reporter {
226 WasmReporter(std::string name, JSObject result): fName(name), fResult(result){}
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;
234 JSObject fResult;
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.needsGpu) {
253 result.set("result", "passed"); // default to passing - the reporter will mark failed.
254 WasmReporter reporter(name, result);
255 test.run(&reporter, grOpts);
256 return result;
257 }
258
259 result.set("result", "passed"); // default to passing - the reporter will mark failed.
260 WasmReporter reporter(name, result);
261 test.run(&reporter, grOpts);
262 return result;
263}
264
265namespace skiatest {
266
Kevin Lubickdffd20e2020-11-02 10:32:05 -0500267using ContextType = sk_gpu_test::GrContextFactory::ContextType;
268
269// These are the supported GrContextTypeFilterFn
270bool IsGLContextType(ContextType ct) {
271 return GrBackendApi::kOpenGL == sk_gpu_test::GrContextFactory::ContextTypeBackend(ct);
272}
273bool IsRenderingGLContextType(ContextType ct) {
274 return IsGLContextType(ct) && sk_gpu_test::GrContextFactory::IsRenderingContext(ct);
275}
276bool IsRenderingGLOrMetalContextType(ContextType ct) {
277 return IsRenderingGLContextType(ct);
278}
279bool IsMockContextType(ContextType ct) {
280 return ct == ContextType::kMock_ContextType;
281}
282// These are not supported
283bool IsVulkanContextType(ContextType) {return false;}
284bool IsMetalContextType(ContextType) {return false;}
285bool IsDirect3DContextType(ContextType) {return false;}
286bool IsDawnContextType(ContextType) {return false;}
287
288void RunWithGPUTestContexts(GrContextTestFn* test, GrContextTypeFilterFn* contextTypeFilter,
289 Reporter* reporter, const GrContextOptions& options) {
290 for (auto contextType : {ContextType::kGLES_ContextType, ContextType::kMock_ContextType}) {
291 if (contextTypeFilter && !(*contextTypeFilter)(contextType)) {
292 continue;
293 }
Nathaniel Nifong110367d2021-01-21 13:16:04 -0500294
295 sk_gpu_test::GrContextFactory factory(options);
296 sk_gpu_test::ContextInfo ctxInfo = factory.getContextInfo(contextType);
297
298 REPORTER_ASSERT(reporter, ctxInfo.directContext() != nullptr);
299 if (!ctxInfo.directContext()) {
Kevin Lubickdffd20e2020-11-02 10:32:05 -0500300 return;
301 }
Nathaniel Nifong110367d2021-01-21 13:16:04 -0500302 ctxInfo.testContext()->makeCurrent();
Kevin Lubickdffd20e2020-11-02 10:32:05 -0500303 // From DMGpuTestProcs.cpp
304 (*test)(reporter, ctxInfo);
305 // Sync so any release/finished procs get called.
306 ctxInfo.directContext()->flushAndSubmit(/*sync*/true);
307 }
308}
309} // namespace skiatest
310
Nathaniel Nifong110367d2021-01-21 13:16:04 -0500311namespace {
312
313// A GLtestContext that we can return from CreatePlatformGLTestContext below.
314// It doesn't have to do anything WebGL-specific that I know of but we can't return
315// a GLTestContext because it has pure virtual methods that need to be implemented.
316class WasmWebGlTestContext : public sk_gpu_test::GLTestContext {
317public:
318 WasmWebGlTestContext() {}
319 ~WasmWebGlTestContext() override {
320 this->teardown();
321 }
322 // We assume WebGL only has one context and that it is always current.
323 // Therefore these context related functions return null intentionally.
324 // It's possible that more tests will pass if these were correctly implemented.
325 std::unique_ptr<GLTestContext> makeNew() const override {
326 // This is supposed to create a new GL context in a new GLTestContext.
327 // Specifically for tests that do not want to re-use the existing one.
328 return nullptr;
329 }
330 void onPlatformMakeNotCurrent() const override { }
331 void onPlatformMakeCurrent() const override { }
332 std::function<void()> onPlatformGetAutoContextRestore() const override {
333 return nullptr;
334 }
335 GrGLFuncPtr onPlatformGetProcAddress(const char* procName) const override {
336 return nullptr;
337 }
338};
339} // namespace
340
Kevin Lubickdffd20e2020-11-02 10:32:05 -0500341namespace sk_gpu_test {
342GLTestContext *CreatePlatformGLTestContext(GrGLStandard forcedGpuAPI,
343 GLTestContext *shareContext) {
Nathaniel Nifong110367d2021-01-21 13:16:04 -0500344 return new WasmWebGlTestContext();
Kevin Lubickdffd20e2020-11-02 10:32:05 -0500345}
346} // namespace sk_gpu_test
347
Kevin Lubickacdc2832020-10-23 11:28:57 -0400348void Init() {
349 // Use the portable fonts.
350 gSkFontMgr_DefaultFactory = &ToolUtils::MakePortableFontMgr;
351}
352
Kevin Lubick00398742020-10-08 10:05:07 -0400353EMSCRIPTEN_BINDINGS(GMs) {
Kevin Lubickacdc2832020-10-23 11:28:57 -0400354 function("Init", &Init);
Kevin Lubick00398742020-10-08 10:05:07 -0400355 function("ListGMs", &ListGMs);
Kevin Lubickdffd20e2020-11-02 10:32:05 -0500356 function("ListTests", &ListTests);
Kevin Lubicka25c0612020-10-19 15:18:45 -0400357 function("LoadKnownDigest", &LoadKnownDigest);
Kevin Lubickbffc1162020-10-21 15:36:35 -0400358 function("_LoadResource", &LoadResource);
Kevin Lubick00398742020-10-08 10:05:07 -0400359 function("MakeGrContext", &MakeGrContext);
360 function("RunGM", &RunGM);
Kevin Lubickdffd20e2020-11-02 10:32:05 -0500361 function("RunTest", &RunTest);
Kevin Lubick00398742020-10-08 10:05:07 -0400362
363 class_<GrDirectContext>("GrDirectContext")
364 .smart_ptr<sk_sp<GrDirectContext>>("sk_sp<GrDirectContext>");
365}