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