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