1 /*
2 * This file defines SkpDebugPlayer, a class which loads a SKP or MSKP file and draws it
3 * to an SkSurface with annotation, and detailed playback controls. It holds as many DebugCanvases
4 * as there are frames in the file.
5 *
6 * It also defines emscripten bindings for SkpDebugPlayer and other classes necessary to us it.
7 *
8 * Copyright 2019 Google LLC
9 *
10 * Use of this source code is governed by a BSD-style license that can be
11 * found in the LICENSE file.
12 */
13
14 #include "include/core/SkPicture.h"
15 #include "include/core/SkString.h"
16 #include "include/core/SkSurface.h"
17 #include "include/utils/SkBase64.h"
18 #include "src/core/SkPicturePriv.h"
19 #include "src/utils/SkJSONWriter.h"
20 #include "src/utils/SkMultiPictureDocument.h"
21 #include "tools/SkSharingProc.h"
22 #include "tools/UrlDataManager.h"
23 #include "tools/debugger/DebugCanvas.h"
24 #include "tools/debugger/DebugLayerManager.h"
25 #include "tools/debugger/DrawCommand.h"
26
27 #include <memory>
28 #include <string>
29 #include <string_view>
30 #include <vector>
31 #include <map>
32 #include <emscripten.h>
33 #include <emscripten/bind.h>
34
35 #ifdef CK_ENABLE_WEBGL
36 #include "include/gpu/GrBackendSurface.h"
37 #include "include/gpu/GrDirectContext.h"
38 #include "include/gpu/gl/GrGLInterface.h"
39 #include "include/gpu/gl/GrGLTypes.h"
40
41 #include <GL/gl.h>
42 #include <emscripten/html5.h>
43 #endif
44
45 #include "modules/canvaskit/WasmCommon.h"
46
47 // file signature for SkMultiPictureDocument
48 // TODO(nifong): make public and include from SkMultiPictureDocument.h
49 static constexpr char kMultiMagic[] = "Skia Multi-Picture Doc\n\n";
50
MinVersion()51 uint32_t MinVersion() { return SkPicturePriv::kMin_Version; }
52
53 struct ImageInfoNoColorspace {
54 int width;
55 int height;
56 SkColorType colorType;
57 SkAlphaType alphaType;
58 };
59
60 // TODO(kjlubick) Should this handle colorspace
toImageInfoNoColorspace(const SkImageInfo & ii)61 ImageInfoNoColorspace toImageInfoNoColorspace(const SkImageInfo& ii) {
62 return (ImageInfoNoColorspace){ii.width(), ii.height(), ii.colorType(), ii.alphaType()};
63 }
64
65
66 class SkpDebugPlayer {
67 public:
SkpDebugPlayer()68 SkpDebugPlayer() :
69 udm(UrlDataManager(SkString("/data"))){}
70
71 /* loadSkp deserializes a skp file that has been copied into the shared WASM memory.
72 * cptr - a pointer to the data to deserialize.
73 * length - length of the data in bytes.
74 * The caller must allocate the memory with M._malloc where M is the wasm module in javascript
75 * and copy the data into M.buffer at the pointer returned by malloc.
76 *
77 * uintptr_t is used here because emscripten will not allow binding of functions with pointers
78 * to primitive types. We can instead pass a number and cast it to whatever kind of
79 * pointer we're expecting.
80 *
81 * Returns an error string which is populated in the case that the file cannot be read.
82 */
loadSkp(uintptr_t cptr,int length)83 std::string loadSkp(uintptr_t cptr, int length) {
84 const uint8_t* data = reinterpret_cast<const uint8_t*>(cptr);
85 // Both traditional and multi-frame skp files have a magic word
86 SkMemoryStream stream(data, length);
87 SkDebugf("make stream at %p, with %d bytes\n",data, length);
88 const bool isMulti = memcmp(data, kMultiMagic, sizeof(kMultiMagic) - 1) == 0;
89
90
91 if (isMulti) {
92 SkDebugf("Try reading as a multi-frame skp\n");
93 const auto& error = loadMultiFrame(&stream);
94 if (!error.empty()) { return error; }
95 } else {
96 SkDebugf("Try reading as single-frame skp\n");
97 // TODO(nifong): Rely on SkPicture's return errors once it provides some.
98 frames.push_back(loadSingleFrame(&stream));
99 }
100 return "";
101 }
102
103 /* drawTo asks the debug canvas to draw from the beginning of the picture
104 * to the given command and flush the canvas.
105 */
drawTo(SkSurface * surface,int32_t index)106 void drawTo(SkSurface* surface, int32_t index) {
107 // Set the command within the frame or layer event being drawn.
108 if (fInspectedLayer >= 0) {
109 fLayerManager->setCommand(fInspectedLayer, fp, index);
110 } else {
111 index = constrainFrameCommand(index);
112 }
113
114 auto* canvas = surface->getCanvas();
115 canvas->clear(SK_ColorTRANSPARENT);
116 if (fInspectedLayer >= 0) {
117 // when it's a layer event we're viewing, we use the layer manager to render it.
118 fLayerManager->drawLayerEventTo(surface, fInspectedLayer, fp);
119 } else {
120 // otherwise, its a frame at the top level.
121 frames[fp]->drawTo(surface->getCanvas(), index);
122 }
123 surface->flush();
124 }
125
126 // Draws to the end of the current frame.
draw(SkSurface * surface)127 void draw(SkSurface* surface) {
128 auto* canvas = surface->getCanvas();
129 canvas->clear(SK_ColorTRANSPARENT);
130 frames[fp]->draw(surface->getCanvas());
131 surface->getCanvas()->flush();
132 }
133
134 // Gets the bounds for the given frame
135 // (or layer update, assuming there is one at that frame for fInspectedLayer)
getBoundsForFrame(int32_t frame)136 const SkIRect getBoundsForFrame(int32_t frame) {
137 if (fInspectedLayer < 0) {
138 return fBoundsArray[frame];
139 }
140 auto summary = fLayerManager->event(fInspectedLayer, fp);
141 return SkIRect::MakeWH(summary.layerWidth, summary.layerHeight);
142 }
143
144 // Gets the bounds for the current frame
getBounds()145 const SkIRect getBounds() {
146 return getBoundsForFrame(fp);
147 }
148
149 // returns the debugcanvas of the current frame, or the current draw event when inspecting
150 // a layer.
visibleCanvas()151 DebugCanvas* visibleCanvas() {
152 if (fInspectedLayer >=0) {
153 return fLayerManager->getEventDebugCanvas(fInspectedLayer, fp);
154 } else {
155 return frames[fp].get();
156 }
157 }
158
159 // The following three operations apply to every debugcanvas because they are overdraw features.
160 // There is only one toggle for them on the app, they are global settings.
161 // However, there's not a simple way to make the debugcanvases pull settings from a central
162 // location so we set it on all of them at once.
setOverdrawVis(bool on)163 void setOverdrawVis(bool on) {
164 for (int i=0; i < frames.size(); i++) {
165 frames[i]->setOverdrawViz(on);
166 }
167 fLayerManager->setOverdrawViz(on);
168 }
setGpuOpBounds(bool on)169 void setGpuOpBounds(bool on) {
170 for (int i=0; i < frames.size(); i++) {
171 frames[i]->setDrawGpuOpBounds(on);
172 }
173 fLayerManager->setDrawGpuOpBounds(on);
174 }
setClipVizColor(JSColor color)175 void setClipVizColor(JSColor color) {
176 for (int i=0; i < frames.size(); i++) {
177 frames[i]->setClipVizColor(SkColor(color));
178 }
179 fLayerManager->setClipVizColor(SkColor(color));
180 }
setAndroidClipViz(bool on)181 void setAndroidClipViz(bool on) {
182 for (int i=0; i < frames.size(); i++) {
183 frames[i]->setAndroidClipViz(on);
184 }
185 // doesn't matter in layers
186 }
setOriginVisible(bool on)187 void setOriginVisible(bool on) {
188 for (int i=0; i < frames.size(); i++) {
189 frames[i]->setOriginVisible(on);
190 }
191 }
192 // The two operations below only apply to the current frame, because they concern the command
193 // list, which is unique to each frame.
deleteCommand(int index)194 void deleteCommand(int index) {
195 visibleCanvas()->deleteDrawCommandAt(index);
196 }
setCommandVisibility(int index,bool visible)197 void setCommandVisibility(int index, bool visible) {
198 visibleCanvas()->toggleCommand(index, visible);
199 }
getSize() const200 int getSize() const {
201 if (fInspectedLayer >=0) {
202 return fLayerManager->event(fInspectedLayer, fp).commandCount;
203 } else {
204 return frames[fp]->getSize();
205 }
206 }
getFrameCount() const207 int getFrameCount() const {
208 return frames.size();
209 }
210
211 // Return the command list in JSON representation as a string
jsonCommandList(sk_sp<SkSurface> surface)212 std::string jsonCommandList(sk_sp<SkSurface> surface) {
213 SkDynamicMemoryWStream stream;
214 SkJSONWriter writer(&stream, SkJSONWriter::Mode::kFast);
215 writer.beginObject(); // root
216 visibleCanvas()->toJSON(writer, udm, surface->getCanvas());
217 writer.endObject(); // root
218 writer.flush();
219 auto skdata = stream.detachAsData();
220 // Convert skdata to string_view, which accepts a length
221 std::string_view data_view(reinterpret_cast<const char*>(skdata->data()), skdata->size());
222 // and string_view to string, which emscripten understands.
223 return std::string(data_view);
224 }
225
226 // Gets the clip and matrix of the last command drawn
lastCommandInfo()227 std::string lastCommandInfo() {
228 SkM44 vm = visibleCanvas()->getCurrentMatrix();
229 SkIRect clip = visibleCanvas()->getCurrentClip();
230
231 SkDynamicMemoryWStream stream;
232 SkJSONWriter writer(&stream, SkJSONWriter::Mode::kFast);
233 writer.beginObject(); // root
234
235 writer.appendName("ViewMatrix");
236 DrawCommand::MakeJsonMatrix44(writer, vm);
237 writer.appendName("ClipRect");
238 DrawCommand::MakeJsonIRect(writer, clip);
239
240 writer.endObject(); // root
241 writer.flush();
242 auto skdata = stream.detachAsData();
243 // Convert skdata to string_view, which accepts a length
244 std::string_view data_view(reinterpret_cast<const char*>(skdata->data()), skdata->size());
245 // and string_view to string, which emscripten understands.
246 return std::string(data_view);
247 }
248
changeFrame(int index)249 void changeFrame(int index) {
250 fp = index;
251 }
252
253 // Return the png file at the requested index in
254 // the skp file's vector of shared images. this is the set of images referred to by the
255 // filenames like "\\1" in DrawImage commands.
256 // Return type is the PNG data as a base64 encoded string with prepended URI.
getImageResource(int index)257 std::string getImageResource(int index) {
258 sk_sp<SkData> pngData = fImages[index]->encodeToData();
259 size_t len = SkBase64::Encode(pngData->data(), pngData->size(), nullptr);
260 SkString dst;
261 dst.resize(len);
262 SkBase64::Encode(pngData->data(), pngData->size(), dst.data());
263 dst.prepend("data:image/png;base64,");
264 return std::string(dst.c_str());
265 }
266
getImageCount()267 int getImageCount() {
268 return fImages.size();
269 }
270
271 // Get the image info of one of the resource images.
getImageInfo(int index)272 ImageInfoNoColorspace getImageInfo(int index) {
273 return toImageInfoNoColorspace(fImages[index]->imageInfo());
274 }
275
276 // return data on which commands each image is used in.
277 // (frame, -1) returns info for the given frame,
278 // (frame, nodeid) return info for a layer update
279 // { imageid: [commandid, commandid, ...], ... }
imageUseInfo(int framenumber,int nodeid)280 JSObject imageUseInfo(int framenumber, int nodeid) {
281 JSObject result = emscripten::val::object();
282 DebugCanvas* debugCanvas = frames[framenumber].get();
283 if (nodeid >= 0) {
284 debugCanvas = fLayerManager->getEventDebugCanvas(nodeid, framenumber);
285 }
286 const auto& map = debugCanvas->getImageIdToCommandMap(udm);
287 for (auto it = map.begin(); it != map.end(); ++it) {
288 JSArray list = emscripten::val::array();
289 for (const int commandId : it->second) {
290 list.call<void>("push", commandId);
291 }
292 result.set(std::to_string(it->first), list);
293 }
294 return result;
295 }
296
297 // Return information on every layer (offscreeen buffer) that is available for drawing at
298 // the current frame.
getLayerSummariesJs()299 JSArray getLayerSummariesJs() {
300 JSArray result = emscripten::val::array();
301 for (auto summary : fLayerManager->summarizeLayers(fp)) {
302 result.call<void>("push", summary);
303 }
304 return result;
305 }
306
getLayerKeys()307 JSArray getLayerKeys() {
308 JSArray result = emscripten::val::array();
309 for (auto key : fLayerManager->getKeys()) {
310 JSObject item = emscripten::val::object();
311 item.set("frame", key.frame);
312 item.set("nodeId", key.nodeId);
313 result.call<void>("push", item);
314 }
315 return result;
316 }
317
318 // When set to a valid layer index, causes this class to playback the layer draw event at nodeId
319 // on frame fp. No validation of nodeId or fp is performed, this must be valid values obtained
320 // from either fLayerManager.listNodesForFrame or fLayerManager.summarizeEvents
321 // Set to -1 to return to viewing the top level animation
setInspectedLayer(int nodeId)322 void setInspectedLayer(int nodeId) {
323 fInspectedLayer = nodeId;
324 }
325
326 // Finds a command that left the given pixel in it's current state.
327 // Note that this method may fail to find the absolute last command that leaves a pixel
328 // the given color, but there is probably only one candidate in most cases, and the log(n)
329 // makes it worth it.
findCommandByPixel(SkSurface * surface,int x,int y,int commandIndex)330 int findCommandByPixel(SkSurface* surface, int x, int y, int commandIndex) {
331 // What color is the pixel now?
332 SkColor finalColor = evaluateCommandColor(surface, commandIndex, x, y);
333
334 int lowerBound = 0;
335 int upperBound = commandIndex;
336
337 while (upperBound - lowerBound > 1) {
338 int command = (upperBound - lowerBound) / 2 + lowerBound;
339 auto c = evaluateCommandColor(surface, command, x, y);
340 if (c == finalColor) {
341 upperBound = command;
342 } else {
343 lowerBound = command;
344 }
345 }
346 // clean up after side effects
347 drawTo(surface, commandIndex);
348 return upperBound;
349 }
350
351 private:
352
353 // Helper for findCommandByPixel.
354 // Has side effect of flushing to surface.
355 // TODO(nifong) eliminate side effect.
evaluateCommandColor(SkSurface * surface,int command,int x,int y)356 SkColor evaluateCommandColor(SkSurface* surface, int command, int x, int y) {
357 drawTo(surface, command);
358
359 SkColor c;
360 SkImageInfo info = SkImageInfo::Make(1, 1, kRGBA_8888_SkColorType, kOpaque_SkAlphaType);
361 SkPixmap pixmap(info, &c, 4);
362 surface->readPixels(pixmap, x, y);
363 return c;
364 }
365
366 // Loads a single frame (traditional) skp file from the provided data stream and returns
367 // a newly allocated DebugCanvas initialized with the SkPicture that was in the file.
loadSingleFrame(SkMemoryStream * stream)368 std::unique_ptr<DebugCanvas> loadSingleFrame(SkMemoryStream* stream) {
369 // note overloaded = operator that actually does a move
370 sk_sp<SkPicture> picture = SkPicture::MakeFromStream(stream);
371 if (!picture) {
372 SkDebugf("Unable to deserialze frame.\n");
373 return nullptr;
374 }
375 SkDebugf("Parsed SKP file.\n");
376 // Make debug canvas using bounds from SkPicture
377 fBoundsArray.push_back(picture->cullRect().roundOut());
378 std::unique_ptr<DebugCanvas> debugCanvas = std::make_unique<DebugCanvas>(fBoundsArray.back());
379
380 // Only draw picture to the debug canvas once.
381 debugCanvas->drawPicture(picture);
382 return debugCanvas;
383 }
384
loadMultiFrame(SkMemoryStream * stream)385 std::string loadMultiFrame(SkMemoryStream* stream) {
386 // Attempt to deserialize with an image sharing serial proc.
387 auto deserialContext = std::make_unique<SkSharingDeserialContext>();
388 SkDeserialProcs procs;
389 procs.fImageProc = SkSharingDeserialContext::deserializeImage;
390 procs.fImageCtx = deserialContext.get();
391
392 int page_count = SkMultiPictureDocumentReadPageCount(stream);
393 if (!page_count) {
394 // MSKP's have a version separate from the SKP subpictures they contain.
395 return "Not a MultiPictureDocument, MultiPictureDocument file version too old, or MultiPictureDocument contained 0 frames.";
396 }
397 SkDebugf("Expecting %d frames\n", page_count);
398
399 std::vector<SkDocumentPage> pages(page_count);
400 if (!SkMultiPictureDocumentRead(stream, pages.data(), page_count, &procs)) {
401 return "Reading frames from MultiPictureDocument failed";
402 }
403
404 fLayerManager = std::make_unique<DebugLayerManager>();
405
406 int i = 0;
407 for (const auto& page : pages) {
408 // Make debug canvas using bounds from SkPicture
409 fBoundsArray.push_back(page.fPicture->cullRect().roundOut());
410 std::unique_ptr<DebugCanvas> debugCanvas = std::make_unique<DebugCanvas>(fBoundsArray.back());
411 debugCanvas->setLayerManagerAndFrame(fLayerManager.get(), i);
412
413 // Only draw picture to the debug canvas once.
414 debugCanvas->drawPicture(page.fPicture);
415
416 if (debugCanvas->getSize() <=0 ){
417 SkDebugf("Skipped corrupted frame, had %d commands \n", debugCanvas->getSize());
418 continue;
419 }
420 // If you don't set these, they're undefined.
421 debugCanvas->setOverdrawViz(false);
422 debugCanvas->setDrawGpuOpBounds(false);
423 debugCanvas->setClipVizColor(SK_ColorTRANSPARENT);
424 debugCanvas->setAndroidClipViz(false);
425 frames.push_back(std::move(debugCanvas));
426 i++;
427 }
428 fImages = deserialContext->fImages;
429
430 udm.indexImages(fImages);
431 return "";
432 }
433
434 // constrains the draw command index to the frame's command list length.
constrainFrameCommand(int index)435 int constrainFrameCommand(int index) {
436 int cmdlen = frames[fp]->getSize();
437 if (index >= cmdlen) {
438 return cmdlen-1;
439 }
440 return index;
441 }
442
443 // A vector of DebugCanvas, each one initialized to a frame of the animation.
444 std::vector<std::unique_ptr<DebugCanvas>> frames;
445 // The index of the current frame (into the vector above)
446 int fp = 0;
447 // The width and height of every frame.
448 // frame sizes are known to change in Android Skia RenderEngine because it interleves pictures from different applications.
449 std::vector<SkIRect> fBoundsArray;
450 // image resources from a loaded file
451 std::vector<sk_sp<SkImage>> fImages;
452
453 // The URLDataManager here is a cache that accepts encoded data (pngs) and puts
454 // numbers on them. We have our own collection of images (fImages) that was populated by the
455 // SkSharingDeserialContext when mskp files are loaded which it can use for IDing images
456 // without having to serialize them.
457 UrlDataManager udm;
458
459 // A structure holding the picture information needed to draw any layers used in an mskp file
460 // individual frames hold a pointer to it, store draw events, and request images from it.
461 // it is stateful and is set to the current frame at all times.
462 std::unique_ptr<DebugLayerManager> fLayerManager;
463
464 // The node id of a layer being inspected, if any.
465 // -1 means we are viewing the top level animation, not a layer.
466 // the exact draw event being inspected depends also on the selected frame `fp`.
467 int fInspectedLayer = -1;
468 };
469
470 using namespace emscripten;
EMSCRIPTEN_BINDINGS(my_module)471 EMSCRIPTEN_BINDINGS(my_module) {
472
473 function("MinVersion", &MinVersion);
474
475 // The main class that the JavaScript in index.html uses
476 class_<SkpDebugPlayer>("SkpDebugPlayer")
477 .constructor<>()
478 .function("changeFrame", &SkpDebugPlayer::changeFrame)
479 .function("deleteCommand", &SkpDebugPlayer::deleteCommand)
480 .function("draw", &SkpDebugPlayer::draw, allow_raw_pointers())
481 .function("drawTo", &SkpDebugPlayer::drawTo, allow_raw_pointers())
482 .function("findCommandByPixel", &SkpDebugPlayer::findCommandByPixel, allow_raw_pointers())
483 .function("getBounds", &SkpDebugPlayer::getBounds)
484 .function("getBoundsForFrame", &SkpDebugPlayer::getBoundsForFrame)
485 .function("getFrameCount", &SkpDebugPlayer::getFrameCount)
486 .function("getImageResource", &SkpDebugPlayer::getImageResource)
487 .function("getImageCount", &SkpDebugPlayer::getImageCount)
488 .function("getImageInfo", &SkpDebugPlayer::getImageInfo)
489 .function("getLayerKeys", &SkpDebugPlayer::getLayerKeys)
490 .function("getLayerSummariesJs", &SkpDebugPlayer::getLayerSummariesJs)
491 .function("getSize", &SkpDebugPlayer::getSize)
492 .function("imageUseInfo", &SkpDebugPlayer::imageUseInfo)
493 .function("imageUseInfoForFrameJs", optional_override([](SkpDebugPlayer& self, const int frame)->JSObject {
494 // -1 as a node id is used throughout the application to mean no layer inspected.
495 return self.imageUseInfo(frame, -1);
496 }))
497 .function("jsonCommandList", &SkpDebugPlayer::jsonCommandList, allow_raw_pointers())
498 .function("lastCommandInfo", &SkpDebugPlayer::lastCommandInfo)
499 .function("loadSkp", &SkpDebugPlayer::loadSkp, allow_raw_pointers())
500 .function("setClipVizColor", &SkpDebugPlayer::setClipVizColor)
501 .function("setCommandVisibility", &SkpDebugPlayer::setCommandVisibility)
502 .function("setGpuOpBounds", &SkpDebugPlayer::setGpuOpBounds)
503 .function("setInspectedLayer", &SkpDebugPlayer::setInspectedLayer)
504 .function("setOriginVisible", &SkpDebugPlayer::setOriginVisible)
505 .function("setOverdrawVis", &SkpDebugPlayer::setOverdrawVis)
506 .function("setAndroidClipViz", &SkpDebugPlayer::setAndroidClipViz);
507
508 // Structs used as arguments or returns to the functions above
509 // TODO(kjlubick) handle this rect like the ones in CanvasKit
510 value_object<SkIRect>("SkIRect")
511 .field("fLeft", &SkIRect::fLeft)
512 .field("fTop", &SkIRect::fTop)
513 .field("fRight", &SkIRect::fRight)
514 .field("fBottom", &SkIRect::fBottom);
515 // emscripten provided the following convenience function for binding vector<T>
516 // https://emscripten.org/docs/api_reference/bind.h.html#_CPPv415register_vectorPKc
517 register_vector<DebugLayerManager::LayerSummary>("VectorLayerSummary");
518 value_object<DebugLayerManager::LayerSummary>("LayerSummary")
519 .field("nodeId", &DebugLayerManager::LayerSummary::nodeId)
520 .field("frameOfLastUpdate", &DebugLayerManager::LayerSummary::frameOfLastUpdate)
521 .field("fullRedraw", &DebugLayerManager::LayerSummary::fullRedraw)
522 .field("layerWidth", &DebugLayerManager::LayerSummary::layerWidth)
523 .field("layerHeight", &DebugLayerManager::LayerSummary::layerHeight);
524
525 value_object<ImageInfoNoColorspace>("ImageInfoNoColorspace")
526 .field("width", &ImageInfoNoColorspace::width)
527 .field("height", &ImageInfoNoColorspace::height)
528 .field("colorType", &ImageInfoNoColorspace::colorType)
529 .field("alphaType", &ImageInfoNoColorspace::alphaType);
530 }
531