• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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