Test YUV images in DDL

The main two CLs calved off (and landed independently) from this CL are:

https://skia-review.googlesource.com/c/skia/+/156761 (Add SkImage_Gpu::MakePromiseYUVATexture)
  -- adds internal place holder for YUVA promise images (only returns Y channel)

https://skia-review.googlesource.com/c/skia/+/156140 (Add SkImage_Base API to access planar data)
  -- adds ability to grab planes for testing purposes (not externally visible)

Bug: skia:7903
Bug: skia:8424
Change-Id: Id0f2f84851dacc66c2c266a30cafa0b628b12eb4
Reviewed-on: https://skia-review.googlesource.com/151983
Reviewed-by: Greg Daniel <egdaniel@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
This commit is contained in:
Robert Phillips 2018-09-27 14:26:47 -04:00 committed by Skia Commit-Bot
parent bba2921c19
commit e8e2bb1384
9 changed files with 356 additions and 68 deletions

View File

@ -91,6 +91,7 @@ skia_core_sources = [
"$_src/core/SkData.cpp",
"$_src/core/SkDataTable.cpp",
"$_src/core/SkDebug.cpp",
"$_src/core/SkDeferredDisplayListPriv.h",
"$_src/core/SkDeferredDisplayList.cpp",
"$_src/core/SkDeferredDisplayListRecorder.cpp",
"$_src/core/SkDeque.cpp",

View File

@ -22,6 +22,7 @@ class GrContext;
class SkCanvas;
class SkImage;
class SkSurface;
struct SkYUVAIndex;
/*
* This class is intended to be used as:
@ -76,7 +77,7 @@ public:
In other words we will never call textureFulfillProc or textureReleaseProc multiple times
for the same textureContext before calling the other.
We we call the promiseDoneProc when we will no longer call the textureFulfillProc again. We
We call the promiseDoneProc when we will no longer call the textureFulfillProc again. We
pass in the textureContext as a parameter to the promiseDoneProc. We also guarantee that
there will be no outstanding textureReleaseProcs that still need to be called when we call
the textureDoneProc. Thus when the textureDoneProc gets called the client is able to cleanup
@ -115,6 +116,24 @@ public:
PromiseDoneProc promiseDoneProc,
TextureContext textureContext);
/**
This entry point operates the same as 'makePromiseTexture' except that its
textureFulfillProc can be called up to four times to fetch the required YUVA
planes (passing a different textureContext to each call). So, if the 'yuvaIndices'
indicate that only the first two backend textures are used, 'textureFulfillProc' will
be called with the first two 'textureContexts'.
*/
sk_sp<SkImage> makeYUVAPromiseTexture(SkYUVColorSpace yuvColorSpace,
const GrBackendFormat yuvaFormats[],
const SkYUVAIndex yuvaIndices[4],
int imageWidth,
int imageHeight,
GrSurfaceOrigin imageOrigin,
sk_sp<SkColorSpace> imageColorSpace,
TextureFulfillProc textureFulfillProc,
TextureReleaseProc textureReleaseProc,
PromiseDoneProc promiseDoneProc,
TextureContext textureContexts[]);
private:
bool init();

View File

@ -17,8 +17,8 @@
#include <map>
#endif
class SkDeferredDisplayListPriv;
class SkSurface;
/*
* This class contains pre-processed gpu operations that can be replayed into
* an SkSurface via draw(SkDeferredDisplayList*).
@ -51,9 +51,14 @@ public:
return fCharacterization;
}
// Provides access to functions that aren't part of the public API.
SkDeferredDisplayListPriv priv();
const SkDeferredDisplayListPriv priv() const;
private:
friend class GrDrawingManager; // for access to 'fOpLists' and 'fLazyProxyData'
friend class SkDeferredDisplayListRecorder; // for access to 'fLazyProxyData'
friend class SkDeferredDisplayListPriv;
const SkSurfaceCharacterization fCharacterization;

View File

@ -0,0 +1,48 @@
/*
* Copyright 2018 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkDeferredDisplayListPriv_DEFINED
#define SkDeferredDisplayListPriv_DEFINED
#include "SkDeferredDisplayList.h"
/** Class that adds methods to SkDeferredDisplayList that are only intended for use internal to Skia.
This class is purely a privileged window into SkDeferredDisplayList. It should never have
additional data members or virtual methods. */
class SkDeferredDisplayListPriv {
public:
int numOpLists() const {
#if SK_SUPPORT_GPU
return fDDL->fOpLists.count();
#else
return 0;
#endif
}
private:
explicit SkDeferredDisplayListPriv(SkDeferredDisplayList* ddl) : fDDL(ddl) {}
SkDeferredDisplayListPriv(const SkDeferredDisplayListPriv&); // unimpl
SkDeferredDisplayListPriv& operator=(const SkDeferredDisplayListPriv&); // unimpl
// No taking addresses of this type.
const SkDeferredDisplayListPriv* operator&() const;
SkDeferredDisplayListPriv* operator&();
SkDeferredDisplayList* fDDL;
friend class SkDeferredDisplayList; // to construct/copy this type.
};
inline SkDeferredDisplayListPriv SkDeferredDisplayList::priv() {
return SkDeferredDisplayListPriv(this);
}
inline const SkDeferredDisplayListPriv SkDeferredDisplayList::priv () const {
return SkDeferredDisplayListPriv(const_cast<SkDeferredDisplayList*>(this));
}
#endif

View File

@ -38,6 +38,21 @@ sk_sp<SkImage> SkDeferredDisplayListRecorder::makePromiseTexture(
return nullptr;
}
sk_sp<SkImage> SkDeferredDisplayListRecorder::makeYUVAPromiseTexture(
SkYUVColorSpace yuvColorSpace,
const GrBackendFormat yuvaFormats[],
const SkYUVAIndex yuvaIndices[4],
int imageWidth,
int imageHeight,
GrSurfaceOrigin imageOrigin,
sk_sp<SkColorSpace> imageColorSpace,
TextureFulfillProc textureFulfillProc,
TextureReleaseProc textureReleaseProc,
PromiseDoneProc promiseDoneProc,
TextureContext textureContexts[]) {
return nullptr;
}
#else
#include "GrContextPriv.h"
@ -211,4 +226,34 @@ sk_sp<SkImage> SkDeferredDisplayListRecorder::makePromiseTexture(
textureContext);
}
sk_sp<SkImage> SkDeferredDisplayListRecorder::makeYUVAPromiseTexture(
SkYUVColorSpace yuvColorSpace,
const GrBackendFormat yuvaFormats[],
const SkYUVAIndex yuvaIndices[4],
int imageWidth,
int imageHeight,
GrSurfaceOrigin imageOrigin,
sk_sp<SkColorSpace> imageColorSpace,
TextureFulfillProc textureFulfillProc,
TextureReleaseProc textureReleaseProc,
PromiseDoneProc promiseDoneProc,
TextureContext textureContexts[]) {
if (!fContext) {
return nullptr;
}
return SkImage_Gpu::MakePromiseYUVATexture(fContext.get(),
yuvColorSpace,
yuvaFormats,
yuvaIndices,
imageWidth,
imageHeight,
imageOrigin,
std::move(imageColorSpace),
textureFulfillProc,
textureReleaseProc,
promiseDoneProc,
textureContexts);
}
#endif

