| /* | |
| * This file defines SkpDebugPlayer, a class which loads a SKP or MSKP file and draws it | |
| * to an SkSurface with annotation, and detailed playback controls. It holds as many DebugCanvases | |
| * as there are frames in the file. | |
| * | |
| * It also defines emscripten bindings for SkpDebugPlayer and other classes necessary to us it. | |
| * | |
| * Copyright 2019 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/codec/SkCodec.h" | |
| #include "include/core/SkFontMgr.h" | |
| #include "include/core/SkImage.h" | |
| #include "include/core/SkPicture.h" | |
| #include "include/core/SkStream.h" | |
| #include "include/core/SkString.h" | |
| #include "include/core/SkSurface.h" | |
| #include "include/docs/SkMultiPictureDocument.h" | |
| #include "include/encode/SkPngEncoder.h" | |
| #include "include/ports/SkFontMgr_empty.h" | |
| #include "src/core/SkBase64.h" | |
| #include "src/core/SkPicturePriv.h" | |
| #include "src/ports/SkTypeface_FreeType.h" | |
| #include "src/utils/SkJSONWriter.h" | |
| #include "tools/SkSharingProc.h" | |
| #include "tools/UrlDataManager.h" | |
| #include "tools/debugger/DebugCanvas.h" | |
| #include "tools/debugger/DebugLayerManager.h" | |
| #include "tools/debugger/DrawCommand.h" | |
| #include <memory> | |
| #include <string> | |
| #include <string_view> | |
| #include <vector> | |
| #include <map> | |
| #include <emscripten.h> | |
| #include <emscripten/bind.h> | |
| #ifdef CK_ENABLE_WEBGL | |
| #include "include/gpu/ganesh/GrBackendSurface.h" | |
| #include "include/gpu/ganesh/GrDirectContext.h" | |
| #include "include/gpu/ganesh/SkSurfaceGanesh.h" | |
| #include "include/gpu/ganesh/gl/GrGLInterface.h" | |
| #include "include/gpu/ganesh/gl/GrGLTypes.h" | |
| #include <GL/gl.h> | |
| #include <emscripten/html5.h> | |
| #endif | |
| #include "modules/canvaskit/WasmCommon.h" | |
| // file signature for SkMultiPictureDocument | |
| // TODO(nifong): make public and include from SkMultiPictureDocument.h | |
| static constexpr char kMultiMagic[] = "Skia Multi-Picture Doc\n\n"; | |
| uint32_t MinVersion() { return SkPicturePriv::kMin_Version; } | |
| struct ImageInfoNoColorspace { | |
| int width; | |
| int height; | |
| SkColorType colorType; | |
| SkAlphaType alphaType; | |
| }; | |
| // TODO(kjlubick) Should this handle colorspace | |
| ImageInfoNoColorspace toImageInfoNoColorspace(const SkImageInfo& ii) { | |
| return (ImageInfoNoColorspace){ii.width(), ii.height(), ii.colorType(), ii.alphaType()}; | |
| } | |
| static sk_sp<SkImage> deserializeImage(sk_sp<SkData> data, std::optional<SkAlphaType> at, void*) { | |
| std::unique_ptr<SkCodec> codec = DecodeImageData(std::move(data)); | |
| if (!codec) { | |
| SkDebugf("Could not decode an image\n"); | |
| return nullptr; | |
| } | |
| // Force the decode to happen instead of returning a lazy image. | |
| return SkCodecs::DeferredImage(std::move(codec), at)->makeRasterImage(nullptr); | |
| } | |
| static sk_sp<SkTypeface> deserializeTypeface(SkStream& stream, void* ctx) { | |
| return SkTypeface::MakeDeserialize(&stream, *static_cast<sk_sp<SkFontMgr>*>(ctx)); | |
| } | |
| static void register_typeface() { | |
| static SkOnce once; | |
| once([] { | |
| SkTypeface::Register(SkTypeface_FreeType::FactoryId, | |
| SkTypeface_FreeType::MakeFromStream ); | |
| }); | |
| } | |
| class SkpDebugPlayer { | |
| public: | |
| SkpDebugPlayer() : | |
| udm(UrlDataManager(SkString("/data"))){} | |
| /* loadSkp deserializes a skp file that has been copied into the shared WASM memory. | |
| * cptr - a pointer to the data to deserialize. | |
| * length - length of the data in bytes. | |
| * The caller must allocate the memory with M._malloc where M is the wasm module in javascript | |
| * and copy the data into M.buffer at the pointer returned by malloc. | |
| * | |
| * uintptr_t is used here because emscripten will not allow binding of functions with pointers | |
| * to primitive types. We can instead pass a number and cast it to whatever kind of | |
| * pointer we're expecting. | |
| * | |
| * Returns an error string which is populated in the case that the file cannot be read. | |
| */ | |
| std::string loadSkp(uintptr_t cptr, int length) { | |
| const uint8_t* data = reinterpret_cast<const uint8_t*>(cptr); | |
| // Both traditional and multi-frame skp files have a magic word | |
| SkMemoryStream stream(data, length); | |
| SkDebugf("make stream at %p, with %d bytes\n",data, length); | |
| const bool isMulti = memcmp(data, kMultiMagic, sizeof(kMultiMagic) - 1) == 0; | |
| if (isMulti) { | |
| SkDebugf("Try reading as a multi-frame skp\n"); | |
| const auto& error = loadMultiFrame(&stream); | |
| if (!error.empty()) { return error; } | |
| } else { | |
| SkDebugf("Try reading as single-frame skp\n"); | |
| // TODO(nifong): Rely on SkPicture's return errors once it provides some. | |
| std::unique_ptr<DebugCanvas> canvas = loadSingleFrame(&stream); | |
| if (!canvas) { | |
| return "Error loading single frame"; | |
| } | |
| frames.push_back(std::move(canvas)); | |
| } | |
| return ""; | |
| } | |
| /* drawTo asks the debug canvas to draw from the beginning of the picture | |
| * to the given command and flush the canvas. | |
| */ | |
| void drawTo(SkSurface* surface, int32_t index) { | |
| // Set the command within the frame or layer event being drawn. | |
| if (fInspectedLayer >= 0) { | |
| fLayerManager->setCommand(fInspectedLayer, fp, index); | |
| } else { | |
| index = constrainFrameCommand(index); | |
| } | |
| auto* canvas = surface->getCanvas(); | |
| canvas->clear(SK_ColorTRANSPARENT); | |
| if (fInspectedLayer >= 0) { | |
| // when it's a layer event we're viewing, we use the layer manager to render it. | |
| fLayerManager->drawLayerEventTo(surface, fInspectedLayer, fp); | |
| } else { | |
| // otherwise, its a frame at the top level. | |
| frames[fp]->drawTo(surface->getCanvas(), index); | |
| } | |
| #ifdef CK_ENABLE_WEBGL | |
| skgpu::ganesh::Flush(surface); | |
| #endif | |
| } | |
| // Draws to the end of the current frame. | |
| void draw(SkSurface* surface) { | |
| auto* canvas = surface->getCanvas(); | |
| canvas->clear(SK_ColorTRANSPARENT); | |
| frames[fp]->draw(surface->getCanvas()); | |
| #ifdef CK_ENABLE_WEBGL | |
| skgpu::ganesh::Flush(surface); | |
| #endif | |
| } | |
| // Gets the bounds for the given frame | |
| // (or layer update, assuming there is one at that frame for fInspectedLayer) | |
| SkIRect getBoundsForFrame(int32_t frame) { | |
| if (fInspectedLayer < 0) { | |
| return fBoundsArray[frame]; | |
| } | |
| auto summary = fLayerManager->event(fInspectedLayer, fp); | |
| return SkIRect::MakeWH(summary.layerWidth, summary.layerHeight); | |
| } | |
| // Gets the bounds for the current frame | |
| const SkIRect getBounds() { | |
| return getBoundsForFrame(fp); | |
| } | |
| // returns the debugcanvas of the current frame, or the current draw event when inspecting | |
| // a layer. | |
| DebugCanvas* visibleCanvas() { | |
| if (fInspectedLayer >=0) { | |
| return fLayerManager->getEventDebugCanvas(fInspectedLayer, fp); | |
| } else { | |
| return frames[fp].get(); | |
| } | |
| } | |
| // The following three operations apply to every debugcanvas because they are overdraw features. | |
| // There is only one toggle for them on the app, they are global settings. | |
| // However, there's not a simple way to make the debugcanvases pull settings from a central | |
| // location so we set it on all of them at once. | |
| void setOverdrawVis(bool on) { | |
| for (size_t i=0; i < frames.size(); i++) { | |
| frames[i]->setOverdrawViz(on); | |
| } | |
| fLayerManager->setOverdrawViz(on); | |
| } | |
| void setGpuOpBounds(bool on) { | |
| for (size_t i=0; i < frames.size(); i++) { | |
| frames[i]->setDrawGpuOpBounds(on); | |
| } | |
| fLayerManager->setDrawGpuOpBounds(on); | |
| } | |
| void setClipVizColor(JSColor color) { | |
| for (size_t i=0; i < frames.size(); i++) { | |
| frames[i]->setClipVizColor(SkColor(color)); | |
| } | |
| fLayerManager->setClipVizColor(SkColor(color)); | |
| } | |
| void setAndroidClipViz(bool on) { | |
| for (size_t i=0; i < frames.size(); i++) { | |
| frames[i]->setAndroidClipViz(on); | |
| } | |
| // doesn't matter in layers | |
| } | |
| void setOriginVisible(bool on) { | |
| for (size_t i=0; i < frames.size(); i++) { | |
| frames[i]->setOriginVisible(on); | |
| } | |
| } | |
| // The two operations below only apply to the current frame, because they concern the command | |
| // list, which is unique to each frame. | |
| void deleteCommand(int index) { | |
| visibleCanvas()->deleteDrawCommandAt(index); | |
| } | |
| void setCommandVisibility(int index, bool visible) { | |
| visibleCanvas()->toggleCommand(index, visible); | |
| } | |
| int getSize() const { | |
| if (fInspectedLayer >=0) { | |
| return fLayerManager->event(fInspectedLayer, fp).commandCount; | |
| } else { | |
| return frames[fp]->getSize(); | |
| } | |
| } | |
| int getFrameCount() const { | |
| return frames.size(); | |
| } | |
| // Return the command list in JSON representation as a string | |
| std::string jsonCommandList(sk_sp<SkSurface> surface) { | |
| SkDynamicMemoryWStream stream; | |
| SkSerialProcs procs; | |
| procs.fImageProc = [](SkImage* img, void*) -> sk_sp<const SkData> { | |
| return SkPngEncoder::Encode(nullptr, img, {}); | |
| }; | |
| SkJSONWriter writer(&stream, procs, SkJSONWriter::Mode::kFast); | |
| writer.beginObject(); // root | |
| visibleCanvas()->toJSON(writer, udm, surface->getCanvas()); | |
| writer.endObject(); // root | |
| writer.flush(); | |
| auto skdata = stream.detachAsData(); | |
| // Convert skdata to string_view, which accepts a length | |
| std::string_view data_view(reinterpret_cast<const char*>(skdata->data()), skdata->size()); | |
| // and string_view to string, which emscripten understands. | |
| return std::string(data_view); | |
| } | |
| // Gets the clip and matrix of the last command drawn | |
| std::string lastCommandInfo() { | |
| SkM44 vm = visibleCanvas()->getCurrentMatrix(); | |
| SkIRect clip = visibleCanvas()->getCurrentClip(); | |
| SkDynamicMemoryWStream stream; | |
| SkJSONWriter writer(&stream, SkSerialProcs{}, SkJSONWriter::Mode::kFast); | |
| writer.beginObject(); // root | |
| writer.appendName("ViewMatrix"); | |
| DrawCommand::MakeJsonMatrix44(writer, vm); | |
| writer.appendName("ClipRect"); | |
| DrawCommand::MakeJsonIRect(writer, clip); | |
| writer.endObject(); // root | |
| writer.flush(); | |
| auto skdata = stream.detachAsData(); | |
| // Convert skdata to string_view, which accepts a length | |
| std::string_view data_view(reinterpret_cast<const char*>(skdata->data()), skdata->size()); | |
| // and string_view to string, which emscripten understands. | |
| return std::string(data_view); | |
| } | |
| void changeFrame(int index) { | |
| fp = index; | |
| } | |
| // Return the png file at the requested index in | |
| // the skp file's vector of shared images. this is the set of images referred to by the | |
| // filenames like "\\1" in DrawImage commands. | |
| // Return type is the PNG data as a base64 encoded string with prepended URI. | |
| std::string getImageResource(int index) { | |
| sk_sp<SkData> pngData = SkPngEncoder::Encode(nullptr, fImages[index].get(), {}); | |
| size_t len = SkBase64::EncodedSize(pngData->size()); | |
| SkString dst; | |
| dst.resize(len); | |
| SkBase64::Encode(pngData->data(), pngData->size(), dst.data()); | |
| dst.prepend("data:image/png;base64,"); | |
| return std::string(dst.c_str()); | |
| } | |
| int getImageCount() { | |
| return fImages.size(); | |
| } | |
| // Get the image info of one of the resource images. | |
| ImageInfoNoColorspace getImageInfo(int index) { | |
| return toImageInfoNoColorspace(fImages[index]->imageInfo()); | |
| } | |
| // return data on which commands each image is used in. | |
| // (frame, -1) returns info for the given frame, | |
| // (frame, nodeid) return info for a layer update | |
| // { imageid: [commandid, commandid, ...], ... } | |
| JSObject imageUseInfo(int framenumber, int nodeid) { | |
| JSObject result = emscripten::val::object(); | |
| DebugCanvas* debugCanvas = frames[framenumber].get(); | |
| if (nodeid >= 0) { | |
| debugCanvas = fLayerManager->getEventDebugCanvas(nodeid, framenumber); | |
| } | |
| const auto& map = debugCanvas->getImageIdToCommandMap(udm); | |
| for (auto it = map.begin(); it != map.end(); ++it) { | |
| JSArray list = emscripten::val::array(); | |
| for (const int commandId : it->second) { | |
| list.call<void>("push", commandId); | |
| } | |
| result.set(std::to_string(it->first), list); | |
| } | |
| return result; | |
| } | |
| // Return information on every layer (offscreeen buffer) that is available for drawing at | |
| // the current frame. | |
| JSArray getLayerSummariesJs() { | |
| JSArray result = emscripten::val::array(); | |
| for (auto summary : fLayerManager->summarizeLayers(fp)) { | |
| result.call<void>("push", summary); | |
| } | |
| return result; | |
| } | |
| JSArray getLayerKeys() { | |
| JSArray result = emscripten::val::array(); | |
| for (auto key : fLayerManager->getKeys()) { | |
| JSObject item = emscripten::val::object(); | |
| item.set("frame", key.frame); | |
| item.set("nodeId", key.nodeId); | |
| result.call<void>("push", item); | |
| } | |
| return result; | |
| } | |
| // When set to a valid layer index, causes this class to playback the layer draw event at nodeId | |
| // on frame fp. No validation of nodeId or fp is performed, this must be valid values obtained | |
| // from either fLayerManager.listNodesForFrame or fLayerManager.summarizeEvents | |
| // Set to -1 to return to viewing the top level animation | |
| void setInspectedLayer(int nodeId) { | |
| fInspectedLayer = nodeId; | |
| } | |
| // Finds a command that left the given pixel in it's current state. | |
| // Note that this method may fail to find the absolute last command that leaves a pixel | |
| // the given color, but there is probably only one candidate in most cases, and the log(n) | |
| // makes it worth it. | |
| int findCommandByPixel(SkSurface* surface, int x, int y, int commandIndex) { | |
| // What color is the pixel now? | |
| SkColor finalColor = evaluateCommandColor(surface, commandIndex, x, y); | |
| int lowerBound = 0; | |
| int upperBound = commandIndex; | |
| while (upperBound - lowerBound > 1) { | |
| int command = (upperBound - lowerBound) / 2 + lowerBound; | |
| auto c = evaluateCommandColor(surface, command, x, y); | |
| if (c == finalColor) { | |
| upperBound = command; | |
| } else { | |
| lowerBound = command; | |
| } | |
| } | |
| // clean up after side effects | |
| drawTo(surface, commandIndex); | |
| return upperBound; | |
| } | |
| private: | |
| // Helper for findCommandByPixel. | |
| // Has side effect of flushing to surface. | |
| // TODO(nifong) eliminate side effect. | |
| SkColor evaluateCommandColor(SkSurface* surface, int command, int x, int y) { | |
| drawTo(surface, command); | |
| SkColor c; | |
| SkImageInfo info = SkImageInfo::Make(1, 1, kRGBA_8888_SkColorType, kOpaque_SkAlphaType); | |
| SkPixmap pixmap(info, &c, 4); | |
| surface->readPixels(pixmap, x, y); | |
| return c; | |
| } | |
| // Loads a single frame (traditional) skp file from the provided data stream and returns | |
| // a newly allocated DebugCanvas initialized with the SkPicture that was in the file. | |
| std::unique_ptr<DebugCanvas> loadSingleFrame(SkMemoryStream* stream) { | |
| register_typeface(); | |
| SkDeserialProcs procs; | |
| procs.fImageDataProc = deserializeImage; | |
| sk_sp<SkFontMgr> fallback = SkFontMgr_New_Custom_Empty(); | |
| procs.fTypefaceCtx = &fallback; | |
| procs.fTypefaceStreamProc = deserializeTypeface; | |
| sk_sp<SkPicture> picture = SkPicture::MakeFromStream(stream, &procs); | |
| if (!picture) { | |
| SkDebugf("Unable to deserialze frame.\n"); | |
| return nullptr; | |
| } | |
| SkDebugf("Parsed SKP file.\n"); | |
| // Make debug canvas using bounds from SkPicture | |
| fBoundsArray.push_back(picture->cullRect().roundOut()); | |
| std::unique_ptr<DebugCanvas> debugCanvas = std::make_unique<DebugCanvas>(fBoundsArray.back()); | |
| // Only draw picture to the debug canvas once. | |
| debugCanvas->drawPicture(picture); | |
| return debugCanvas; | |
| } | |
| std::string loadMultiFrame(SkMemoryStream* stream) { | |
| register_typeface(); | |
| // Attempt to deserialize with an image sharing serial proc. | |
| auto deserialContext = std::make_unique<SkSharingDeserialContext>(); | |
| SkDeserialProcs procs; | |
| procs.fImageDataProc = SkSharingContext::deserializeImage; | |
| procs.fImageCtx = deserialContext.get(); | |
| sk_sp<SkFontMgr> fallback = SkFontMgr_New_Custom_Empty(); | |
| procs.fTypefaceCtx = &fallback; | |
| procs.fTypefaceStreamProc = deserializeTypeface; | |
| int page_count = SkMultiPictureDocument::ReadPageCount(stream); | |
| if (!page_count) { | |
| // MSKP's have a version separate from the SKP subpictures they contain. | |
| return "Not a MultiPictureDocument, MultiPictureDocument file version too old, or MultiPictureDocument contained 0 frames."; | |
| } | |
| SkDebugf("Expecting %d frames\n", page_count); | |
| std::vector<SkDocumentPage> pages(page_count); | |
| if (!SkMultiPictureDocument::Read(stream, pages.data(), page_count, &procs)) { | |
| return "Reading frames from MultiPictureDocument failed"; | |
| } | |
| fLayerManager = std::make_unique<DebugLayerManager>(); | |
| int i = 0; | |
| for (const auto& page : pages) { | |
| // Make debug canvas using bounds from SkPicture | |
| fBoundsArray.push_back(page.fPicture->cullRect().roundOut()); | |
| std::unique_ptr<DebugCanvas> debugCanvas = std::make_unique<DebugCanvas>(fBoundsArray.back()); | |
| debugCanvas->setLayerManagerAndFrame(fLayerManager.get(), i); | |
| // Only draw picture to the debug canvas once. | |
| debugCanvas->drawPicture(page.fPicture); | |
| if (debugCanvas->getSize() <=0 ){ | |
| SkDebugf("Skipped corrupted frame, had %d commands \n", debugCanvas->getSize()); | |
| continue; | |
| } | |
| // If you don't set these, they're undefined. | |
| debugCanvas->setOverdrawViz(false); | |
| debugCanvas->setDrawGpuOpBounds(false); | |
| debugCanvas->setClipVizColor(SK_ColorTRANSPARENT); | |
| debugCanvas->setAndroidClipViz(false); | |
| frames.push_back(std::move(debugCanvas)); | |
| i++; | |
| } | |
| fImages = deserialContext->fImages; | |
| udm.indexImages(fImages); | |
| return ""; | |
| } | |
| // constrains the draw command index to the frame's command list length. | |
| int constrainFrameCommand(int index) { | |
| int cmdlen = frames[fp]->getSize(); | |
| if (index >= cmdlen) { | |
| return cmdlen-1; | |
| } | |
| return index; | |
| } | |
| // A vector of DebugCanvas, each one initialized to a frame of the animation. | |
| std::vector<std::unique_ptr<DebugCanvas>> frames; | |
| // The index of the current frame (into the vector above) | |
| int fp = 0; | |
| // The width and height of every frame. | |
| // frame sizes are known to change in Android Skia RenderEngine because it interleves pictures from different applications. | |
| std::vector<SkIRect> fBoundsArray; | |
| // image resources from a loaded file | |
| std::vector<sk_sp<SkImage>> fImages; | |
| // The URLDataManager here is a cache that accepts encoded data (pngs) and puts | |
| // numbers on them. We have our own collection of images (fImages) that was populated by the | |
| // SkSharingDeserialContext when mskp files are loaded which it can use for IDing images | |
| // without having to serialize them. | |
| UrlDataManager udm; | |
| // A structure holding the picture information needed to draw any layers used in an mskp file | |
| // individual frames hold a pointer to it, store draw events, and request images from it. | |
| // it is stateful and is set to the current frame at all times. | |
| std::unique_ptr<DebugLayerManager> fLayerManager; | |
| // The node id of a layer being inspected, if any. | |
| // -1 means we are viewing the top level animation, not a layer. | |
| // the exact draw event being inspected depends also on the selected frame `fp`. | |
| int fInspectedLayer = -1; | |
| }; | |
| using namespace emscripten; | |
| EMSCRIPTEN_BINDINGS(my_module) { | |
| function("MinVersion", &MinVersion); | |
| // The main class that the JavaScript in index.html uses | |
| class_<SkpDebugPlayer>("SkpDebugPlayer") | |
| .constructor<>() | |
| .function("changeFrame", &SkpDebugPlayer::changeFrame) | |
| .function("deleteCommand", &SkpDebugPlayer::deleteCommand) | |
| .function("draw", &SkpDebugPlayer::draw, allow_raw_pointers()) | |
| .function("drawTo", &SkpDebugPlayer::drawTo, allow_raw_pointers()) | |
| .function("findCommandByPixel", &SkpDebugPlayer::findCommandByPixel, allow_raw_pointers()) | |
| .function("getBounds", &SkpDebugPlayer::getBounds) | |
| .function("getBoundsForFrame", &SkpDebugPlayer::getBoundsForFrame) | |
| .function("getFrameCount", &SkpDebugPlayer::getFrameCount) | |
| .function("getImageResource", &SkpDebugPlayer::getImageResource) | |
| .function("getImageCount", &SkpDebugPlayer::getImageCount) | |
| .function("getImageInfo", &SkpDebugPlayer::getImageInfo) | |
| .function("getLayerKeys", &SkpDebugPlayer::getLayerKeys) | |
| .function("getLayerSummariesJs", &SkpDebugPlayer::getLayerSummariesJs) | |
| .function("getSize", &SkpDebugPlayer::getSize) | |
| .function("imageUseInfo", &SkpDebugPlayer::imageUseInfo) | |
| .function("imageUseInfoForFrameJs", optional_override([](SkpDebugPlayer& self, const int frame)->JSObject { | |
| // -1 as a node id is used throughout the application to mean no layer inspected. | |
| return self.imageUseInfo(frame, -1); | |
| })) | |
| .function("jsonCommandList", &SkpDebugPlayer::jsonCommandList, allow_raw_pointers()) | |
| .function("lastCommandInfo", &SkpDebugPlayer::lastCommandInfo) | |
| .function("loadSkp", &SkpDebugPlayer::loadSkp, allow_raw_pointers()) | |
| .function("setClipVizColor", &SkpDebugPlayer::setClipVizColor) | |
| .function("setCommandVisibility", &SkpDebugPlayer::setCommandVisibility) | |
| .function("setGpuOpBounds", &SkpDebugPlayer::setGpuOpBounds) | |
| .function("setInspectedLayer", &SkpDebugPlayer::setInspectedLayer) | |
| .function("setOriginVisible", &SkpDebugPlayer::setOriginVisible) | |
| .function("setOverdrawVis", &SkpDebugPlayer::setOverdrawVis) | |
| .function("setAndroidClipViz", &SkpDebugPlayer::setAndroidClipViz); | |
| // Structs used as arguments or returns to the functions above | |
| // TODO(kjlubick) handle this rect like the ones in CanvasKit | |
| value_object<SkIRect>("SkIRect") | |
| .field("fLeft", &SkIRect::fLeft) | |
| .field("fTop", &SkIRect::fTop) | |
| .field("fRight", &SkIRect::fRight) | |
| .field("fBottom", &SkIRect::fBottom); | |
| // emscripten provided the following convenience function for binding vector<T> | |
| // https://emscripten.org/docs/api_reference/bind.h.html#_CPPv415register_vectorPKc | |
| register_vector<DebugLayerManager::LayerSummary>("VectorLayerSummary"); | |
| value_object<DebugLayerManager::LayerSummary>("LayerSummary") | |
| .field("nodeId", &DebugLayerManager::LayerSummary::nodeId) | |
| .field("frameOfLastUpdate", &DebugLayerManager::LayerSummary::frameOfLastUpdate) | |
| .field("fullRedraw", &DebugLayerManager::LayerSummary::fullRedraw) | |
| .field("layerWidth", &DebugLayerManager::LayerSummary::layerWidth) | |
| .field("layerHeight", &DebugLayerManager::LayerSummary::layerHeight); | |
| value_object<ImageInfoNoColorspace>("ImageInfoNoColorspace") | |
| .field("width", &ImageInfoNoColorspace::width) | |
| .field("height", &ImageInfoNoColorspace::height) | |
| .field("colorType", &ImageInfoNoColorspace::colorType) | |
| .field("alphaType", &ImageInfoNoColorspace::alphaType); | |
| } |