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