View File

@ -105,7 +105,7 @@ enum SkImageSourceChannel {
/** Describes the alpha channel; */
kA_SkImageSourceChannel,
/** Describes the alpha channel; */
/** Utility value */
kLastEnum_SkImageSourceChannel = kA_SkImageSourceChannel,
};

View File

@ -10,7 +10,11 @@
#include "GrContext.h"
#include "GrContextPriv.h"
#include "GrGpu.h"
#include "SkCachedData.h"
#include "SkDeferredDisplayListRecorder.h"
#include "SkImage_Base.h"
#include "SkImagePriv.h"
#include "SkYUVSizeInfo.h"
DDLPromiseImageHelper::PromiseImageCallbackContext::~PromiseImageCallbackContext() {
GrGpu* gpu = fContext->contextPriv().getGpu();
@ -26,6 +30,8 @@ const GrCaps* DDLPromiseImageHelper::PromiseImageCallbackContext::caps() const {
///////////////////////////////////////////////////////////////////////////////////////////////////
DDLPromiseImageHelper::~DDLPromiseImageHelper() {}
sk_sp<SkData> DDLPromiseImageHelper::deflateSKP(const SkPicture* inputPicture) {
SkSerialProcs procs;
@ -50,23 +56,45 @@ void DDLPromiseImageHelper::uploadAllToGPU(GrContext* context) {
SkASSERT(gpu);
for (int i = 0; i < fImageInfo.count(); ++i) {
sk_sp<PromiseImageCallbackContext> callbackContext(
new PromiseImageCallbackContext(context));
const PromiseImageInfo& info = fImageInfo[i];
// DDL TODO: how can we tell if we need mipmapping!
callbackContext->setBackendTexture(gpu->createTestingOnlyBackendTexture(
info.fBitmap.getPixels(),
info.fBitmap.width(),
info.fBitmap.height(),
info.fBitmap.colorType(),
false, GrMipMapped::kNo));
// The GMs sometimes request too large an image
//SkAssertResult(callbackContext->backendTexture().isValid());
if (info.isYUV()) {
for (int j = 0; j < 3; ++j) {
const SkPixmap& yuvPixmap = info.yuvPixmap(j);
sk_sp<PromiseImageCallbackContext> callbackContext(
new PromiseImageCallbackContext(context));
callbackContext->setBackendTexture(gpu->createTestingOnlyBackendTexture(
yuvPixmap.addr(),
yuvPixmap.width(),
yuvPixmap.height(),
yuvPixmap.colorType(),
false, GrMipMapped::kNo,
yuvPixmap.rowBytes()));
SkAssertResult(callbackContext->backendTexture().isValid());
fImageInfo[i].setCallbackContext(j, std::move(callbackContext));
}
} else {
sk_sp<PromiseImageCallbackContext> callbackContext(
new PromiseImageCallbackContext(context));
const SkBitmap& bm = info.normalBitmap();
callbackContext->setBackendTexture(gpu->createTestingOnlyBackendTexture(
bm.getPixels(),
bm.width(),
bm.height(),
bm.colorType(),
false, GrMipMapped::kNo,
bm.rowBytes()));
// The GMs sometimes request too large an image
//SkAssertResult(callbackContext->backendTexture().isValid());
fImageInfo[i].setCallbackContext(0, std::move(callbackContext));
}
// The fImageInfo array gets the creation ref
fImageInfo[i].fCallbackContext = std::move(callbackContext);
}
}
@ -99,34 +127,71 @@ sk_sp<SkImage> DDLPromiseImageHelper::PromiseImageCreator(const void* rawData,
const DDLPromiseImageHelper::PromiseImageInfo& curImage = helper->getInfo(*indexPtr);
if (!curImage.fCallbackContext->backendTexture().isValid()) {
if (!curImage.backendTexture(0).isValid()) {
SkASSERT(!curImage.isYUV());
// We weren't able to make a backend texture for this SkImage. In this case we create
// a separate bitmap-backed image for each thread.
SkASSERT(curImage.fBitmap.isImmutable());
return SkImage::MakeFromBitmap(curImage.fBitmap);
SkASSERT(curImage.normalBitmap().isImmutable());
return SkImage::MakeFromBitmap(curImage.normalBitmap());
}
SkASSERT(curImage.fIndex == *indexPtr);
SkASSERT(curImage.index() == *indexPtr);
const GrCaps* caps = curImage.fCallbackContext->caps();
const GrBackendTexture& backendTex = curImage.fCallbackContext->backendTexture();
GrBackendFormat backendFormat = caps->createFormatFromBackendTexture(backendTex);
const GrCaps* caps = curImage.caps();
// Each DDL recorder gets its own ref on the promise callback context for the
// promise images it creates.
// DDL TODO: sort out mipmapping
sk_sp<SkImage> image = recorder->makePromiseTexture(
backendFormat,
curImage.fBitmap.width(),
curImage.fBitmap.height(),
GrMipMapped::kNo,
GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
curImage.fBitmap.colorType(),
curImage.fBitmap.alphaType(),
curImage.fBitmap.refColorSpace(),
DDLPromiseImageHelper::PromiseImageFulfillProc,
DDLPromiseImageHelper::PromiseImageReleaseProc,
DDLPromiseImageHelper::PromiseImageDoneProc,
(void*) SkSafeRef(curImage.fCallbackContext.get()));
sk_sp<SkImage> image;
if (curImage.isYUV()) {
GrBackendFormat backendFormats[4];
void* contexts[4] = { nullptr, nullptr, nullptr, nullptr };
for (int i = 0; i < 3; ++i) {
const GrBackendTexture& backendTex = curImage.backendTexture(i);
backendFormats[i] = caps->createFormatFromBackendTexture(backendTex);
contexts[i] = curImage.refCallbackContext(i).release();
}
SkYUVAIndex yuvaIndices[4] = {
SkYUVAIndex{0, SkImageSourceChannel::kA_SkImageSourceChannel},
SkYUVAIndex{1, SkImageSourceChannel::kA_SkImageSourceChannel},
SkYUVAIndex{2, SkImageSourceChannel::kA_SkImageSourceChannel},
SkYUVAIndex{-1, SkImageSourceChannel::kA_SkImageSourceChannel}
};
int tempWidth = curImage.backendTexture(0).width();
int tempHeight = curImage.backendTexture(0).height();
image = recorder->makeYUVAPromiseTexture(curImage.yuvColorSpace(),
backendFormats,
yuvaIndices,
tempWidth, //curImage.overallWidth(),
tempHeight, //curImage.overallHeight(),
GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
curImage.refOverallColorSpace(),
DDLPromiseImageHelper::PromiseImageFulfillProc,
DDLPromiseImageHelper::PromiseImageReleaseProc,
DDLPromiseImageHelper::PromiseImageDoneProc,
contexts);
} else {
const GrBackendTexture& backendTex = curImage.backendTexture(0);
GrBackendFormat backendFormat = caps->createFormatFromBackendTexture(backendTex);
// Each DDL recorder gets its own ref on the promise callback context for the
// promise images it creates.
// DDL TODO: sort out mipmapping
image = recorder->makePromiseTexture(backendFormat,
curImage.overallWidth(),
curImage.overallHeight(),
GrMipMapped::kNo,
GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
curImage.overallColorType(),
curImage.overallAlphaType(),
curImage.refOverallColorSpace(),
DDLPromiseImageHelper::PromiseImageFulfillProc,
DDLPromiseImageHelper::PromiseImageReleaseProc,
DDLPromiseImageHelper::PromiseImageDoneProc,
(void*) curImage.refCallbackContext(0).release());
}
perRecorderContext->fPromiseImages->push_back(image);
SkASSERT(image);
return image;
@ -134,9 +199,9 @@ sk_sp<SkImage> DDLPromiseImageHelper::PromiseImageCreator(const void* rawData,
int DDLPromiseImageHelper::findImage(SkImage* image) const {
for (int i = 0; i < fImageInfo.count(); ++i) {
if (fImageInfo[i].fOriginalUniqueID == image->uniqueID()) { // trying to dedup here
SkASSERT(fImageInfo[i].fIndex == i);
SkASSERT(this->isValidID(i) && this->isValidID(fImageInfo[i].fIndex));
if (fImageInfo[i].originalUniqueID() == image->uniqueID()) { // trying to dedup here
SkASSERT(fImageInfo[i].index() == i);
SkASSERT(this->isValidID(i) && this->isValidID(fImageInfo[i].index()));
return i;
}
}
@ -144,26 +209,42 @@ int DDLPromiseImageHelper::findImage(SkImage* image) const {
}
int DDLPromiseImageHelper::addImage(SkImage* image) {
sk_sp<SkImage> rasterImage = image->makeRasterImage(); // force decoding of lazy images
SkImage_Base* ib = as_IB(image);
SkImageInfo ii = SkImageInfo::Make(rasterImage->width(), rasterImage->height(),
rasterImage->colorType(), rasterImage->alphaType(),
rasterImage->refColorSpace());
SkImageInfo overallII = SkImageInfo::Make(image->width(), image->height(),
image->colorType(), image->alphaType(),
image->refColorSpace());
SkBitmap bm;
bm.allocPixels(ii);
PromiseImageInfo& newImageInfo = fImageInfo.emplace_back(fImageInfo.count(),
image->uniqueID(),
overallII);
if (!rasterImage->readPixels(bm.pixmap(), 0, 0)) {
return -1;
SkYUVSizeInfo yuvSizeInfo;
SkYUVColorSpace yuvColorSpace;
const void* planes[3];
sk_sp<SkCachedData> yuvData = ib->getPlanes(&yuvSizeInfo, &yuvColorSpace, planes);
if (yuvData) {
newImageInfo.setYUVData(std::move(yuvData), yuvColorSpace);
for (int i = 0; i < 3; ++i) {
SkImageInfo planeII = SkImageInfo::MakeA8(yuvSizeInfo.fSizes[i].fWidth,
yuvSizeInfo.fSizes[i].fHeight);
newImageInfo.addYUVPlane(i, planeII, planes[i], yuvSizeInfo.fWidthBytes[i]);
}
} else {
sk_sp<SkImage> rasterImage = image->makeRasterImage(); // force decoding of lazy images
SkBitmap tmp;
tmp.allocPixels(overallII);
if (!rasterImage->readPixels(tmp.pixmap(), 0, 0)) {
return -1;
}
tmp.setImmutable();
newImageInfo.setNormalBitmap(tmp);
}
bm.setImmutable();
PromiseImageInfo& newImageInfo = fImageInfo.push_back();
newImageInfo.fIndex = fImageInfo.count()-1;
newImageInfo.fOriginalUniqueID = image->uniqueID();
newImageInfo.fBitmap = bm;
/* fCallbackContext is filled in by uploadAllToGPU */
// In either case newImageInfo's PromiseImageCallbackContext is filled in by uploadAllToGPU
return fImageInfo.count()-1;
}

View File

@ -12,6 +12,8 @@
#include "SkTArray.h"
#include "GrBackendSurface.h"
#include "SkCachedData.h"
#include "SkYUVSizeInfo.h"
class GrContext;
class SkDeferredDisplayListRecorder;
@ -35,7 +37,7 @@ class SkPicture;
// This class is then reset - dropping all of its refs on the PromiseImageCallbackContexts
//
// Each done callback unrefs its PromiseImageCallbackContext so, once all the promise images
// are done the PromiseImageCallbackContext is freed and its GrBackendTexture removed
// are done, the PromiseImageCallbackContext is freed and its GrBackendTexture removed
// from VRAM
//
// Note: if DDLs are going to be replayed multiple times, the reset call can be delayed until
@ -43,6 +45,7 @@ class SkPicture;
class DDLPromiseImageHelper {
public:
DDLPromiseImageHelper() { }
~DDLPromiseImageHelper();
// Convert the SkPicture into SkData replacing all the SkImages with an index.
sk_sp<SkData> deflateSKP(const SkPicture* inputPicture);
@ -58,10 +61,10 @@ public:
void reset() { fImageInfo.reset(); }
private:
// This class acts as a proxy for the single GrBackendTexture representing an image.
// This class acts as a proxy for a GrBackendTexture that is part of an image.
// Whenever a promise image is created for the image, the promise image receives a ref to
// this object. Once all the promise images receive their done callbacks this object
// is deleted - removing the GrBackendTexture from VRAM.
// potentially several of these objects. Once all the promise images receive their done
// callbacks this object is deleted - removing the GrBackendTexture from VRAM.
// Note that while the DDLs are being created in the threads, the PromiseImageHelper holds
// a ref on all the PromiseImageCallbackContexts. However, once all the threads are done
// it drops all of its refs (via "reset").
@ -72,6 +75,7 @@ private:
~PromiseImageCallbackContext();
void setBackendTexture(const GrBackendTexture& backendTexture) {
SkASSERT(!fBackendTexture.isValid());
fBackendTexture = backendTexture;
}
@ -91,10 +95,85 @@ private:
// is all dropped via "reset".
class PromiseImageInfo {
public:
int fIndex; // index in the 'fImageInfo' array
uint32_t fOriginalUniqueID; // original ID for deduping
SkBitmap fBitmap; // CPU-side cache of the contents
sk_sp<PromiseImageCallbackContext> fCallbackContext;
PromiseImageInfo(int index, uint32_t originalUniqueID, const SkImageInfo& ii)
: fIndex(index)
, fOriginalUniqueID(originalUniqueID)
, fImageInfo(ii) {
}
~PromiseImageInfo() {}
int index() const { return fIndex; }
uint32_t originalUniqueID() const { return fOriginalUniqueID; }
bool isYUV() const { return SkToBool(fYUVData.get()); }
int overallWidth() const { return fImageInfo.width(); }
int overallHeight() const { return fImageInfo.height(); }
SkColorType overallColorType() const { return fImageInfo.colorType(); }
SkAlphaType overallAlphaType() const { return fImageInfo.alphaType(); }
sk_sp<SkColorSpace> refOverallColorSpace() const { return fImageInfo.refColorSpace(); }
SkYUVColorSpace yuvColorSpace() const {
SkASSERT(this->isYUV());
return fYUVColorSpace;
}
const SkPixmap& yuvPixmap(int index) const {
SkASSERT(this->isYUV());
SkASSERT(index >= 0 && index < 3);
return fYUVPlanes[index];
}
const SkBitmap& normalBitmap() const {
SkASSERT(!this->isYUV());
return fBitmap;
}
void setCallbackContext(int index, sk_sp<PromiseImageCallbackContext> callbackContext) {
SkASSERT(index >= 0 && index < (this->isYUV() ? 3 : 1));
fCallbackContexts[index] = callbackContext;
}
PromiseImageCallbackContext* callbackContext(int index) {
SkASSERT(index >= 0 && index < (this->isYUV() ? 3 : 1));
return fCallbackContexts[index].get();
}
sk_sp<PromiseImageCallbackContext> refCallbackContext(int index) const {
SkASSERT(index >= 0 && index < (this->isYUV() ? 3 : 1));
return fCallbackContexts[index];
}
const GrCaps* caps() const { return fCallbackContexts[0]->caps(); }
const GrBackendTexture& backendTexture(int index) const {
SkASSERT(index >= 0 && index < (this->isYUV() ? 3 : 1));
return fCallbackContexts[index]->backendTexture();
}
void setNormalBitmap(const SkBitmap& bm) { fBitmap = bm; }
void setYUVData(sk_sp<SkCachedData> yuvData, SkYUVColorSpace cs) {
fYUVData = yuvData;
fYUVColorSpace = cs;
}
void addYUVPlane(int index, const SkImageInfo& ii, const void* plane, size_t widthBytes) {
SkASSERT(this->isYUV());
SkASSERT(index >= 0 && index < 3);
fYUVPlanes[index].reset(ii, plane, widthBytes);
}
private:
const int fIndex; // index in the 'fImageInfo' array
const uint32_t fOriginalUniqueID; // original ID for deduping
const SkImageInfo fImageInfo; // info for the overarching image
// CPU-side cache of a normal SkImage's contents
SkBitmap fBitmap;
// CPU-side cache of a YUV SkImage's contents
sk_sp<SkCachedData> fYUVData; // when !null, this is a YUV image
SkYUVColorSpace fYUVColorSpace = kJPEG_SkYUVColorSpace;
SkPixmap fYUVPlanes[3];
// Up to 3 for a YUV image. Only one for a normal image.
sk_sp<PromiseImageCallbackContext> fCallbackContexts[3];
};
// This stack-based context allows each thread to re-inflate the image indices into

View File

@ -9,6 +9,7 @@
#include "DDLPromiseImageHelper.h"
#include "SkCanvas.h"
#include "SkDeferredDisplayListPriv.h"
#include "SkDeferredDisplayListRecorder.h"
#include "SkImage_Gpu.h"
#include "SkPicture.h"
@ -31,10 +32,17 @@ void DDLTileHelper::TileData::createTileSpecificSKP(SkData* compressedPictureDat
SkDeferredDisplayListRecorder recorder(fCharacterization);
fReconstitutedPicture = helper.reinflateSKP(&recorder, compressedPictureData, &fPromiseImages);
std::unique_ptr<SkDeferredDisplayList> ddl = recorder.detach();
if (ddl.get()->priv().numOpLists()) {
// TODO: remove this once skbug.com/8424 is fixed. If the DDL resulting from the
// reinflation of the SKPs contains opLists that means some image subset operation
// created a draw.
fReconstitutedPicture.reset();
}
}
void DDLTileHelper::TileData::createDDL() {
SkASSERT(fReconstitutedPicture);
SkASSERT(!fDisplayList);
SkDeferredDisplayListRecorder recorder(fCharacterization);
@ -60,7 +68,9 @@ void DDLTileHelper::TileData::createDDL() {
// Note: in this use case we only render a picture to the deferred canvas
// but, more generally, clients will use arbitrary draw calls.
subCanvas->drawPicture(fReconstitutedPicture);
if (fReconstitutedPicture) {
subCanvas->drawPicture(fReconstitutedPicture);
}
fDisplayList = recorder.detach();
}