skia2/tools/debugger/DebugCanvas.cpp
Michael Ludwig e4b79699ef Use SkClipOp::kIntersect instead of kReplace_ClipOp
This fixes the perf bot crashes from https://skia-review.googlesource.com/c/skia/+/317209
and removes a few other instances of kReplace_ClipOp that are equivalent to the
still supported SkClipOp::kIntersect op. Other than the benchmark case, the GPU
clip stack wasn't hitting these cases but they do need to be updated when the deprecated
ops are fully removed.

At this point, the remaining uses of deprecated clip ops are all in unit tests that
specifically test a clip stack (raster, conservative, or skclipstack) that specifically
supports expanded clip ops and the tests are testing those operations. They can remain
until we remove full support.

Bug: skia:10208
Change-Id: I5703bf43fd41b6addf329190a70c5429d5971240
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/317380
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
2020-09-16 18:37:37 +00:00

633 lines
24 KiB
C++

/*
* Copyright 2012 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
#include "include/core/SkPicture.h"
#include "include/core/SkPoint.h"
#include "include/core/SkTextBlob.h"
#include "include/utils/SkPaintFilterCanvas.h"
#include "src/core/SkCanvasPriv.h"
#include "src/core/SkClipOpPriv.h"
#include "src/core/SkRectPriv.h"
#include "src/utils/SkJSONWriter.h"
#include "tools/debugger/DebugCanvas.h"
#include "tools/debugger/DebugLayerManager.h"
#include "tools/debugger/DrawCommand.h"
#include "src/gpu/GrAuditTrail.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/GrRenderTargetContext.h"
#include <string>
#define SKDEBUGCANVAS_VERSION 1
#define SKDEBUGCANVAS_ATTRIBUTE_VERSION "version"
#define SKDEBUGCANVAS_ATTRIBUTE_COMMANDS "commands"
#define SKDEBUGCANVAS_ATTRIBUTE_AUDITTRAIL "auditTrail"
namespace {
// Constants used in Annotations by Android for keeping track of layers
static constexpr char kOffscreenLayerDraw[] = "OffscreenLayerDraw";
static constexpr char kSurfaceID[] = "SurfaceID";
static constexpr char kAndroidClip[] = "AndroidDeviceClipRestriction";
static SkPath arrowHead = SkPath::Polygon({
{ 0, 0},
{ 6, -15},
{ 0, -12},
{-6, -15},
}, true);
void drawArrow(SkCanvas* canvas, const SkPoint& a, const SkPoint& b, const SkPaint& paint) {
canvas->translate(0.5, 0.5);
canvas->drawLine(a, b, paint);
canvas->save();
canvas->translate(b.fX, b.fY);
SkScalar angle = SkScalarATan2((b.fY - a.fY), b.fX - a.fX);
canvas->rotate(angle * 180 / SK_ScalarPI - 90);
// arrow head
canvas->drawPath(arrowHead, paint);
canvas->restore();
canvas->restore();
}
} // namespace
class DebugPaintFilterCanvas : public SkPaintFilterCanvas {
public:
DebugPaintFilterCanvas(SkCanvas* canvas) : INHERITED(canvas) {}
protected:
bool onFilter(SkPaint& paint) const override {
paint.setColor(SK_ColorRED);
paint.setAlpha(0x08);
paint.setBlendMode(SkBlendMode::kSrcOver);
return true;
}
void onDrawPicture(const SkPicture* picture,
const SkMatrix* matrix,
const SkPaint* paint) override {
// We need to replay the picture onto this canvas in order to filter its internal paints.
this->SkCanvas::onDrawPicture(picture, matrix, paint);
}
private:
using INHERITED = SkPaintFilterCanvas;
};
DebugCanvas::DebugCanvas(int width, int height)
: INHERITED(width, height)
, fOverdrawViz(false)
, fClipVizColor(SK_ColorTRANSPARENT)
, fDrawGpuOpBounds(false)
, fShowAndroidClip(false)
, fShowOrigin(false)
, fnextDrawPictureLayerId(-1)
, fnextDrawImageRectLayerId(-1)
, fAndroidClip(SkRect::MakeEmpty()) {
// SkPicturePlayback uses the base-class' quickReject calls to cull clipped
// operations. This can lead to problems in the debugger which expects all
// the operations in the captured skp to appear in the debug canvas. To
// circumvent this we create a wide open clip here (an empty clip rect
// is not sufficient).
// Internally, the SkRect passed to clipRect is converted to an SkIRect and
// rounded out. The following code creates a nearly maximal rect that will
// not get collapsed by the coming conversions (Due to precision loss the
// inset has to be surprisingly large).
SkIRect largeIRect = SkRectPriv::MakeILarge();
largeIRect.inset(1024, 1024);
SkRect large = SkRect::Make(largeIRect);
#ifdef SK_DEBUG
SkASSERT(!large.roundOut().isEmpty());
#endif
// call the base class' version to avoid adding a draw command
this->INHERITED::onClipRect(large, SkClipOp::kIntersect, kHard_ClipEdgeStyle);
}
DebugCanvas::DebugCanvas(SkIRect bounds)
: DebugCanvas(bounds.width(), bounds.height()) {}
DebugCanvas::~DebugCanvas() { fCommandVector.deleteAll(); }
void DebugCanvas::addDrawCommand(DrawCommand* command) { fCommandVector.push_back(command); }
void DebugCanvas::draw(SkCanvas* canvas) {
if (!fCommandVector.isEmpty()) {
this->drawTo(canvas, fCommandVector.count() - 1);
}
}
void DebugCanvas::drawTo(SkCanvas* originalCanvas, int index, int m) {
SkASSERT(!fCommandVector.isEmpty());
SkASSERT(index < fCommandVector.count());
int saveCount = originalCanvas->save();
SkRect windowRect = SkRect::MakeWH(SkIntToScalar(originalCanvas->getBaseLayerSize().width()),
SkIntToScalar(originalCanvas->getBaseLayerSize().height()));
originalCanvas->resetMatrix();
if (!windowRect.isEmpty()) {
originalCanvas->clipRect(windowRect, kReplace_SkClipOp);
}
DebugPaintFilterCanvas filterCanvas(originalCanvas);
SkCanvas* finalCanvas = fOverdrawViz ? &filterCanvas : originalCanvas;
// If we have a GPU backend we can also visualize the op information
GrAuditTrail* at = nullptr;
if (fDrawGpuOpBounds || m != -1) {
// The audit trail must be obtained from the original canvas.
at = this->getAuditTrail(originalCanvas);
}
for (int i = 0; i <= index; i++) {
GrAuditTrail::AutoCollectOps* acb = nullptr;
if (at) {
// We need to flush any pending operations, or they might combine with commands below.
// Previous operations were not registered with the audit trail when they were
// created, so if we allow them to combine, the audit trail will fail to find them.
finalCanvas->flush();
acb = new GrAuditTrail::AutoCollectOps(at, i);
}
if (fCommandVector[i]->isVisible()) {
fCommandVector[i]->execute(finalCanvas);
}
if (at && acb) {
delete acb;
}
}
if (SkColorGetA(fClipVizColor) != 0) {
finalCanvas->save();
SkPaint clipPaint;
clipPaint.setColor(fClipVizColor);
finalCanvas->drawPaint(clipPaint);
finalCanvas->restore();
}
fMatrix = finalCanvas->getTotalMatrix();
fClip = finalCanvas->getDeviceClipBounds();
if (fShowOrigin) {
const SkPaint originXPaint = SkPaint({1.0, 0, 0, 1.0});
const SkPaint originYPaint = SkPaint({0, 1.0, 0, 1.0});
// Draw an origin cross at the origin before restoring to assist in visualizing the
// current matrix.
drawArrow(finalCanvas, {-50, 0}, {50, 0}, originXPaint);
drawArrow(finalCanvas, {0, -50}, {0, 50}, originYPaint);
}
finalCanvas->restoreToCount(saveCount);
if (fShowAndroidClip) {
// Draw visualization of android device clip restriction
SkPaint androidClipPaint;
androidClipPaint.setARGB(80, 255, 100, 0);
finalCanvas->drawRect(fAndroidClip, androidClipPaint);
}
// draw any ops if required and issue a full reset onto GrAuditTrail
if (at) {
// just in case there is global reordering, we flush the canvas before querying
// GrAuditTrail
GrAuditTrail::AutoEnable ae(at);
finalCanvas->flush();
// we pick three colorblind-safe colors, 75% alpha
static const SkColor kTotalBounds = SkColorSetARGB(0xC0, 0x6A, 0x3D, 0x9A);
static const SkColor kCommandOpBounds = SkColorSetARGB(0xC0, 0xE3, 0x1A, 0x1C);
static const SkColor kOtherOpBounds = SkColorSetARGB(0xC0, 0xFF, 0x7F, 0x00);
// get the render target of the top device (from the original canvas) so we can ignore ops
// drawn offscreen
GrRenderTargetContext* rtc =
originalCanvas->internal_private_accessTopLayerRenderTargetContext();
GrSurfaceProxy::UniqueID proxyID = rtc->asSurfaceProxy()->uniqueID();
// get the bounding boxes to draw
SkTArray<GrAuditTrail::OpInfo> childrenBounds;
if (m == -1) {
at->getBoundsByClientID(&childrenBounds, index);
} else {
// the client wants us to draw the mth op
at->getBoundsByOpsTaskID(&childrenBounds.push_back(), m);
}
// Shift the rects half a pixel, so they appear as exactly 1px thick lines.
finalCanvas->save();
finalCanvas->translate(0.5, -0.5);
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(1);
for (int i = 0; i < childrenBounds.count(); i++) {
if (childrenBounds[i].fProxyUniqueID != proxyID) {
// offscreen draw, ignore for now
continue;
}
paint.setColor(kTotalBounds);
finalCanvas->drawRect(childrenBounds[i].fBounds, paint);
for (int j = 0; j < childrenBounds[i].fOps.count(); j++) {
const GrAuditTrail::OpInfo::Op& op = childrenBounds[i].fOps[j];
if (op.fClientID != index) {
paint.setColor(kOtherOpBounds);
} else {
paint.setColor(kCommandOpBounds);
}
finalCanvas->drawRect(op.fBounds, paint);
}
}
finalCanvas->restore();
}
this->cleanupAuditTrail(originalCanvas);
}
void DebugCanvas::deleteDrawCommandAt(int index) {
SkASSERT(index < fCommandVector.count());
delete fCommandVector[index];
fCommandVector.remove(index);
}
DrawCommand* DebugCanvas::getDrawCommandAt(int index) const {
SkASSERT(index < fCommandVector.count());
return fCommandVector[index];
}
GrAuditTrail* DebugCanvas::getAuditTrail(SkCanvas* canvas) {
GrAuditTrail* at = nullptr;
auto ctx = canvas->recordingContext();
if (ctx) {
at = ctx->priv().auditTrail();
}
return at;
}
void DebugCanvas::drawAndCollectOps(SkCanvas* canvas) {
GrAuditTrail* at = this->getAuditTrail(canvas);
if (at) {
// loop over all of the commands and draw them, this is to collect reordering
// information
for (int i = 0; i < this->getSize(); i++) {
GrAuditTrail::AutoCollectOps enable(at, i);
fCommandVector[i]->execute(canvas);
}
// in case there is some kind of global reordering
{
GrAuditTrail::AutoEnable ae(at);
canvas->flush();
}
}
}
void DebugCanvas::cleanupAuditTrail(SkCanvas* canvas) {
GrAuditTrail* at = this->getAuditTrail(canvas);
if (at) {
GrAuditTrail::AutoEnable ae(at);
at->fullReset();
}
}
void DebugCanvas::toJSON(SkJSONWriter& writer,
UrlDataManager& urlDataManager,
SkCanvas* canvas) {
this->drawAndCollectOps(canvas);
// now collect json
GrAuditTrail* at = this->getAuditTrail(canvas);
writer.appendS32(SKDEBUGCANVAS_ATTRIBUTE_VERSION, SKDEBUGCANVAS_VERSION);
writer.beginArray(SKDEBUGCANVAS_ATTRIBUTE_COMMANDS);
for (int i = 0; i < this->getSize(); i++) {
writer.beginObject(); // command
this->getDrawCommandAt(i)->toJSON(writer, urlDataManager);
if (at) {
writer.appendName(SKDEBUGCANVAS_ATTRIBUTE_AUDITTRAIL);
at->toJson(writer, i);
}
writer.endObject(); // command
}
writer.endArray(); // commands
this->cleanupAuditTrail(canvas);
}
void DebugCanvas::toJSONOpsTask(SkJSONWriter& writer, SkCanvas* canvas) {
this->drawAndCollectOps(canvas);
GrAuditTrail* at = this->getAuditTrail(canvas);
if (at) {
GrAuditTrail::AutoManageOpsTask enable(at);
at->toJson(writer);
} else {
writer.beginObject();
writer.endObject();
}
this->cleanupAuditTrail(canvas);
}
void DebugCanvas::setOverdrawViz(bool overdrawViz) { fOverdrawViz = overdrawViz; }
void DebugCanvas::onClipPath(const SkPath& path, SkClipOp op, ClipEdgeStyle edgeStyle) {
this->addDrawCommand(new ClipPathCommand(path, op, kSoft_ClipEdgeStyle == edgeStyle));
}
void DebugCanvas::onClipRect(const SkRect& rect, SkClipOp op, ClipEdgeStyle edgeStyle) {
this->addDrawCommand(new ClipRectCommand(rect, op, kSoft_ClipEdgeStyle == edgeStyle));
}
void DebugCanvas::onClipRRect(const SkRRect& rrect, SkClipOp op, ClipEdgeStyle edgeStyle) {
this->addDrawCommand(new ClipRRectCommand(rrect, op, kSoft_ClipEdgeStyle == edgeStyle));
}
void DebugCanvas::onClipRegion(const SkRegion& region, SkClipOp op) {
this->addDrawCommand(new ClipRegionCommand(region, op));
}
void DebugCanvas::onClipShader(sk_sp<SkShader> cs, SkClipOp op) {
this->addDrawCommand(new ClipShaderCommand(std::move(cs), op));
}
void DebugCanvas::didConcat44(const SkM44& m) {
this->addDrawCommand(new Concat44Command(m));
this->INHERITED::didConcat44(m);
}
void DebugCanvas::didScale(SkScalar x, SkScalar y) {
this->didConcat(SkMatrix::Scale(x, y));
}
void DebugCanvas::didTranslate(SkScalar x, SkScalar y) {
this->didConcat(SkMatrix::Translate(x, y));
}
void DebugCanvas::didConcat(const SkMatrix& matrix) {
this->addDrawCommand(new ConcatCommand(matrix));
this->INHERITED::didConcat(matrix);
}
void DebugCanvas::onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) {
// Parse layer-releated annotations added in SkiaPipeline.cpp and RenderNodeDrawable.cpp
// the format of the annotations is <Indicator|RenderNodeId>
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 the
// node at this id in an offscreen buffer.
fnextDrawPictureLayerId = std::stoi(tokens[1].c_str());
fnextDrawPictureDirtyRect = rect.roundOut();
return; // don't record it
} else if (tokens[0].equals(kSurfaceID)) {
// Indicates that the following drawImageRect should draw the offscreen buffer.
fnextDrawImageRectLayerId = std::stoi(tokens[1].c_str());
return; // don't record it
}
}
if (strcmp(kAndroidClip, key) == 0) {
// Store this frame's android device clip restriction for visualization later.
// This annotation stands in place of the androidFramework_setDeviceClipRestriction
// which is unrecordable.
fAndroidClip = rect;
}
this->addDrawCommand(new DrawAnnotationCommand(rect, key, sk_ref_sp(value)));
}
void DebugCanvas::onDrawImage(const SkImage* image,
SkScalar left,
SkScalar top,
const SkPaint* paint) {
this->addDrawCommand(new DrawImageCommand(image, left, top, paint));
}
void DebugCanvas::onDrawImageLattice(const SkImage* image,
const Lattice& lattice,
const SkRect& dst,
const SkPaint* paint) {
this->addDrawCommand(new DrawImageLatticeCommand(image, lattice, dst, paint));
}
void DebugCanvas::onDrawImageRect(const SkImage* image,
const SkRect* src,
const SkRect& dst,
const SkPaint* paint,
SrcRectConstraint constraint) {
if (fnextDrawImageRectLayerId != -1 && fLayerManager) {
// This drawImageRect command would have drawn the offscreen buffer for a layer.
// On Android, we recorded an SkPicture of the commands that drew to the layer.
// To render the layer as it would have looked on the frame this DebugCanvas draws, we need
// to call fLayerManager->getLayerAsImage(id). This must be done just before
// drawTo(command), since it depends on the index into the layer's commands
// (managed by fLayerManager)
// Instead of adding a DrawImageRectCommand, we need a deferred command, that when
// executed, will call drawImageRect(fLayerManager->getLayerAsImage())
this->addDrawCommand(new DrawImageRectLayerCommand(
fLayerManager, fnextDrawImageRectLayerId, fFrame, src, dst, paint, constraint));
} else {
this->addDrawCommand(new DrawImageRectCommand(image, src, dst, paint, constraint));
}
// Reset expectation so next drawImageRect is not special.
fnextDrawImageRectLayerId = -1;
}
void DebugCanvas::onDrawImageNine(const SkImage* image,
const SkIRect& center,
const SkRect& dst,
const SkPaint* paint) {
this->addDrawCommand(new DrawImageNineCommand(image, center, dst, paint));
}
void DebugCanvas::onDrawOval(const SkRect& oval, const SkPaint& paint) {
this->addDrawCommand(new DrawOvalCommand(oval, paint));
}
void DebugCanvas::onDrawArc(const SkRect& oval,
SkScalar startAngle,
SkScalar sweepAngle,
bool useCenter,
const SkPaint& paint) {
this->addDrawCommand(new DrawArcCommand(oval, startAngle, sweepAngle, useCenter, paint));
}
void DebugCanvas::onDrawPaint(const SkPaint& paint) {
this->addDrawCommand(new DrawPaintCommand(paint));
}
void DebugCanvas::onDrawBehind(const SkPaint& paint) {
this->addDrawCommand(new DrawBehindCommand(paint));
}
void DebugCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) {
this->addDrawCommand(new DrawPathCommand(path, paint));
}
void DebugCanvas::onDrawRegion(const SkRegion& region, const SkPaint& paint) {
this->addDrawCommand(new DrawRegionCommand(region, paint));
}
void DebugCanvas::onDrawPicture(const SkPicture* picture,
const SkMatrix* matrix,
const SkPaint* paint) {
if (fnextDrawPictureLayerId != -1 && fLayerManager) {
fLayerManager->storeSkPicture(fnextDrawPictureLayerId, fFrame, sk_ref_sp(picture),
fnextDrawPictureDirtyRect);
} else {
this->addDrawCommand(new BeginDrawPictureCommand(picture, matrix, paint));
SkAutoCanvasMatrixPaint acmp(this, matrix, paint, picture->cullRect());
picture->playback(this);
this->addDrawCommand(new EndDrawPictureCommand(SkToBool(matrix) || SkToBool(paint)));
}
fnextDrawPictureLayerId = -1;
}
void DebugCanvas::onDrawPoints(PointMode mode,
size_t count,
const SkPoint pts[],
const SkPaint& paint) {
this->addDrawCommand(new DrawPointsCommand(mode, count, pts, paint));
}
void DebugCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint) {
// NOTE(chudy): Messing up when renamed to DrawRect... Why?
addDrawCommand(new DrawRectCommand(rect, paint));
}
void DebugCanvas::onDrawRRect(const SkRRect& rrect, const SkPaint& paint) {
this->addDrawCommand(new DrawRRectCommand(rrect, paint));
}
void DebugCanvas::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) {
this->addDrawCommand(new DrawDRRectCommand(outer, inner, paint));
}
void DebugCanvas::onDrawTextBlob(const SkTextBlob* blob,
SkScalar x,
SkScalar y,
const SkPaint& paint) {
this->addDrawCommand(
new DrawTextBlobCommand(sk_ref_sp(const_cast<SkTextBlob*>(blob)), x, y, paint));
}
void DebugCanvas::onDrawPatch(const SkPoint cubics[12],
const SkColor colors[4],
const SkPoint texCoords[4],
SkBlendMode bmode,
const SkPaint& paint) {
this->addDrawCommand(new DrawPatchCommand(cubics, colors, texCoords, bmode, paint));
}
void DebugCanvas::onDrawVerticesObject(const SkVertices* vertices,
SkBlendMode bmode,
const SkPaint& paint) {
this->addDrawCommand(
new DrawVerticesCommand(sk_ref_sp(const_cast<SkVertices*>(vertices)), bmode, paint));
}
void DebugCanvas::onDrawAtlas(const SkImage* image,
const SkRSXform xform[],
const SkRect tex[],
const SkColor colors[],
int count,
SkBlendMode bmode,
const SkRect* cull,
const SkPaint* paint) {
this->addDrawCommand(
new DrawAtlasCommand(image, xform, tex, colors, count, bmode, cull, paint));
}
void DebugCanvas::onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) {
this->addDrawCommand(new DrawShadowCommand(path, rec));
}
void DebugCanvas::onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) {
this->addDrawCommand(new DrawDrawableCommand(drawable, matrix));
}
void DebugCanvas::onDrawEdgeAAQuad(const SkRect& rect,
const SkPoint clip[4],
QuadAAFlags aa,
const SkColor4f& color,
SkBlendMode mode) {
this->addDrawCommand(new DrawEdgeAAQuadCommand(rect, clip, aa, color, mode));
}
void DebugCanvas::onDrawEdgeAAImageSet(const ImageSetEntry set[],
int count,
const SkPoint dstClips[],
const SkMatrix preViewMatrices[],
const SkPaint* paint,
SrcRectConstraint constraint) {
this->addDrawCommand(new DrawEdgeAAImageSetCommand(
set, count, dstClips, preViewMatrices, paint, constraint));
}
void DebugCanvas::willRestore() {
this->addDrawCommand(new RestoreCommand());
this->INHERITED::willRestore();
}
void DebugCanvas::willSave() {
this->addDrawCommand(new SaveCommand());
this->INHERITED::willSave();
}
SkCanvas::SaveLayerStrategy DebugCanvas::getSaveLayerStrategy(const SaveLayerRec& rec) {
this->addDrawCommand(new SaveLayerCommand(rec));
(void)this->INHERITED::getSaveLayerStrategy(rec);
// No need for a full layer.
return kNoLayer_SaveLayerStrategy;
}
bool DebugCanvas::onDoSaveBehind(const SkRect* subset) {
// TODO
return false;
}
void DebugCanvas::didSetMatrix(const SkMatrix& matrix) {
this->addDrawCommand(new SetMatrixCommand(matrix));
this->INHERITED::didSetMatrix(matrix);
}
void DebugCanvas::toggleCommand(int index, bool toggle) {
SkASSERT(index < fCommandVector.count());
fCommandVector[index]->setVisible(toggle);
}
std::map<int, std::vector<int>> DebugCanvas::getImageIdToCommandMap(UrlDataManager& udm) const {
// map from image ids to list of commands that reference them.
std::map<int, std::vector<int>> m;
for (int i = 0; i < this->getSize(); i++) {
const DrawCommand* command = this->getDrawCommandAt(i);
int imageIndex = -1;
// this is not an exaustive list of where images can be used, they show up in paints too.
switch (command->getOpType()) {
case DrawCommand::OpType::kDrawImage_OpType: {
imageIndex = static_cast<const DrawImageCommand*>(command)->imageId(udm);
break;
}
case DrawCommand::OpType::kDrawImageRect_OpType: {
imageIndex = static_cast<const DrawImageRectCommand*>(command)->imageId(udm);
break;
}
case DrawCommand::OpType::kDrawImageNine_OpType: {
imageIndex = static_cast<const DrawImageNineCommand*>(command)->imageId(udm);
break;
}
case DrawCommand::OpType::kDrawImageLattice_OpType: {
imageIndex = static_cast<const DrawImageLatticeCommand*>(command)->imageId(udm);
break;
}
default: break;
}
if (imageIndex >= 0) {
m[imageIndex].push_back(i);
}
}
return m;
}