• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 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 "tools/MSKPPlayer.h"
9 
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkCanvasVirtualEnforcer.h"
12 #include "include/core/SkPicture.h"
13 #include "include/core/SkPictureRecorder.h"
14 #include "include/core/SkSurface.h"
15 #include "include/gpu/GrDirectContext.h"
16 #include "include/private/SkTArray.h"
17 #include "include/utils/SkNoDrawCanvas.h"
18 #include "src/core/SkCanvasPriv.h"
19 #include "src/core/SkTLazy.h"
20 #include "src/utils/SkMultiPictureDocument.h"
21 #include "tools/SkSharingProc.h"
22 
23 ///////////////////////////////////////////////////////////////////////////////
24 
25 // Base Cmd struct.
26 struct MSKPPlayer::Cmd {
27     virtual ~Cmd() = default;
28     virtual bool isFullRedraw(SkCanvas*) const = 0;
29     virtual void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const = 0;
30     // If this command draws another layer, return it's ID. Otherwise, -1.
layerIDMSKPPlayer::Cmd31     virtual int layerID() const { return -1; }
32 };
33 
34 // Draws a SkPicture.
35 struct MSKPPlayer::PicCmd : Cmd {
36     sk_sp<SkPicture> fContent;
37     SkIRect fClipRect = SkIRect::MakeEmpty(); // clip for picture (no clip if empty).
38 
isFullRedrawMSKPPlayer::PicCmd39     bool isFullRedraw(SkCanvas* canvas) const override {
40         if (fClipRect.isEmpty()) {
41             return false;
42         }
43         return fClipRect.contains(SkIRect::MakeSize(canvas->getBaseLayerSize()));
44     }
45 
drawMSKPPlayer::PicCmd46     void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const override {
47         if (!fClipRect.isEmpty()) {
48             canvas->save();
49             canvas->clipIRect(fClipRect);
50         }
51         canvas->drawPicture(fContent.get());
52         if (!fClipRect.isEmpty()) {
53             canvas->restore();
54         }
55     }
56 };
57 
58 // Draws another layer. Stores the ID of the layer to draw and what command index on that
59 // layer should be current when the layer is drawn. The layer contents are updated to the
60 // stored command index before the layer is drawn.
61 struct MSKPPlayer::DrawLayerCmd : Cmd {
62     int                         fLayerId;
63     size_t                      fLayerCmdCnt;
64     SkRect                      fSrcRect;
65     SkRect                      fDstRect;
66     SkSamplingOptions           fSampling;
67     SkCanvas::SrcRectConstraint fConstraint;
68     SkTLazy<SkPaint>            fPaint;
69 
isFullRedrawMSKPPlayer::DrawLayerCmd70     bool isFullRedraw(SkCanvas* canvas) const override { return false; }
71     void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const override;
layerIDMSKPPlayer::DrawLayerCmd72     int layerID() const override { return fLayerId; }
73 };
74 
draw(SkCanvas * canvas,const LayerMap & layerMap,LayerStateMap * layerStateMap) const75 void MSKPPlayer::DrawLayerCmd::draw(SkCanvas* canvas,
76                                     const LayerMap& layerMap,
77                                     LayerStateMap* layerStateMap) const {
78     const Layer& layer = layerMap.at(fLayerId);
79     LayerState* layerState = &(*layerStateMap)[fLayerId];
80     if (!layerState->fSurface) {
81         layerState->fCurrCmd = 0;
82         layerState->fSurface = MSKPPlayer::MakeSurfaceForLayer(layer, canvas);
83         if (!layerState->fSurface) {
84             SkDebugf("Couldn't create surface for layer");
85             return;
86         }
87     }
88     size_t cmd = layerState->fCurrCmd;
89     if (cmd > fLayerCmdCnt) {
90         // If the layer contains contents from later commands then replay from the beginning.
91         cmd = 0;
92     }
93     SkCanvas* layerCanvas = layerState->fSurface->getCanvas();
94     // Check if there is a full redraw between cmd and fLayerCmdCnt and if so jump to it and ensure
95     // we clear the canvas if starting from a full redraw.
96     for (size_t checkCmd = fLayerCmdCnt - 1; checkCmd > cmd; --checkCmd) {
97         if (layer.fCmds[checkCmd]->isFullRedraw(layerCanvas)) {
98             cmd = checkCmd;
99             break;
100         }
101     }
102     for (; cmd < fLayerCmdCnt; ++cmd) {
103         if (cmd == 0 || layer.fCmds[cmd]->isFullRedraw(layerCanvas)) {
104             layerState->fSurface->getCanvas()->clear(SK_ColorTRANSPARENT);
105         }
106         layer.fCmds[cmd]->draw(layerCanvas, layerMap, layerStateMap);
107     }
108     layerState->fCurrCmd = fLayerCmdCnt;
109     const SkPaint* paint = fPaint.isValid() ? fPaint.get() : nullptr;
110     canvas->drawImageRect(layerState->fSurface->makeImageSnapshot(),
111                           fSrcRect,
112                           fDstRect,
113                           fSampling,
114                           paint,
115                           fConstraint);
116 }
117 
118 ///////////////////////////////////////////////////////////////////////////////
119 
120 class MSKPPlayer::CmdRecordCanvas : public SkCanvasVirtualEnforcer<SkCanvas> {
121 public:
CmdRecordCanvas(Layer * dst,LayerMap * offscreenLayers,const SkIRect * clipRect=nullptr)122     CmdRecordCanvas(Layer* dst, LayerMap* offscreenLayers, const SkIRect* clipRect = nullptr)
123             : fDst(dst), fOffscreenLayers(offscreenLayers) {
124         if (clipRect) {
125             fClipRect = *clipRect;
126         }
127         fRecorder.beginRecording(SkRect::Make(dst->fDimensions));
128     }
~CmdRecordCanvas()129     ~CmdRecordCanvas() override { this->recordPicCmd(); }
130 
131 protected:
onDrawPaint(const SkPaint & paint)132     void onDrawPaint(const SkPaint& paint) override {
133         fRecorder.getRecordingCanvas()->drawPaint(paint);
134     }
135 
onDrawBehind(const SkPaint & paint)136     void onDrawBehind(const SkPaint& paint) override {
137         SkCanvasPriv::DrawBehind(fRecorder.getRecordingCanvas(), paint);
138     }
139 
onDrawRect(const SkRect & rect,const SkPaint & paint)140     void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
141         fRecorder.getRecordingCanvas()->drawRect(rect, paint);
142     }
143 
onDrawRRect(const SkRRect & rrect,const SkPaint & paint)144     void onDrawRRect(const SkRRect& rrect, const SkPaint& paint) override {
145         fRecorder.getRecordingCanvas()->drawRRect(rrect, paint);
146     }
147 
onDrawDRRect(const SkRRect & outer,const SkRRect & inner,const SkPaint & paint)148     void onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) override {
149         fRecorder.getRecordingCanvas()->drawDRRect(outer, inner, paint);
150     }
151 
onDrawOval(const SkRect & rect,const SkPaint & paint)152     void onDrawOval(const SkRect& rect, const SkPaint& paint) override {
153         fRecorder.getRecordingCanvas()->drawOval(rect, paint);
154     }
155 
onDrawArc(const SkRect & rect,SkScalar startAngle,SkScalar sweepAngle,bool useCenter,const SkPaint & paint)156     void onDrawArc(const SkRect& rect,
157                    SkScalar startAngle,
158                    SkScalar sweepAngle,
159                    bool useCenter,
160                    const SkPaint& paint) override {
161         fRecorder.getRecordingCanvas()->drawArc(rect, startAngle, sweepAngle, useCenter, paint);
162     }
163 
onDrawPath(const SkPath & path,const SkPaint & paint)164     void onDrawPath(const SkPath& path, const SkPaint& paint) override {
165         fRecorder.getRecordingCanvas()->drawPath(path, paint);
166     }
167 
onDrawRegion(const SkRegion & region,const SkPaint & paint)168     void onDrawRegion(const SkRegion& region, const SkPaint& paint) override {
169         fRecorder.getRecordingCanvas()->drawRegion(region, paint);
170     }
171 
onDrawTextBlob(const SkTextBlob * blob,SkScalar x,SkScalar y,const SkPaint & paint)172     void onDrawTextBlob(const SkTextBlob* blob,
173                         SkScalar x,
174                         SkScalar y,
175                         const SkPaint& paint) override {
176         fRecorder.getRecordingCanvas()->drawTextBlob(blob, x, y, paint);
177     }
178 
onDrawPatch(const SkPoint cubics[12],const SkColor colors[4],const SkPoint texCoords[4],SkBlendMode mode,const SkPaint & paint)179     void onDrawPatch(const SkPoint cubics[12],
180                      const SkColor colors[4],
181                      const SkPoint texCoords[4],
182                      SkBlendMode mode,
183                      const SkPaint& paint) override {
184         fRecorder.getRecordingCanvas()->drawPatch(cubics, colors, texCoords, mode, paint);
185     }
186 
onDrawPoints(SkCanvas::PointMode mode,size_t count,const SkPoint pts[],const SkPaint & paint)187     void onDrawPoints(SkCanvas::PointMode mode,
188                       size_t count,
189                       const SkPoint pts[],
190                       const SkPaint& paint) override {
191         fRecorder.getRecordingCanvas()->drawPoints(mode, count, pts, paint);
192     }
193 
onDrawImage2(const SkImage * image,SkScalar dx,SkScalar dy,const SkSamplingOptions & sampling,const SkPaint * paint)194     void onDrawImage2(const SkImage* image,
195                       SkScalar dx,
196                       SkScalar dy,
197                       const SkSamplingOptions& sampling,
198                       const SkPaint* paint) override {
199         fRecorder.getRecordingCanvas()->drawImage(image, dx, dy, sampling, paint);
200     }
201 
onDrawImageRect2(const SkImage * image,const SkRect & src,const SkRect & dst,const SkSamplingOptions & sampling,const SkPaint * paint,SrcRectConstraint constraint)202     void onDrawImageRect2(const SkImage* image,
203                           const SkRect& src,
204                           const SkRect& dst,
205                           const SkSamplingOptions& sampling,
206                           const SkPaint* paint,
207                           SrcRectConstraint constraint) override {
208         if (fNextDrawImageFromLayerID != -1) {
209             this->recordPicCmd();
210             auto drawLayer = std::make_unique<DrawLayerCmd>();
211             drawLayer->fLayerId = fNextDrawImageFromLayerID;
212             drawLayer->fLayerCmdCnt = fOffscreenLayers->at(fNextDrawImageFromLayerID).fCmds.size();
213             drawLayer->fSrcRect = src;
214             drawLayer->fDstRect = dst;
215             drawLayer->fSampling = sampling;
216             drawLayer->fConstraint = constraint;
217             if (paint) {
218                 drawLayer->fPaint.init(*paint);
219             }
220             fDst->fCmds.push_back(std::move(drawLayer));
221             fNextDrawImageFromLayerID = -1;
222             return;
223         }
224         fRecorder.getRecordingCanvas()->drawImageRect(image, src, dst, sampling, paint, constraint);
225     }
226 
onDrawImageLattice2(const SkImage * image,const Lattice & lattice,const SkRect & dst,SkFilterMode mode,const SkPaint * paint)227     void onDrawImageLattice2(const SkImage* image,
228                              const Lattice& lattice,
229                              const SkRect& dst,
230                              SkFilterMode mode,
231                              const SkPaint* paint) override {
232         fRecorder.getRecordingCanvas()->drawImageLattice(image, lattice, dst, mode, paint);
233     }
234 
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)235     void onDrawAtlas2(const SkImage* image,
236                       const SkRSXform rsxForms[],
237                       const SkRect src[],
238                       const SkColor colors[],
239                       int count,
240                       SkBlendMode mode,
241                       const SkSamplingOptions& sampling,
242                       const SkRect* cull,
243                       const SkPaint* paint) override {
244         fRecorder.getRecordingCanvas()->drawAtlas(image,
245                                                   rsxForms,
246                                                   src,
247                                                   colors,
248                                                   count,
249                                                   mode,
250                                                   sampling,
251                                                   cull,
252                                                   paint);
253     }
254 
onDrawEdgeAAImageSet2(const ImageSetEntry imageSet[],int count,const SkPoint dstClips[],const SkMatrix preViewMatrices[],const SkSamplingOptions & sampling,const SkPaint * paint,SrcRectConstraint constraint)255     void onDrawEdgeAAImageSet2(const ImageSetEntry imageSet[],
256                                int count,
257                                const SkPoint dstClips[],
258                                const SkMatrix preViewMatrices[],
259                                const SkSamplingOptions& sampling,
260                                const SkPaint* paint,
261                                SrcRectConstraint constraint) override {
262         fRecorder.getRecordingCanvas()->experimental_DrawEdgeAAImageSet(imageSet,
263                                                                         count,
264                                                                         dstClips,
265                                                                         preViewMatrices,
266                                                                         sampling,
267                                                                         paint,
268                                                                         constraint);
269     }
270 
271 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
onDrawEdgeAAQuad(const SkRect & rect,const SkPoint clip[4],SkCanvas::QuadAAFlags aaFlags,const SkColor4f & color,SkBlendMode mode)272     void onDrawEdgeAAQuad(const SkRect& rect,
273                           const SkPoint clip[4],
274                           SkCanvas::QuadAAFlags aaFlags,
275                           const SkColor4f& color,
276                           SkBlendMode mode) override {}
277 #else
onDrawEdgeAAQuad(const SkRect & rect,const SkPoint clip[4],SkCanvas::QuadAAFlags aaFlags,const SkColor4f & color,SkBlendMode mode)278     void onDrawEdgeAAQuad(const SkRect& rect,
279                           const SkPoint clip[4],
280                           SkCanvas::QuadAAFlags aaFlags,
281                           const SkColor4f& color,
282                           SkBlendMode mode) override {
283         fRecorder.getRecordingCanvas()->experimental_DrawEdgeAAQuad(rect,
284                                                                     clip,
285                                                                     aaFlags,
286                                                                     color,
287                                                                     mode);
288     }
289 #endif
290 
onDrawAnnotation(const SkRect & rect,const char key[],SkData * value)291     void onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) override {
292         static constexpr char kOffscreenLayerDraw[] = "OffscreenLayerDraw";
293         static constexpr char kSurfaceID[] = "SurfaceID";
294         SkTArray<SkString> tokens;
295         SkStrSplit(key, "|", kStrict_SkStrSplitMode, &tokens);
296         if (tokens.size() == 2) {
297             if (tokens[0].equals(kOffscreenLayerDraw)) {
298                 // Indicates that the next drawPicture command contains the SkPicture to render
299                 // to the layer identified by the ID. 'rect' indicates the dirty area to update
300                 // (and indicates the layer size if this command is introducing a new layer id).
301                 fNextDrawPictureToLayerID = std::stoi(tokens[1].c_str());
302                 fNextDrawPictureToLayerClipRect = rect.roundOut();
303                 if (fOffscreenLayers->find(fNextDrawPictureToLayerID) == fOffscreenLayers->end()) {
304                     SkASSERT(fNextDrawPictureToLayerClipRect.left() == 0 &&
305                              fNextDrawPictureToLayerClipRect.top()  == 0);
306                     (*fOffscreenLayers)[fNextDrawPictureToLayerID].fDimensions =
307                             fNextDrawPictureToLayerClipRect.size();
308                 }
309                 // The next draw picture will notice that fNextDrawPictureToLayerID is set and
310                 // redirect the picture to the offscreen layer.
311                 return;
312             } else if (tokens[0].equals(kSurfaceID)) {
313                 // Indicates that the following drawImageRect should draw an offscreen layer
314                 // to this layer.
315                 fNextDrawImageFromLayerID = std::stoi(tokens[1].c_str());
316                 return;
317             }
318         }
319     }
320 
onDrawShadowRec(const SkPath & path,const SkDrawShadowRec & rec)321     void onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) override {
322         fRecorder.getRecordingCanvas()->private_draw_shadow_rec(path, rec);
323     }
324 
onDrawDrawable(SkDrawable * drawable,const SkMatrix * matrix)325     void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override {
326         fRecorder.getRecordingCanvas()->drawDrawable(drawable, matrix);
327     }
328 
onDrawPicture(const SkPicture * picture,const SkMatrix * matrix,const SkPaint * paint)329     void onDrawPicture(const SkPicture* picture,
330                        const SkMatrix* matrix,
331                        const SkPaint* paint) override {
332         if (fNextDrawPictureToLayerID != -1) {
333             SkASSERT(!matrix);
334             SkASSERT(!paint);
335             Layer* layer = &fOffscreenLayers->at(fNextDrawPictureToLayerID);
336             CmdRecordCanvas sc(layer, fOffscreenLayers, &fNextDrawPictureToLayerClipRect);
337             picture->playback(&sc);
338             fNextDrawPictureToLayerID = -1;
339             fNextDrawPictureToLayerClipRect = SkIRect::MakeEmpty();
340             return;
341         }
342         if (paint) {
343             this->saveLayer(nullptr, paint);
344         }
345         if (matrix) {
346             this->save();
347             this->concat(*matrix);
348         }
349 
350         picture->playback(this);
351 
352         if (matrix) {
353             this->restore();
354         }
355         if (paint) {
356             this->restore();
357         }
358         fRecorder.getRecordingCanvas()->drawPicture(picture, matrix, paint);
359     }
360 
361 private:
recordPicCmd()362     void recordPicCmd() {
363         auto cmd = std::make_unique<PicCmd>();
364         cmd->fContent = fRecorder.finishRecordingAsPicture();
365         cmd->fClipRect = fClipRect;
366         if (cmd->fContent) {
367             fDst->fCmds.push_back(std::move(cmd));
368         }
369         // Set up to accumulate again.
370         fRecorder.beginRecording(SkRect::Make(fDst->fDimensions));
371     }
372 
373     SkPictureRecorder fRecorder; // accumulates draws until we draw an offscreen into this layer.
374     Layer*            fDst                            = nullptr;
375     SkIRect           fClipRect                       = SkIRect::MakeEmpty();
376     int               fNextDrawPictureToLayerID       = -1;
377     SkIRect           fNextDrawPictureToLayerClipRect = SkIRect::MakeEmpty();
378     int               fNextDrawImageFromLayerID       = -1;
379     LayerMap*         fOffscreenLayers                = nullptr;
380 };
381 
382 ///////////////////////////////////////////////////////////////////////////////
383 
Make(SkStreamSeekable * stream)384 std::unique_ptr<MSKPPlayer> MSKPPlayer::Make(SkStreamSeekable* stream) {
385     auto deserialContext = std::make_unique<SkSharingDeserialContext>();
386     SkDeserialProcs procs;
387     procs.fImageProc = SkSharingDeserialContext::deserializeImage;
388     procs.fImageCtx = deserialContext.get();
389 
390     int pageCount = SkMultiPictureDocumentReadPageCount(stream);
391     if (!pageCount) {
392         return nullptr;
393     }
394     std::vector<SkDocumentPage> pages(pageCount);
395     if (!SkMultiPictureDocumentRead(stream, pages.data(), pageCount, &procs)) {
396         return nullptr;
397     }
398     std::unique_ptr<MSKPPlayer> result(new MSKPPlayer);
399     result->fRootLayers.reserve(pages.size());
400     for (const auto& page : pages) {
401         SkISize dims = {SkScalarCeilToInt(page.fSize.width()),
402                         SkScalarCeilToInt(page.fSize.height())};
403         result->fRootLayers.emplace_back();
404         result->fRootLayers.back().fDimensions = dims;
405         result->fMaxDimensions.fWidth  = std::max(dims.width() , result->fMaxDimensions.width() );
406         result->fMaxDimensions.fHeight = std::max(dims.height(), result->fMaxDimensions.height());
407         CmdRecordCanvas sc(&result->fRootLayers.back(), &result->fOffscreenLayers);
408         page.fPicture->playback(&sc);
409     }
410     return result;
411 }
412 
413 MSKPPlayer::~MSKPPlayer() = default;
414 
frameDimensions(int i) const415 SkISize MSKPPlayer::frameDimensions(int i) const {
416     if (i < 0 || i >= this->numFrames()) {
417         return {-1, -1};
418     }
419     return fRootLayers[i].fDimensions;
420 }
421 
playFrame(SkCanvas * canvas,int i)422 bool MSKPPlayer::playFrame(SkCanvas* canvas, int i) {
423     if (i < 0 || i >= this->numFrames()) {
424         return false;
425     }
426 
427     // Find the first offscreen layer that has a valid surface. If it's recording context
428     // differs from the passed canvas's then reset all the layers. Playback will
429     // automatically allocate new surfaces for offscreen layers as they're encountered.
430     for (const auto& ols : fOffscreenLayerStates) {
431         const LayerState& state = ols.second;
432         if (state.fSurface) {
433             if (state.fSurface->recordingContext() != canvas->recordingContext()) {
434                 this->resetLayers();
435             }
436             break;
437         }
438     }
439 
440     // Replay all the commands for this frame to the caller's canvas.
441     const Layer& layer = fRootLayers[i];
442     for (const auto& cmd : layer.fCmds) {
443         cmd->draw(canvas, fOffscreenLayers, &fOffscreenLayerStates);
444     }
445     return true;
446 }
447 
MakeSurfaceForLayer(const Layer & layer,SkCanvas * rootCanvas)448 sk_sp<SkSurface> MSKPPlayer::MakeSurfaceForLayer(const Layer& layer, SkCanvas* rootCanvas) {
449     // Assume layer has same surface props and info as this (mskp doesn't currently record this
450     // data).
451     SkSurfaceProps props;
452     rootCanvas->getProps(&props);
453     return rootCanvas->makeSurface(rootCanvas->imageInfo().makeDimensions(layer.fDimensions),
454                                    &props);
455 }
456 
resetLayers()457 void MSKPPlayer::resetLayers() { fOffscreenLayerStates.clear(); }
458 
rewindLayers()459 void MSKPPlayer::rewindLayers() {
460     for (auto& [id, state] : fOffscreenLayerStates) {
461         state.fCurrCmd = -1;
462     }
463 }
464 
allocateLayers(SkCanvas * canvas)465 void MSKPPlayer::allocateLayers(SkCanvas* canvas) {
466     // Iterate over layers not states as states are lazily created in playback but here we want to
467     // create any that don't already exist.
468     for (auto& [id, layer] : fOffscreenLayers) {
469         LayerState& state = fOffscreenLayerStates[id];
470         if (!state.fSurface || state.fSurface->recordingContext() != canvas->recordingContext()) {
471             state.fCurrCmd = -1;
472             state.fSurface = MakeSurfaceForLayer(fOffscreenLayers[id], canvas);
473         }
474     }
475 }
476 
layerIDs(int frame) const477 std::vector<int> MSKPPlayer::layerIDs(int frame) const {
478     std::vector<int> result;
479     if (frame < 0) {
480         result.reserve(fOffscreenLayers.size());
481         for (auto& [id, _] : fOffscreenLayers) {
482             result.push_back(id);
483         }
484         return result;
485     }
486     if (frame < static_cast<int>(fRootLayers.size())) {
487         this->collectReferencedLayers(fRootLayers[frame], &result);
488     }
489     return result;
490 }
491 
layerSnapshot(int layerID) const492 sk_sp<SkImage> MSKPPlayer::layerSnapshot(int layerID) const {
493     auto iter = fOffscreenLayerStates.find(layerID);
494     if (iter == fOffscreenLayerStates.end() || !iter->second.fSurface) {
495         return nullptr;
496     }
497     return iter->second.fSurface->makeImageSnapshot();
498 }
499 
collectReferencedLayers(const Layer & layer,std::vector<int> * out) const500 void MSKPPlayer::collectReferencedLayers(const Layer& layer, std::vector<int>* out) const {
501     for (const auto& cmd : layer.fCmds) {
502         if (int id = cmd->layerID(); id >= 0) {
503             // Linear, but we'd need to have a lot of layers to actually care.
504             if (std::find(out->begin(), out->end(), id) == out->end()) {
505                 out->push_back(id);
506                 auto iter = fOffscreenLayers.find(id);
507                 SkASSERT(iter != fOffscreenLayers.end());
508                 this->collectReferencedLayers(iter->second, out);
509             }
510         }
511     }
512 }
513