blob: 233303e01c39c6929bc111be4b20ea7adc471e5e [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"
21#include "include/gpu/GrDirectContext.h"
22#include "include/gpu/gl/GrGLInterface.h"
23#include "include/gpu/gl/GrGLTypes.h"
Kevin Lubickacdc2832020-10-23 11:28:57 -040024#include "modules/canvaskit/WasmCommon.h"
25#include "src/core/SkFontMgrPriv.h"
Kevin Lubick00398742020-10-08 10:05:07 -040026#include "src/core/SkMD5.h"
27#include "tools/HashAndEncode.h"
Kevin Lubickbffc1162020-10-21 15:36:35 -040028#include "tools/ResourceFactory.h"
Kevin Lubick00398742020-10-08 10:05:07 -040029#include "tools/flags/CommandLineFlags.h"
Kevin Lubickacdc2832020-10-23 11:28:57 -040030#include "tools/fonts/TestFontMgr.h"
Kevin Lubick00398742020-10-08 10:05:07 -040031
32using namespace emscripten;
33
34/**
35 * Returns a JS array of strings containing the names of the registered GMs. GMs are only registered
36 * when their source is included in the "link" step, not if they are in something like libgm.a.
37 * The names are also logged to the console.
38 */
39static JSArray ListGMs() {
40 SkDebugf("Listing GMs\n");
41 JSArray gms = emscripten::val::array();
42 for (skiagm::GMFactory fact : skiagm::GMRegistry::Range()) {
43 std::unique_ptr<skiagm::GM> gm(fact());
44 SkDebugf("gm %s\n", gm->getName());
45 gms.call<void>("push", std::string(gm->getName()));
46 }
47 return gms;
48}
49
50static std::unique_ptr<skiagm::GM> getGMWithName(std::string name) {
51 for (skiagm::GMFactory fact : skiagm::GMRegistry::Range()) {
52 std::unique_ptr<skiagm::GM> gm(fact());
53 if (gm->getName() == name) {
54 return gm;
55 }
56 }
57 return nullptr;
58}
59
60/**
61 * Sets the given WebGL context to be "current" and then creates a GrDirectContext from that
62 * context.
63 */
64static sk_sp<GrDirectContext> MakeGrContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
65{
66 EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context);
67 if (r < 0) {
68 printf("failed to make webgl context current %d\n", r);
69 return nullptr;
70 }
71 // setup GrDirectContext
72 auto interface = GrGLMakeNativeInterface();
73 // setup contexts
74 sk_sp<GrDirectContext> dContext(GrDirectContext::MakeGL(interface));
75 return dContext;
76}
77
Kevin Lubicka25c0612020-10-19 15:18:45 -040078static std::set<std::string> gKnownDigests;
79
80static void LoadKnownDigest(std::string md5) {
81 gKnownDigests.insert(md5);
82}
83
Kevin Lubickbffc1162020-10-21 15:36:35 -040084static std::map<std::string, sk_sp<SkData>> gResources;
85
86static sk_sp<SkData> getResource(const char* name) {
87 auto it = gResources.find(name);
88 if (it == gResources.end()) {
89 SkDebugf("Resource %s not found\n", name);
90 return nullptr;
91 }
92 return it->second;
93}
94
95static void LoadResource(std::string name, uintptr_t /* byte* */ bPtr, size_t len) {
96 const uint8_t* bytes = reinterpret_cast<const uint8_t*>(bPtr);
97 auto data = SkData::MakeFromMalloc(bytes, len);
98 gResources[name] = std::move(data);
99
100 if (!gResourceFactory) {
101 gResourceFactory = getResource;
102 }
103}
104
Kevin Lubick00398742020-10-08 10:05:07 -0400105/**
106 * Runs the given GM and returns a JS object. If the GM was successful, the object will have the
107 * following properties:
108 * "png" - a Uint8Array of the PNG data extracted from the surface.
109 * "hash" - a string which is the md5 hash of the pixel contents and the metadata.
110 */
111static JSObject RunGM(sk_sp<GrDirectContext> ctx, std::string name) {
112 JSObject result = emscripten::val::object();
113 auto gm = getGMWithName(name);
114 if (!gm) {
115 SkDebugf("Could not find gm with name %s\n", name.c_str());
116 return result;
117 }
118 // TODO(kjlubick) make these configurable somehow. This probably makes sense to do as function
119 // parameters.
120 auto alphaType = SkAlphaType::kPremul_SkAlphaType;
121 auto colorType = SkColorType::kN32_SkColorType;
122 SkISize size = gm->getISize();
123 SkImageInfo info = SkImageInfo::Make(size, colorType, alphaType);
124 sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(ctx.get(),
125 SkBudgeted::kYes,
126 info, 0,
127 kBottomLeft_GrSurfaceOrigin,
128 nullptr, true));
129 if (!surface) {
130 SkDebugf("Could not make surface\n");
131 return result;
132 }
133 auto canvas = surface->getCanvas();
134
135 gm->onceBeforeDraw();
136 SkString msg;
137 // Based on GMSrc::draw from DM.
138 auto gpuSetupResult = gm->gpuSetup(ctx.get(), canvas, &msg);
139 if (gpuSetupResult == skiagm::DrawResult::kFail) {
140 SkDebugf("Error with gpu setup for gm %s: %s\n", name.c_str(), msg.c_str());
141 return result;
142 } else if (gpuSetupResult == skiagm::DrawResult::kSkip) {
143 return result;
144 }
145
146 auto drawResult = gm->draw(canvas, &msg);
147 if (drawResult == skiagm::DrawResult::kFail) {
148 SkDebugf("Error with gm %s: %s\n", name.c_str(), msg.c_str());
149 return result;
150 } else if (drawResult == skiagm::DrawResult::kSkip) {
151 return result;
152 }
153 surface->flushAndSubmit(true);
154
155 // Based on GPUSink::readBack
156 SkBitmap bitmap;
157 bitmap.allocPixels(info);
158 if (!canvas->readPixels(bitmap, 0, 0)) {
159 SkDebugf("Could not read pixels back\n");
160 return result;
161 }
162
163 // Now we need to encode to PNG and get the md5 hash of the pixels (and colorspace and stuff).
164 // This is based on Task::Run from DM.cpp
165 std::unique_ptr<HashAndEncode> hashAndEncode = std::make_unique<HashAndEncode>(bitmap);
166 SkString md5;
167 SkMD5 hash;
168 hashAndEncode->feedHash(&hash);
169 SkMD5::Digest digest = hash.finish();
170 for (int i = 0; i < 16; i++) {
171 md5.appendf("%02x", digest.data[i]);
172 }
173
Kevin Lubicka25c0612020-10-19 15:18:45 -0400174 auto ok = gKnownDigests.find(md5.c_str());
175 if (ok == gKnownDigests.end()) {
176 // We only need to decode the image if it is "interesting", that is, we have not written it
177 // before to disk and uploaded it to gold.
178 SkDynamicMemoryWStream stream;
179 // We do not need to include the keys because they are optional - they are not read by Gold.
180 CommandLineFlags::StringArray empty;
181 hashAndEncode->encodePNG(&stream, md5.c_str(), empty, empty);
Kevin Lubick00398742020-10-08 10:05:07 -0400182
Kevin Lubicka25c0612020-10-19 15:18:45 -0400183 auto data = stream.detachAsData();
Kevin Lubick00398742020-10-08 10:05:07 -0400184
Kevin Lubicka25c0612020-10-19 15:18:45 -0400185 // This is the cleanest way to create a new Uint8Array with a copy of the data that is not
186 // in the WASM heap. kjlubick tried returning a pointer inside an SkData, but that lead to
187 // some use after free issues. By making the copy using the JS transliteration, we don't
188 // risk the SkData object being cleaned up before we make the copy.
189 Uint8Array pngData = emscripten::val(
190 // https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#memory-views
191 typed_memory_view(data->size(), data->bytes())
192 ).call<Uint8Array>("slice"); // slice with no args makes a copy of the memory view.
Kevin Lubick00398742020-10-08 10:05:07 -0400193
Kevin Lubicka25c0612020-10-19 15:18:45 -0400194 result.set("png", pngData);
195 gKnownDigests.emplace(md5.c_str());
196 }
Kevin Lubick00398742020-10-08 10:05:07 -0400197 result.set("hash", md5.c_str());
198 return result;
199}
200
Kevin Lubickacdc2832020-10-23 11:28:57 -0400201void Init() {
202 // Use the portable fonts.
203 gSkFontMgr_DefaultFactory = &ToolUtils::MakePortableFontMgr;
204}
205
Kevin Lubick00398742020-10-08 10:05:07 -0400206EMSCRIPTEN_BINDINGS(GMs) {
Kevin Lubickacdc2832020-10-23 11:28:57 -0400207 function("Init", &Init);
Kevin Lubick00398742020-10-08 10:05:07 -0400208 function("ListGMs", &ListGMs);
Kevin Lubicka25c0612020-10-19 15:18:45 -0400209 function("LoadKnownDigest", &LoadKnownDigest);
Kevin Lubickbffc1162020-10-21 15:36:35 -0400210 function("_LoadResource", &LoadResource);
Kevin Lubick00398742020-10-08 10:05:07 -0400211 function("MakeGrContext", &MakeGrContext);
212 function("RunGM", &RunGM);
213
214 class_<GrDirectContext>("GrDirectContext")
215 .smart_ptr<sk_sp<GrDirectContext>>("sk_sp<GrDirectContext>");
216}