blob: ccd7f2f0535d7b0041d91d5d1f7b39ccdbba9ea3 [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
8#include <string>
9#include <emscripten.h>
10#include <emscripten/bind.h>
11#include <emscripten/html5.h>
12
13#include "gm/gm.h"
14#include "include/core/SkBitmap.h"
15#include "include/core/SkCanvas.h"
16#include "include/core/SkImageInfo.h"
17#include "include/core/SkStream.h"
18#include "include/core/SkSurface.h"
19#include "include/gpu/GrDirectContext.h"
20#include "include/gpu/gl/GrGLInterface.h"
21#include "include/gpu/gl/GrGLTypes.h"
22#include "src/core/SkMD5.h"
23#include "tools/HashAndEncode.h"
24#include "tools/flags/CommandLineFlags.h"
25
26#include "modules/canvaskit/WasmCommon.h"
27
28using namespace emscripten;
29
30/**
31 * Returns a JS array of strings containing the names of the registered GMs. GMs are only registered
32 * when their source is included in the "link" step, not if they are in something like libgm.a.
33 * The names are also logged to the console.
34 */
35static JSArray ListGMs() {
36 SkDebugf("Listing GMs\n");
37 JSArray gms = emscripten::val::array();
38 for (skiagm::GMFactory fact : skiagm::GMRegistry::Range()) {
39 std::unique_ptr<skiagm::GM> gm(fact());
40 SkDebugf("gm %s\n", gm->getName());
41 gms.call<void>("push", std::string(gm->getName()));
42 }
43 return gms;
44}
45
46static std::unique_ptr<skiagm::GM> getGMWithName(std::string name) {
47 for (skiagm::GMFactory fact : skiagm::GMRegistry::Range()) {
48 std::unique_ptr<skiagm::GM> gm(fact());
49 if (gm->getName() == name) {
50 return gm;
51 }
52 }
53 return nullptr;
54}
55
56/**
57 * Sets the given WebGL context to be "current" and then creates a GrDirectContext from that
58 * context.
59 */
60static sk_sp<GrDirectContext> MakeGrContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
61{
62 EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context);
63 if (r < 0) {
64 printf("failed to make webgl context current %d\n", r);
65 return nullptr;
66 }
67 // setup GrDirectContext
68 auto interface = GrGLMakeNativeInterface();
69 // setup contexts
70 sk_sp<GrDirectContext> dContext(GrDirectContext::MakeGL(interface));
71 return dContext;
72}
73
74/**
75 * Runs the given GM and returns a JS object. If the GM was successful, the object will have the
76 * following properties:
77 * "png" - a Uint8Array of the PNG data extracted from the surface.
78 * "hash" - a string which is the md5 hash of the pixel contents and the metadata.
79 */
80static JSObject RunGM(sk_sp<GrDirectContext> ctx, std::string name) {
81 JSObject result = emscripten::val::object();
82 auto gm = getGMWithName(name);
83 if (!gm) {
84 SkDebugf("Could not find gm with name %s\n", name.c_str());
85 return result;
86 }
87 // TODO(kjlubick) make these configurable somehow. This probably makes sense to do as function
88 // parameters.
89 auto alphaType = SkAlphaType::kPremul_SkAlphaType;
90 auto colorType = SkColorType::kN32_SkColorType;
91 SkISize size = gm->getISize();
92 SkImageInfo info = SkImageInfo::Make(size, colorType, alphaType);
93 sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(ctx.get(),
94 SkBudgeted::kYes,
95 info, 0,
96 kBottomLeft_GrSurfaceOrigin,
97 nullptr, true));
98 if (!surface) {
99 SkDebugf("Could not make surface\n");
100 return result;
101 }
102 auto canvas = surface->getCanvas();
103
104 gm->onceBeforeDraw();
105 SkString msg;
106 // Based on GMSrc::draw from DM.
107 auto gpuSetupResult = gm->gpuSetup(ctx.get(), canvas, &msg);
108 if (gpuSetupResult == skiagm::DrawResult::kFail) {
109 SkDebugf("Error with gpu setup for gm %s: %s\n", name.c_str(), msg.c_str());
110 return result;
111 } else if (gpuSetupResult == skiagm::DrawResult::kSkip) {
112 return result;
113 }
114
115 auto drawResult = gm->draw(canvas, &msg);
116 if (drawResult == skiagm::DrawResult::kFail) {
117 SkDebugf("Error with gm %s: %s\n", name.c_str(), msg.c_str());
118 return result;
119 } else if (drawResult == skiagm::DrawResult::kSkip) {
120 return result;
121 }
122 surface->flushAndSubmit(true);
123
124 // Based on GPUSink::readBack
125 SkBitmap bitmap;
126 bitmap.allocPixels(info);
127 if (!canvas->readPixels(bitmap, 0, 0)) {
128 SkDebugf("Could not read pixels back\n");
129 return result;
130 }
131
132 // Now we need to encode to PNG and get the md5 hash of the pixels (and colorspace and stuff).
133 // This is based on Task::Run from DM.cpp
134 std::unique_ptr<HashAndEncode> hashAndEncode = std::make_unique<HashAndEncode>(bitmap);
135 SkString md5;
136 SkMD5 hash;
137 hashAndEncode->feedHash(&hash);
138 SkMD5::Digest digest = hash.finish();
139 for (int i = 0; i < 16; i++) {
140 md5.appendf("%02x", digest.data[i]);
141 }
142
143 // We do not need to include the keys because they are optional - they are not read by Gold.
144 CommandLineFlags::StringArray empty;
145 SkDynamicMemoryWStream stream;
146 // TODO(kjlubick) make emission of PNGs optional and make it so we can check the hash against
147 // the list of known digests to not emit it. This will hopefully speed tests up.
148 hashAndEncode->encodePNG(&stream, md5.c_str(), empty, empty);
149
150 auto data = stream.detachAsData();
151
152 // This is the cleanest way to create a new Uint8Array with a copy of the data that is not
153 // in the WASM heap. kjlubick tried returning a pointer inside an SkData, but that lead to some
154 // use after free issues. By making the copy using the JS transliteration, we don't risk the
155 // SkData object being cleaned up before we make the copy.
156 Uint8Array pngData = emscripten::val(
157 // https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#memory-views
158 typed_memory_view(data->size(), data->bytes())
159 ).call<Uint8Array>("slice"); // slice with no args makes a copy of the memory view.
160
161 result.set("png", pngData);
162 result.set("hash", md5.c_str());
163 return result;
164}
165
166EMSCRIPTEN_BINDINGS(GMs) {
167 function("ListGMs", &ListGMs);
168 function("MakeGrContext", &MakeGrContext);
169 function("RunGM", &RunGM);
170
171 class_<GrDirectContext>("GrDirectContext")
172 .smart_ptr<sk_sp<GrDirectContext>>("sk_sp<GrDirectContext>");
173}