blob: 2ccce3c73b1eaee58205abde0320d718f340f8e4 [file] [log] [blame]
Xiaoming Shi73dfa202020-03-12 11:31:35 -07001/*
2 * Copyright 2019 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 "include/core/SkPicture.h"
9#include "include/core/SkSurface.h"
10#include "include/utils/SkBase64.h"
11#include "src/utils/SkJSONWriter.h"
12#include "src/utils/SkMultiPictureDocument.h"
13#include "tools/SkSharingProc.h"
14#include "tools/UrlDataManager.h"
15#include "tools/debugger/DebugCanvas.h"
16
17#include <emscripten.h>
18#include <emscripten/bind.h>
19
20#if SK_SUPPORT_GPU
21#include "include/gpu/GrBackendSurface.h"
22#include "include/gpu/GrContext.h"
23#include "include/gpu/gl/GrGLInterface.h"
24#include "include/gpu/gl/GrGLTypes.h"
25
26#include <GL/gl.h>
27#include <emscripten/html5.h>
28#endif
29
30using JSColor = int32_t;
31using Uint8Array = emscripten::val;
32
33// file signature for SkMultiPictureDocument
34// TODO(nifong): make public and include from SkMultiPictureDocument.h
35static constexpr char kMultiMagic[] = "Skia Multi-Picture Doc\n\n";
36
37struct SimpleImageInfo {
38 int width;
39 int height;
40 SkColorType colorType;
41 SkAlphaType alphaType;
42};
43
44SkImageInfo toSkImageInfo(const SimpleImageInfo& sii) {
45 return SkImageInfo::Make(sii.width, sii.height, sii.colorType, sii.alphaType);
46}
47
48SimpleImageInfo toSimpleImageInfo(const SkImageInfo& ii) {
49 return (SimpleImageInfo){ii.width(), ii.height(), ii.colorType(), ii.alphaType()};
50}
51
52class SkpDebugPlayer {
53 public:
54 SkpDebugPlayer() :
55 udm(UrlDataManager(SkString("/data"))){}
56
57 /* loadSkp deserializes a skp file that has been copied into the shared WASM memory.
58 * cptr - a pointer to the data to deserialize.
59 * length - length of the data in bytes.
60 * The caller must allocate the memory with M._malloc where M is the wasm module in javascript
61 * and copy the data into M.buffer at the pointer returned by malloc.
62 *
63 * uintptr_t is used here because emscripten will not allow binding of functions with pointers
64 * to primitive types. We can instead pass a number and cast it to whatever kind of
65 * pointer we're expecting.
66 */
67 void loadSkp(uintptr_t cptr, int length) {
68 const uint8_t* data = reinterpret_cast<const uint8_t*>(cptr);
69 char magic[8];
70 // Both traditional and multi-frame skp files have a magic word
71 SkMemoryStream stream(data, length);
72 SkDebugf("make stream at %p, with %d bytes\n",data, length);
73 // Why -1? I think it's got to do with using a constexpr, just a guess.
74 const size_t magicsize = sizeof(kMultiMagic) - 1;
75 if (memcmp(data, kMultiMagic, magicsize) == 0) {
76 SkDebugf("Try reading as a multi-frame skp\n");
77 loadMultiFrame(&stream);
78 } else {
79 SkDebugf("Try reading as single-frame skp\n");
80 frames.push_back(loadSingleFrame(&stream));
81 }
82 }
83
84 /* drawTo asks the debug canvas to draw from the beginning of the picture
85 * to the given command and flush the canvas.
86 */
87 void drawTo(SkSurface* surface, int32_t index) {
88 int cmdlen = frames[fp]->getSize();
89 if (cmdlen == 0) {
90 SkDebugf("Zero commands to execute");
91 return;
92 }
93 if (index >= cmdlen) {
94 SkDebugf("Constrained command index (%d) within this frame's length (%d)\n", index, cmdlen);
95 index = cmdlen-1;
96 }
97 frames[fp]->drawTo(surface->getCanvas(), index);
98 surface->getCanvas()->flush();
99 }
100
101 const SkIRect& getBounds() { return fBounds; }
102
103 void setOverdrawVis(bool on) {
104 frames[fp]->setOverdrawViz(on);
105 }
106 void setGpuOpBounds(bool on) {
107 frames[fp]->setDrawGpuOpBounds(on);
108 }
109 void setClipVizColor(JSColor color) {
110 frames[fp]->setClipVizColor(SkColor(color));
111 }
112 void deleteCommand(int index) {
113 frames[fp]->deleteDrawCommandAt(index);
114 }
115 void setCommandVisibility(int index, bool visible) {
116 frames[fp]->toggleCommand(index, visible);
117 }
118 int getSize() const {
119 return frames[fp]->getSize();
120 }
121 int getFrameCount() const {
122 return frames.size();
123 }
124
125 // Return the command list in JSON representation as a string
126 std::string jsonCommandList(sk_sp<SkSurface> surface) {
127 SkDynamicMemoryWStream stream;
128 SkJSONWriter writer(&stream, SkJSONWriter::Mode::kFast);
129 writer.beginObject(); // root
130 frames[fp]->toJSON(writer, udm, getSize(), surface->getCanvas());
131 writer.endObject(); // root
132 writer.flush();
133 auto skdata = stream.detachAsData();
134 // Convert skdata to string_view, which accepts a length
135 std::string_view data_view(reinterpret_cast<const char*>(skdata->data()), skdata->size());
136 // and string_view to string, which emscripten understands.
137 return std::string(data_view);
138 }
139
140 // Gets the clip and matrix of the last command drawn
141 std::string lastCommandInfo() {
142 SkMatrix vm = frames[fp]->getCurrentMatrix();
143 SkIRect clip = frames[fp]->getCurrentClip();
144
145 SkDynamicMemoryWStream stream;
146 SkJSONWriter writer(&stream, SkJSONWriter::Mode::kFast);
147 writer.beginObject(); // root
148
149 writer.appendName("ViewMatrix");
150 DrawCommand::MakeJsonMatrix(writer, vm);
151 writer.appendName("ClipRect");
152 DrawCommand::MakeJsonIRect(writer, clip);
153
154 writer.endObject(); // root
155 writer.flush();
156 auto skdata = stream.detachAsData();
157 // Convert skdata to string_view, which accepts a length
158 std::string_view data_view(reinterpret_cast<const char*>(skdata->data()), skdata->size());
159 // and string_view to string, which emscripten understands.
160 return std::string(data_view);
161 }
162
163 void changeFrame(int index) {
164 fp = index;
165 }
166
167 // Return the png file at the requested index in
168 // the skp file's vector of shared images. this is the set of images referred to by the
169 // filenames like "\\1" in DrawImage commands.
170 // Return type is the PNG data as a base64 encoded string with prepended URI.
171 std::string getImageResource(int index) {
172 sk_sp<SkData> pngData = fImages[index]->encodeToData();
173 size_t len = SkBase64::Encode(pngData->data(), pngData->size(), nullptr);
174 SkString dst;
175 dst.resize(len);
176 SkBase64::Encode(pngData->data(), pngData->size(), dst.writable_str());
177 dst.prepend("data:image/png;base64,");
178 return std::string(dst.c_str());
179 }
180
181 int getImageCount() {
182 return fImages.size();
183 }
184
185 // Get the image info of one of the resource images.
186 SimpleImageInfo getImageInfo(int index) {
187 return toSimpleImageInfo(fImages[index]->imageInfo());
188 }
189
190 private:
191
192 // Loads a single frame (traditional) skp file from the provided data stream and returns
193 // a newly allocated DebugCanvas initialized with the SkPicture that was in the file.
194 std::unique_ptr<DebugCanvas> loadSingleFrame(SkMemoryStream* stream) {
195 // note overloaded = operator that actually does a move
196 sk_sp<SkPicture> picture = SkPicture::MakeFromStream(stream);
197 if (!picture) {
198 SkDebugf("Unable to deserialze frame.\n");
199 return nullptr;
200 }
201 SkDebugf("Parsed SKP file.\n");
202 // Make debug canvas using bounds from SkPicture
203 fBounds = picture->cullRect().roundOut();
204 std::unique_ptr<DebugCanvas> debugDanvas = std::make_unique<DebugCanvas>(fBounds);
205 SkDebugf("DebugCanvas created.\n");
206
207 // Only draw picture to the debug canvas once.
208 debugDanvas->drawPicture(picture);
209 SkDebugf("Added picture with %d commands.\n", debugDanvas->getSize());
210 return debugDanvas;
211 }
212
213 void loadMultiFrame(SkMemoryStream* stream) {
214
215 // Attempt to deserialize with an image sharing serial proc.
216 auto deserialContext = std::make_unique<SkSharingDeserialContext>();
217 SkDeserialProcs procs;
218 procs.fImageProc = SkSharingDeserialContext::deserializeImage;
219 procs.fImageCtx = deserialContext.get();
220
221 int page_count = SkMultiPictureDocumentReadPageCount(stream);
222 if (!page_count) {
223 SkDebugf("Not a MultiPictureDocument");
224 return;
225 }
226 SkDebugf("Expecting %d frames\n", page_count);
227
228 std::vector<SkDocumentPage> pages(page_count);
229 if (!SkMultiPictureDocumentRead(stream, pages.data(), page_count, &procs)) {
230 SkDebugf("Reading frames from MultiPictureDocument failed");
231 return;
232 }
233
234 for (const auto& page : pages) {
235 // Make debug canvas using bounds from SkPicture
236 fBounds = page.fPicture->cullRect().roundOut();
237 std::unique_ptr<DebugCanvas> debugDanvas = std::make_unique<DebugCanvas>(fBounds);
238 // Only draw picture to the debug canvas once.
239 debugDanvas->drawPicture(page.fPicture);
240 SkDebugf("Added picture with %d commands.\n", debugDanvas->getSize());
241
242 if (debugDanvas->getSize() <=0 ){
243 SkDebugf("Skipped corrupted frame, had %d commands \n", debugDanvas->getSize());
244 continue;
245 }
246 debugDanvas->setOverdrawViz(false);
247 debugDanvas->setDrawGpuOpBounds(false);
248 debugDanvas->setClipVizColor(SK_ColorTRANSPARENT);
249 frames.push_back(std::move(debugDanvas));
250 }
251 fImages = deserialContext->fImages;
252 }
253
254 // A vector of DebugCanvas, each one initialized to a frame of the animation.
255 std::vector<std::unique_ptr<DebugCanvas>> frames;
256 // The index of the current frame (into the vector above)
257 int fp = 0;
258 // The width and height of the animation. (in practice the bounds of the last loaded frame)
259 SkIRect fBounds;
260 // SKP version of loaded file.
261 uint32_t fFileVersion;
262 // image resources from a loaded file
263 std::vector<sk_sp<SkImage>> fImages;
264
265 // The URLDataManager here is a cache that accepts encoded data (pngs) and puts
266 // numbers on them. We have our own collection of images (fImages) that was populated by the
267 // SkSharingDeserialContext when mskp files are loaded. It would be nice to have the mapping
268 // indices between these two caches so the urls displayed in command info match the list
269 // in the resource tab, and to make cross linking possible. One way to do this would be to
270 // look up all of fImages in udm but the exact encoding of the PNG differs and we wouldn't
271 // find anything. TODO(nifong): Unify these two numbering schemes in CollatingCanvas.
272 UrlDataManager udm;
273
274};
275
276#if SK_SUPPORT_GPU
277sk_sp<GrContext> MakeGrContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context)
278{
279 EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context);
280 if (r < 0) {
281 SkDebugf("failed to make webgl context current %d\n", r);
282 return nullptr;
283 }
284 // setup GrContext
285 auto interface = GrGLMakeNativeInterface();
286 // setup contexts
287 sk_sp<GrContext> grContext(GrContext::MakeGL(interface));
288 return grContext;
289}
290
291sk_sp<SkSurface> MakeOnScreenGLSurface(sk_sp<GrContext> grContext, int width, int height) {
292 glClearColor(0, 0, 0, 0);
293 glClearStencil(0);
294 glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
295
296
297 // Wrap the frame buffer object attached to the screen in a Skia render
298 // target so Skia can render to it
299 GrGLint buffer;
300 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &buffer);
301 GrGLFramebufferInfo info;
302 info.fFBOID = (GrGLuint) buffer;
303 SkColorType colorType;
304
305 info.fFormat = GL_RGBA8;
306 colorType = kRGBA_8888_SkColorType;
307
308 GrBackendRenderTarget target(width, height, 0, 8, info);
309
310 sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(grContext.get(), target,
311 kBottomLeft_GrSurfaceOrigin,
312 colorType, nullptr, nullptr));
313 return surface;
314}
315
316sk_sp<SkSurface> MakeRenderTarget(sk_sp<GrContext> grContext, int width, int height) {
317 SkImageInfo info = SkImageInfo::MakeN32(width, height, SkAlphaType::kPremul_SkAlphaType);
318
319 sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(grContext.get(),
320 SkBudgeted::kYes,
321 info, 0,
322 kBottomLeft_GrSurfaceOrigin,
323 nullptr, true));
324 return surface;
325}
326
327sk_sp<SkSurface> MakeRenderTarget(sk_sp<GrContext> grContext, SimpleImageInfo sii) {
328 sk_sp<SkSurface> surface(SkSurface::MakeRenderTarget(grContext.get(),
329 SkBudgeted::kYes,
330 toSkImageInfo(sii), 0,
331 kBottomLeft_GrSurfaceOrigin,
332 nullptr, true));
333 return surface;
334}
335#endif
336
337using namespace emscripten;
338EMSCRIPTEN_BINDINGS(my_module) {
339
340 // The main class that the JavaScript in index.html uses
341 class_<SkpDebugPlayer>("SkpDebugPlayer")
342 .constructor<>()
343 .function("loadSkp", &SkpDebugPlayer::loadSkp, allow_raw_pointers())
344 .function("drawTo", &SkpDebugPlayer::drawTo, allow_raw_pointers())
345 .function("getBounds", &SkpDebugPlayer::getBounds)
346 .function("setOverdrawVis", &SkpDebugPlayer::setOverdrawVis)
347 .function("setClipVizColor", &SkpDebugPlayer::setClipVizColor)
348 .function("getSize", &SkpDebugPlayer::getSize)
349 .function("deleteCommand", &SkpDebugPlayer::deleteCommand)
350 .function("setCommandVisibility", &SkpDebugPlayer::setCommandVisibility)
351 .function("setGpuOpBounds", &SkpDebugPlayer::setGpuOpBounds)
352 .function("jsonCommandList", &SkpDebugPlayer::jsonCommandList, allow_raw_pointers())
353 .function("lastCommandInfo", &SkpDebugPlayer::lastCommandInfo)
354 .function("changeFrame", &SkpDebugPlayer::changeFrame)
355 .function("getFrameCount", &SkpDebugPlayer::getFrameCount)
356 .function("getImageResource", &SkpDebugPlayer::getImageResource)
357 .function("getImageCount", &SkpDebugPlayer::getImageCount)
358 .function("getImageInfo", &SkpDebugPlayer::getImageInfo);
359
360 // Structs used as arguments or returns to the functions above
361 value_object<SkIRect>("SkIRect")
362 .field("fLeft", &SkIRect::fLeft)
363 .field("fTop", &SkIRect::fTop)
364 .field("fRight", &SkIRect::fRight)
365 .field("fBottom", &SkIRect::fBottom);
366
367 // Symbols needed by cpu.js to perform surface creation and flushing.
368 enum_<SkColorType>("ColorType")
369 .value("RGBA_8888", SkColorType::kRGBA_8888_SkColorType);
370 enum_<SkAlphaType>("AlphaType")
371 .value("Opaque", SkAlphaType::kOpaque_SkAlphaType)
372 .value("Premul", SkAlphaType::kPremul_SkAlphaType)
373 .value("Unpremul", SkAlphaType::kUnpremul_SkAlphaType);
374 value_object<SimpleImageInfo>("SkImageInfo")
375 .field("width", &SimpleImageInfo::width)
376 .field("height", &SimpleImageInfo::height)
377 .field("colorType", &SimpleImageInfo::colorType)
378 .field("alphaType", &SimpleImageInfo::alphaType);
379 constant("TRANSPARENT", (JSColor) SK_ColorTRANSPARENT);
380 function("_getRasterDirectSurface", optional_override([](const SimpleImageInfo ii,
381 uintptr_t /* uint8_t* */ pPtr,
382 size_t rowBytes)->sk_sp<SkSurface> {
383 uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
384 SkImageInfo imageInfo = toSkImageInfo(ii);
385 SkDebugf("Made raster direct surface.\n");
386 return SkSurface::MakeRasterDirect(imageInfo, pixels, rowBytes, nullptr);
387 }), allow_raw_pointers());
388 class_<SkSurface>("SkSurface")
389 .smart_ptr<sk_sp<SkSurface>>("sk_sp<SkSurface>")
390 .function("width", &SkSurface::width)
391 .function("height", &SkSurface::height)
392 .function("_flush", select_overload<void()>(&SkSurface::flush))
393 .function("getCanvas", &SkSurface::getCanvas, allow_raw_pointers());
394 class_<SkCanvas>("SkCanvas")
395 .function("clear", optional_override([](SkCanvas& self, JSColor color)->void {
396 // JS side gives us a signed int instead of an unsigned int for color
397 // Add a optional_override to change it out.
398 self.clear(SkColor(color));
399 }));
400
401 #if SK_SUPPORT_GPU
402 class_<GrContext>("GrContext")
403 .smart_ptr<sk_sp<GrContext>>("sk_sp<GrContext>");
404 function("currentContext", &emscripten_webgl_get_current_context);
405 function("setCurrentContext", &emscripten_webgl_make_context_current);
406 function("MakeGrContext", &MakeGrContext);
407 function("MakeOnScreenGLSurface", &MakeOnScreenGLSurface);
408 function("MakeRenderTarget", select_overload<sk_sp<SkSurface>(
409 sk_sp<GrContext>, int, int)>(&MakeRenderTarget));
410 function("MakeRenderTarget", select_overload<sk_sp<SkSurface>(
411 sk_sp<GrContext>, SimpleImageInfo)>(&MakeRenderTarget));
412 constant("gpu", true);
413 #endif
414}