| /* | |
| * Copyright 2021 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "tools/MSKPPlayer.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkCanvasVirtualEnforcer.h" | |
| #include "include/core/SkPicture.h" | |
| #include "include/core/SkPictureRecorder.h" | |
| #include "include/core/SkSurface.h" | |
| #include "include/gpu/GrDirectContext.h" | |
| #include "include/private/SkTArray.h" | |
| #include "include/utils/SkNoDrawCanvas.h" | |
| #include "src/core/SkCanvasPriv.h" | |
| #include "src/core/SkTLazy.h" | |
| #include "src/utils/SkMultiPictureDocument.h" | |
| #include "tools/SkSharingProc.h" | |
| /////////////////////////////////////////////////////////////////////////////// | |
| // Base Cmd struct. | |
| struct MSKPPlayer::Cmd { | |
| virtual ~Cmd() = default; | |
| virtual bool isFullRedraw(SkCanvas*) const = 0; | |
| virtual void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const = 0; | |
| // If this command draws another layer, return it's ID. Otherwise, -1. | |
| virtual int layerID() const { return -1; } | |
| }; | |
| // Draws a SkPicture. | |
| struct MSKPPlayer::PicCmd : Cmd { | |
| sk_sp<SkPicture> fContent; | |
| SkIRect fClipRect = SkIRect::MakeEmpty(); // clip for picture (no clip if empty). | |
| bool isFullRedraw(SkCanvas* canvas) const override { | |
| if (fClipRect.isEmpty()) { | |
| return false; | |
| } | |
| return fClipRect.contains(SkIRect::MakeSize(canvas->getBaseLayerSize())); | |
| } | |
| void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const override { | |
| if (!fClipRect.isEmpty()) { | |
| canvas->save(); | |
| canvas->clipIRect(fClipRect); | |
| } | |
| canvas->drawPicture(fContent.get()); | |
| if (!fClipRect.isEmpty()) { | |
| canvas->restore(); | |
| } | |
| } | |
| }; | |
| // Draws another layer. Stores the ID of the layer to draw and what command index on that | |
| // layer should be current when the layer is drawn. The layer contents are updated to the | |
| // stored command index before the layer is drawn. | |
| struct MSKPPlayer::DrawLayerCmd : Cmd { | |
| int fLayerId; | |
| size_t fLayerCmdCnt; | |
| SkRect fSrcRect; | |
| SkRect fDstRect; | |
| SkSamplingOptions fSampling; | |
| SkCanvas::SrcRectConstraint fConstraint; | |
| SkTLazy<SkPaint> fPaint; | |
| bool isFullRedraw(SkCanvas* canvas) const override { return false; } | |
| void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const override; | |
| int layerID() const override { return fLayerId; } | |
| }; | |
| void MSKPPlayer::DrawLayerCmd::draw(SkCanvas* canvas, | |
| const LayerMap& layerMap, | |
| LayerStateMap* layerStateMap) const { | |
| const LayerCmds& layer = layerMap.at(fLayerId); | |
| LayerState* layerState = &(*layerStateMap)[fLayerId]; | |
| if (!layerState->fSurface) { | |
| layerState->fCurrCmd = 0; | |
| layerState->fSurface = MSKPPlayer::MakeSurfaceForLayer(layer, canvas); | |
| if (!layerState->fSurface) { | |
| SkDebugf("Couldn't create surface for layer"); | |
| return; | |
| } | |
| } | |
| size_t cmd = layerState->fCurrCmd; | |
| if (cmd > fLayerCmdCnt) { | |
| // If the layer contains contents from later commands then replay from the beginning. | |
| cmd = 0; | |
| } | |
| SkCanvas* layerCanvas = layerState->fSurface->getCanvas(); | |
| // Check if there is a full redraw between cmd and fLayerCmdCnt and if so jump to it and ensure | |
| // we clear the canvas if starting from a full redraw. | |
| for (size_t checkCmd = fLayerCmdCnt - 1; checkCmd > cmd; --checkCmd) { | |
| if (layer.fCmds[checkCmd]->isFullRedraw(layerCanvas)) { | |
| cmd = checkCmd; | |
| break; | |
| } | |
| } | |
| for (; cmd < fLayerCmdCnt; ++cmd) { | |
| if (cmd == 0 || layer.fCmds[cmd]->isFullRedraw(layerCanvas)) { | |
| layerState->fSurface->getCanvas()->clear(SK_ColorTRANSPARENT); | |
| } | |
| layer.fCmds[cmd]->draw(layerCanvas, layerMap, layerStateMap); | |
| } | |
| layerState->fCurrCmd = fLayerCmdCnt; | |
| const SkPaint* paint = fPaint.isValid() ? fPaint.get() : nullptr; | |
| canvas->drawImageRect(layerState->fSurface->makeImageSnapshot(), | |
| fSrcRect, | |
| fDstRect, | |
| fSampling, | |
| paint, | |
| fConstraint); | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| class MSKPPlayer::CmdRecordCanvas : public SkCanvasVirtualEnforcer<SkCanvas> { | |
| public: | |
| CmdRecordCanvas(LayerCmds* dst, LayerMap* offscreenLayers, const SkIRect* clipRect = nullptr) | |
| : fDst(dst), fOffscreenLayers(offscreenLayers) { | |
| if (clipRect) { | |
| fClipRect = *clipRect; | |
| } | |
| fRecorder.beginRecording(SkRect::Make(dst->fDimensions)); | |
| } | |
| ~CmdRecordCanvas() override { this->recordPicCmd(); } | |
| protected: | |
| void onDrawPaint(const SkPaint& paint) override { | |
| fRecorder.getRecordingCanvas()->drawPaint(paint); | |
| } | |
| void onDrawBehind(const SkPaint& paint) override { | |
| SkCanvasPriv::DrawBehind(fRecorder.getRecordingCanvas(), paint); | |
| } | |
| void onDrawRect(const SkRect& rect, const SkPaint& paint) override { | |
| fRecorder.getRecordingCanvas()->drawRect(rect, paint); | |
| } | |
| void onDrawRRect(const SkRRect& rrect, const SkPaint& paint) override { | |
| fRecorder.getRecordingCanvas()->drawRRect(rrect, paint); | |
| } | |
| void onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) override { | |
| fRecorder.getRecordingCanvas()->drawDRRect(outer, inner, paint); | |
| } | |
| void onDrawOval(const SkRect& rect, const SkPaint& paint) override { | |
| fRecorder.getRecordingCanvas()->drawOval(rect, paint); | |
| } | |
| void onDrawArc(const SkRect& rect, | |
| SkScalar startAngle, | |
| SkScalar sweepAngle, | |
| bool useCenter, | |
| const SkPaint& paint) override { | |
| fRecorder.getRecordingCanvas()->drawArc(rect, startAngle, sweepAngle, useCenter, paint); | |
| } | |
| void onDrawPath(const SkPath& path, const SkPaint& paint) override { | |
| fRecorder.getRecordingCanvas()->drawPath(path, paint); | |
| } | |
| void onDrawRegion(const SkRegion& region, const SkPaint& paint) override { | |
| fRecorder.getRecordingCanvas()->drawRegion(region, paint); | |
| } | |
| void onDrawTextBlob(const SkTextBlob* blob, | |
| SkScalar x, | |
| SkScalar y, | |
| const SkPaint& paint) override { | |
| fRecorder.getRecordingCanvas()->drawTextBlob(blob, x, y, paint); | |
| } | |
| void onDrawPatch(const SkPoint cubics[12], | |
| const SkColor colors[4], | |
| const SkPoint texCoords[4], | |
| SkBlendMode mode, | |
| const SkPaint& paint) override { | |
| fRecorder.getRecordingCanvas()->drawPatch(cubics, colors, texCoords, mode, paint); | |
| } | |
| void onDrawPoints(SkCanvas::PointMode mode, | |
| size_t count, | |
| const SkPoint pts[], | |
| const SkPaint& paint) override { | |
| fRecorder.getRecordingCanvas()->drawPoints(mode, count, pts, paint); | |
| } | |
| void onDrawImage2(const SkImage* image, | |
| SkScalar dx, | |
| SkScalar dy, | |
| const SkSamplingOptions& sampling, | |
| const SkPaint* paint) override { | |
| fRecorder.getRecordingCanvas()->drawImage(image, dx, dy, sampling, paint); | |
| } | |
| void onDrawImageRect2(const SkImage* image, | |
| const SkRect& src, | |
| const SkRect& dst, | |
| const SkSamplingOptions& sampling, | |
| const SkPaint* paint, | |
| SrcRectConstraint constraint) override { | |
| if (fNextDrawImageFromLayerID != -1) { | |
| this->recordPicCmd(); | |
| auto drawLayer = std::make_unique<DrawLayerCmd>(); | |
| drawLayer->fLayerId = fNextDrawImageFromLayerID; | |
| drawLayer->fLayerCmdCnt = fOffscreenLayers->at(fNextDrawImageFromLayerID).fCmds.size(); | |
| drawLayer->fSrcRect = src; | |
| drawLayer->fDstRect = dst; | |
| drawLayer->fSampling = sampling; | |
| drawLayer->fConstraint = constraint; | |
| if (paint) { | |
| drawLayer->fPaint.init(*paint); | |
| } | |
| fDst->fCmds.push_back(std::move(drawLayer)); | |
| fNextDrawImageFromLayerID = -1; | |
| return; | |
| } | |
| fRecorder.getRecordingCanvas()->drawImageRect(image, src, dst, sampling, paint, constraint); | |
| } | |
| void onDrawImageLattice2(const SkImage* image, | |
| const Lattice& lattice, | |
| const SkRect& dst, | |
| SkFilterMode mode, | |
| const SkPaint* paint) override { | |
| fRecorder.getRecordingCanvas()->drawImageLattice(image, lattice, dst, mode, paint); | |
| } | |
| void onDrawAtlas2(const SkImage* image, | |
| const SkRSXform rsxForms[], | |
| const SkRect src[], | |
| const SkColor colors[], | |
| int count, | |
| SkBlendMode mode, | |
| const SkSamplingOptions& sampling, | |
| const SkRect* cull, | |
| const SkPaint* paint) override { | |
| fRecorder.getRecordingCanvas()->drawAtlas(image, | |
| rsxForms, | |
| src, | |
| colors, | |
| count, | |
| mode, | |
| sampling, | |
| cull, | |
| paint); | |
| } | |
| void onDrawEdgeAAImageSet2(const ImageSetEntry imageSet[], | |
| int count, | |
| const SkPoint dstClips[], | |
| const SkMatrix preViewMatrices[], | |
| const SkSamplingOptions& sampling, | |
| const SkPaint* paint, | |
| SrcRectConstraint constraint) override { | |
| fRecorder.getRecordingCanvas()->experimental_DrawEdgeAAImageSet(imageSet, | |
| count, | |
| dstClips, | |
| preViewMatrices, | |
| sampling, | |
| paint, | |
| constraint); | |
| } | |
| #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK | |
| void onDrawEdgeAAQuad(const SkRect& rect, | |
| const SkPoint clip[4], | |
| SkCanvas::QuadAAFlags aaFlags, | |
| const SkColor4f& color, | |
| SkBlendMode mode) override {} | |
| #else | |
| void onDrawEdgeAAQuad(const SkRect& rect, | |
| const SkPoint clip[4], | |
| SkCanvas::QuadAAFlags aaFlags, | |
| const SkColor4f& color, | |
| SkBlendMode mode) override { | |
| fRecorder.getRecordingCanvas()->experimental_DrawEdgeAAQuad(rect, | |
| clip, | |
| aaFlags, | |
| color, | |
| mode); | |
| } | |
| #endif | |
| void onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) override { | |
| static constexpr char kOffscreenLayerDraw[] = "OffscreenLayerDraw"; | |
| static constexpr char kSurfaceID[] = "SurfaceID"; | |
| SkTArray<SkString> tokens; | |
| SkStrSplit(key, "|", kStrict_SkStrSplitMode, &tokens); | |
| if (tokens.size() == 2) { | |
| if (tokens[0].equals(kOffscreenLayerDraw)) { | |
| // Indicates that the next drawPicture command contains the SkPicture to render | |
| // to the layer identified by the ID. 'rect' indicates the dirty area to update | |
| // (and indicates the layer size if this command is introducing a new layer id). | |
| fNextDrawPictureToLayerID = std::stoi(tokens[1].c_str()); | |
| fNextDrawPictureToLayerClipRect = rect.roundOut(); | |
| if (fOffscreenLayers->find(fNextDrawPictureToLayerID) == fOffscreenLayers->end()) { | |
| SkASSERT(fNextDrawPictureToLayerClipRect.left() == 0 && | |
| fNextDrawPictureToLayerClipRect.top() == 0); | |
| (*fOffscreenLayers)[fNextDrawPictureToLayerID].fDimensions = | |
| fNextDrawPictureToLayerClipRect.size(); | |
| } | |
| // The next draw picture will notice that fNextDrawPictureToLayerID is set and | |
| // redirect the picture to the offscreen layer. | |
| return; | |
| } else if (tokens[0].equals(kSurfaceID)) { | |
| // Indicates that the following drawImageRect should draw an offscreen layer | |
| // to this layer. | |
| fNextDrawImageFromLayerID = std::stoi(tokens[1].c_str()); | |
| return; | |
| } | |
| } | |
| } | |
| void onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) override { | |
| fRecorder.getRecordingCanvas()->private_draw_shadow_rec(path, rec); | |
| } | |
| void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override { | |
| fRecorder.getRecordingCanvas()->drawDrawable(drawable, matrix); | |
| } | |
| void onDrawPicture(const SkPicture* picture, | |
| const SkMatrix* matrix, | |
| const SkPaint* paint) override { | |
| if (fNextDrawPictureToLayerID != -1) { | |
| SkASSERT(!matrix); | |
| SkASSERT(!paint); | |
| LayerCmds* layer = &fOffscreenLayers->at(fNextDrawPictureToLayerID); | |
| CmdRecordCanvas sc(layer, fOffscreenLayers, &fNextDrawPictureToLayerClipRect); | |
| picture->playback(&sc); | |
| fNextDrawPictureToLayerID = -1; | |
| fNextDrawPictureToLayerClipRect = SkIRect::MakeEmpty(); | |
| return; | |
| } | |
| if (paint) { | |
| this->saveLayer(nullptr, paint); | |
| } | |
| if (matrix) { | |
| this->save(); | |
| this->concat(*matrix); | |
| } | |
| picture->playback(this); | |
| if (matrix) { | |
| this->restore(); | |
| } | |
| if (paint) { | |
| this->restore(); | |
| } | |
| fRecorder.getRecordingCanvas()->drawPicture(picture, matrix, paint); | |
| } | |
| private: | |
| void recordPicCmd() { | |
| auto cmd = std::make_unique<PicCmd>(); | |
| cmd->fContent = fRecorder.finishRecordingAsPicture(); | |
| cmd->fClipRect = fClipRect; | |
| if (cmd->fContent) { | |
| fDst->fCmds.push_back(std::move(cmd)); | |
| } | |
| // Set up to accumulate again. | |
| fRecorder.beginRecording(SkRect::Make(fDst->fDimensions)); | |
| } | |
| SkPictureRecorder fRecorder; // accumulates draws until we draw an offscreen into this layer. | |
| LayerCmds* fDst = nullptr; | |
| SkIRect fClipRect = SkIRect::MakeEmpty(); | |
| int fNextDrawPictureToLayerID = -1; | |
| SkIRect fNextDrawPictureToLayerClipRect = SkIRect::MakeEmpty(); | |
| int fNextDrawImageFromLayerID = -1; | |
| LayerMap* fOffscreenLayers = nullptr; | |
| }; | |
| /////////////////////////////////////////////////////////////////////////////// | |
| std::unique_ptr<MSKPPlayer> MSKPPlayer::Make(SkStreamSeekable* stream) { | |
| auto deserialContext = std::make_unique<SkSharingDeserialContext>(); | |
| SkDeserialProcs procs; | |
| procs.fImageProc = SkSharingDeserialContext::deserializeImage; | |
| procs.fImageCtx = deserialContext.get(); | |
| int pageCount = SkMultiPictureDocumentReadPageCount(stream); | |
| if (!pageCount) { | |
| return nullptr; | |
| } | |
| std::vector<SkDocumentPage> pages(pageCount); | |
| if (!SkMultiPictureDocumentRead(stream, pages.data(), pageCount, &procs)) { | |
| return nullptr; | |
| } | |
| std::unique_ptr<MSKPPlayer> result(new MSKPPlayer); | |
| result->fRootLayers.reserve(pages.size()); | |
| for (const auto& page : pages) { | |
| SkISize dims = {SkScalarCeilToInt(page.fSize.width()), | |
| SkScalarCeilToInt(page.fSize.height())}; | |
| result->fRootLayers.emplace_back(); | |
| result->fRootLayers.back().fDimensions = dims; | |
| result->fMaxDimensions.fWidth = std::max(dims.width() , result->fMaxDimensions.width() ); | |
| result->fMaxDimensions.fHeight = std::max(dims.height(), result->fMaxDimensions.height()); | |
| CmdRecordCanvas sc(&result->fRootLayers.back(), &result->fOffscreenLayers); | |
| page.fPicture->playback(&sc); | |
| } | |
| return result; | |
| } | |
| MSKPPlayer::~MSKPPlayer() = default; | |
| SkISize MSKPPlayer::frameDimensions(int i) const { | |
| if (i < 0 || i >= this->numFrames()) { | |
| return {-1, -1}; | |
| } | |
| return fRootLayers[i].fDimensions; | |
| } | |
| bool MSKPPlayer::playFrame(SkCanvas* canvas, int i) { | |
| if (i < 0 || i >= this->numFrames()) { | |
| return false; | |
| } | |
| // Find the first offscreen layer that has a valid surface. If it's recording context | |
| // differs from the passed canvas's then reset all the layers. Playback will | |
| // automatically allocate new surfaces for offscreen layers as they're encountered. | |
| for (const auto& ols : fOffscreenLayerStates) { | |
| const LayerState& state = ols.second; | |
| if (state.fSurface) { | |
| if (state.fSurface->recordingContext() != canvas->recordingContext()) { | |
| this->resetLayers(); | |
| } | |
| break; | |
| } | |
| } | |
| // Replay all the commands for this frame to the caller's canvas. | |
| const LayerCmds& layer = fRootLayers[i]; | |
| for (const auto& cmd : layer.fCmds) { | |
| cmd->draw(canvas, fOffscreenLayers, &fOffscreenLayerStates); | |
| } | |
| return true; | |
| } | |
| sk_sp<SkSurface> MSKPPlayer::MakeSurfaceForLayer(const LayerCmds& layer, SkCanvas* rootCanvas) { | |
| // Assume layer has same surface props and info as this (mskp doesn't currently record this | |
| // data). | |
| SkSurfaceProps props; | |
| rootCanvas->getProps(&props); | |
| return rootCanvas->makeSurface(rootCanvas->imageInfo().makeDimensions(layer.fDimensions), | |
| &props); | |
| } | |
| void MSKPPlayer::resetLayers() { fOffscreenLayerStates.clear(); } | |
| void MSKPPlayer::rewindLayers() { | |
| for (auto& [id, state] : fOffscreenLayerStates) { | |
| state.fCurrCmd = -1; | |
| } | |
| } | |
| void MSKPPlayer::allocateLayers(SkCanvas* canvas) { | |
| // Iterate over layers not states as states are lazily created in playback but here we want to | |
| // create any that don't already exist. | |
| for (auto& [id, layer] : fOffscreenLayers) { | |
| LayerState& state = fOffscreenLayerStates[id]; | |
| if (!state.fSurface || state.fSurface->recordingContext() != canvas->recordingContext()) { | |
| state.fCurrCmd = -1; | |
| state.fSurface = MakeSurfaceForLayer(fOffscreenLayers[id], canvas); | |
| } | |
| } | |
| } | |
| std::vector<int> MSKPPlayer::layerIDs(int frame) const { | |
| std::vector<int> result; | |
| if (frame < 0) { | |
| result.reserve(fOffscreenLayers.size()); | |
| for (auto& [id, _] : fOffscreenLayers) { | |
| result.push_back(id); | |
| } | |
| return result; | |
| } | |
| if (frame < static_cast<int>(fRootLayers.size())) { | |
| this->collectReferencedLayers(fRootLayers[frame], &result); | |
| } | |
| return result; | |
| } | |
| sk_sp<SkImage> MSKPPlayer::layerSnapshot(int layerID) const { | |
| auto iter = fOffscreenLayerStates.find(layerID); | |
| if (iter == fOffscreenLayerStates.end() || !iter->second.fSurface) { | |
| return nullptr; | |
| } | |
| return iter->second.fSurface->makeImageSnapshot(); | |
| } | |
| void MSKPPlayer::collectReferencedLayers(const LayerCmds& layer, std::vector<int>* out) const { | |
| for (const auto& cmd : layer.fCmds) { | |
| if (int id = cmd->layerID(); id >= 0) { | |
| // Linear, but we'd need to have a lot of layers to actually care. | |
| if (std::find(out->begin(), out->end(), id) == out->end()) { | |
| out->push_back(id); | |
| auto iter = fOffscreenLayers.find(id); | |
| SkASSERT(iter != fOffscreenLayers.end()); | |
| this->collectReferencedLayers(iter->second, out); | |
| } | |
| } | |
| } | |
| } |