Switch DDL rendering to be a Via in DM (take 2)

This will let us also render the GMs via DDLs.

TBR=mtklein@google.com
Change-Id: If7c2460d964822a6decc33cf5e8e685e67923127
Reviewed-on: https://skia-review.googlesource.com/116463
Reviewed-by: Mike Klein <mtklein@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
This commit is contained in:
Robert Phillips 2018-03-27 08:06:57 -04:00 committed by Skia Commit-Bot
parent 2ef4525daf
commit 33f02edb14
5 changed files with 385 additions and 4 deletions

View File

@ -957,6 +957,8 @@ static Sink* create_via(const SkString& tag, Sink* wrapped) {
VIA("tiles", ViaTiles, 256, 256, nullptr, wrapped);
VIA("tiles_rt", ViaTiles, 256, 256, new SkRTreeFactory, wrapped);
VIA("ddl", ViaDDL, 3, wrapped);
if (FLAGS_matrix.count() == 4) {
SkMatrix m;
m.reset();

View File

@ -1153,8 +1153,6 @@ Name ColorCodecSrc::name() const {
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
static const SkRect kSKPViewport = {0, 0, 1000, 1000};
SKPSrc::SKPSrc(Path path) : fPath(path) { }
static sk_sp<SkPicture> read_skp(const char* path, const SkDeserialProcs* procs = nullptr) {
@ -1177,7 +1175,7 @@ Error SKPSrc::draw(SkCanvas* canvas) const {
return SkStringPrintf("Couldn't read %s.", fPath.c_str());
}
canvas->clipRect(kSKPViewport);
canvas->clipRect(SkRect::MakeWH(FLAGS_skpViewportSize, FLAGS_skpViewportSize));
canvas->drawPicture(pic);
return "";
}
@ -1197,7 +1195,7 @@ static SkRect get_cull_rect_for_skp(const char* path) {
SkISize SKPSrc::size() const {
SkRect viewport = get_cull_rect_for_skp(fPath.c_str());
if (!viewport.intersect(kSKPViewport)) {
if (!viewport.intersect((SkRect::MakeWH(FLAGS_skpViewportSize, FLAGS_skpViewportSize)))) {
return {0, 0};
}
return viewport.roundOut().size();
@ -2325,6 +2323,371 @@ Error ViaTiles::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkStri
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
#if SK_SUPPORT_GPU
ViaDDL::ViaDDL(int numDivisions, Sink* sink)
: Via(sink)
, fNumDivisions(numDivisions) {
}
// This class consolidates tracking & extraction of the original image data from the sources,
// the upload of said data to the GPU and the fulfillment of promise images.
class ViaDDL::PromiseImageHelper {
public:
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
GrBackendTexture fBackendTexture; // GPU-side version
};
PromiseImageHelper() : fLocked(false) { }
// This class will hand out pointers to its PromiseImageInfo. This is just some insurance
// we won't be moving them around.
void lock() { fLocked = true; }
bool isValidID(int id) const {
return id >= 0 && id < fImageInfo.count();
}
const PromiseImageInfo* getInfo(int id) const {
SkASSERT(fLocked);
return &fImageInfo[id];
}
// returns -1 on failure
int findOrDefineImage(SkImage* image) {
int preExistingID = this->findImage(image);
if (preExistingID >= 0) {
SkASSERT(this->isValidID(preExistingID));
return preExistingID;
}
int newID = this->addImage(image);
SkASSERT(this->isValidID(newID));
return newID;
}
void uploadAllToGPU(GrContext* context) {
GrGpu* gpu = context->contextPriv().getGpu();
SkASSERT(gpu);
for (int i = 0; i < fImageInfo.count(); ++i) {
// DDL TODO: how can we tell if we need mipmapping!
fImageInfo[i].fBackendTexture = gpu->createTestingOnlyBackendTexture(
fImageInfo[i].fBitmap.getPixels(),
fImageInfo[i].fBitmap.width(),
fImageInfo[i].fBitmap.height(),
fImageInfo[i].fBitmap.colorType(),
false, GrMipMapped::kNo);
SkAssertResult(fImageInfo[i].fBackendTexture.isValid());
}
}
void cleanUpVRAM(GrContext* context) {
GrGpu* gpu = context->contextPriv().getGpu();
SkASSERT(gpu);
for (int i = 0; i < fImageInfo.count(); ++i) {
gpu->deleteTestingOnlyBackendTexture(fImageInfo[i].fBackendTexture);
}
}
static void PromiseImageFulfillProc(void* textureContext, GrBackendTexture* outTexture) {
auto imgInfo = static_cast<const PromiseImageInfo*>(textureContext);
SkASSERT(imgInfo->fBackendTexture.isValid());
*outTexture = imgInfo->fBackendTexture;
}
static void PromiseImageReleaseProc(void* textureContext) {
// Do nothing. We free all the backend textures at the end in cleanUpVRAM.
}
static void PromiseImageDoneProc(void* textureContext) {
// Do nothing.
}
private:
// returns -1 if not found
int findImage(SkImage* image) const {
for (int i = 0; i < fImageInfo.count(); ++i) {
if (fImageInfo[i].fOriginalUniqueID == image->uniqueID()) {
SkASSERT(fImageInfo[i].fIndex == i);
SkASSERT(this->isValidID(i) && this->isValidID(fImageInfo[i].fIndex));
return i;
}
}
return -1;
}
// returns -1 on failure
int addImage(SkImage* image) {
SkASSERT(!fLocked);
sk_sp<SkImage> rasterImage = image->makeRasterImage(); // force decoding of lazy images
SkImageInfo ii = SkImageInfo::Make(rasterImage->width(), rasterImage->height(),
rasterImage->colorType(), rasterImage->alphaType(),
rasterImage->refColorSpace());
SkBitmap bm;
bm.allocPixels(ii);
if (!rasterImage->readPixels(bm.pixmap(), 0, 0)) {
return -1;
}
bm.setImmutable();
PromiseImageInfo newImageInfo;
newImageInfo.fIndex = fImageInfo.count();
newImageInfo.fOriginalUniqueID = image->uniqueID();
newImageInfo.fBitmap = bm;
/* fBackendTexture is filled in by uploadAllToGPU */
fImageInfo.push_back(newImageInfo);
SkASSERT(newImageInfo.fIndex == fImageInfo.count()-1);
return fImageInfo.count()-1;
}
SkTArray<PromiseImageInfo> fImageInfo;
bool fLocked; // are additions still allowed
};
// TileData class encapsulates the information and behavior for a single tile/thread in
// a DDL rendering.
class ViaDDL::TileData {
public:
// Note: we could just pass in surface characterization
TileData(sk_sp<SkSurface> surf, const SkIRect& clip)
: fSurface(std::move(surf))
, fClip(clip) {
SkAssertResult(fSurface->characterize(&fCharacterization));
}
// This method operates in parallel
// In each thread we will reconvert the compressedPictureData into an SkPicture
// replacing each image-index with a promise image.
void preprocess(SkData* compressedPictureData, const PromiseImageHelper& helper) {
SkDeferredDisplayListRecorder recorder(fCharacterization);
// DDL TODO: the DDLRecorder's GrContext isn't initialized until getCanvas is called.
// Maybe set it up in the ctor?
SkCanvas* subCanvas = recorder.getCanvas();
sk_sp<SkPicture> reconstitutedPicture;
{
PromiseImageCallbackContext callbackCtx = { &helper, &recorder };
SkDeserialProcs procs;
procs.fImageCtx = &callbackCtx;
procs.fImageProc = PromiseImageCreator;
reconstitutedPicture = SkPicture::MakeFromData(compressedPictureData, &procs);
if (!reconstitutedPicture) {
return;
}
}
subCanvas->clipRect(SkRect::MakeWH(fClip.width(), fClip.height()));
subCanvas->translate(-fClip.fLeft, -fClip.fTop);
// 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(reconstitutedPicture);
fDisplayList = recorder.detach();
}
// This method operates serially and replays the recorded DDL into the tile surface.
void draw() {
fSurface->draw(fDisplayList.get());
}
// This method also operates serially and composes the results of replaying the DDL into
// the final destination surface.
void compose(SkCanvas* dst) {
sk_sp<SkImage> img = fSurface->makeImageSnapshot();
dst->save();
dst->clipRect(SkRect::Make(fClip));
dst->drawImage(std::move(img), fClip.fLeft, fClip.fTop);
dst->restore();
}
private:
// This class lets us pass the collected image information and the DDLRecorder to the
// promise_image_creator callback when reconstituting a deflated SKP for a particular tile
// (i.e., in a thread).
class PromiseImageCallbackContext {
public:
const PromiseImageHelper* fHelper;
SkDeferredDisplayListRecorder* fRecorder;
};
// This generates promise images to replace the indices in the compressed picture. This
// reconstitution is performed separately in each thread so we end of with multiple
// promise image referring to the same GrBackendTexture.
// DDL TODO: Having multiple promise images using the same GrBackendTexture won't work in
// Vulkan! Move creation of the promise images to the main thread & SkImage.
static sk_sp<SkImage> PromiseImageCreator(const void* rawData, size_t length, void* ctxIn) {
PromiseImageCallbackContext* ctx = static_cast<PromiseImageCallbackContext*>(ctxIn);
const PromiseImageHelper* helper = ctx->fHelper;
SkDeferredDisplayListRecorder* recorder = ctx->fRecorder;
SkASSERT(length == sizeof(int));
const int* indexPtr = static_cast<const int*>(rawData);
SkASSERT(helper->isValidID(*indexPtr));
const PromiseImageHelper::PromiseImageInfo* curImage = helper->getInfo(*indexPtr);
SkASSERT(curImage->fIndex == *indexPtr);
GrBackendFormat backendFormat = curImage->fBackendTexture.format();
// 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(),
PromiseImageHelper::PromiseImageFulfillProc,
PromiseImageHelper::PromiseImageReleaseProc,
PromiseImageHelper::PromiseImageDoneProc,
(void*) curImage);
SkASSERT(image);
return image;
}
sk_sp<SkSurface> fSurface;
SkIRect fClip; // in the device space of the dest canvas
std::unique_ptr<SkDeferredDisplayList> fDisplayList;
SkSurfaceCharacterization fCharacterization;
};
Error ViaDDL::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
auto size = src.size();
SkPictureRecorder recorder;
Error err = src.draw(recorder.beginRecording(SkIntToScalar(size.width()),
SkIntToScalar(size.height())));
if (!err.isEmpty()) {
return err;
}
sk_sp<SkPicture> inputPicture(recorder.finishRecordingAsPicture());
// this is our ultimate final drawing area/rect
SkIRect viewport = SkIRect::MakeWH(size.fWidth, size.fHeight);
PromiseImageHelper helper;
sk_sp<SkData> compressedPictureData;
// Convert the SkPicture into SkData replacing all the SkImages with an index.
{
SkSerialProcs procs;
procs.fImageCtx = &helper;
procs.fImageProc = [](SkImage* image, void* ctx) -> sk_sp<SkData> {
auto helper = static_cast<PromiseImageHelper*>(ctx);
int id = helper->findOrDefineImage(image);
if (id >= 0) {
SkASSERT(helper->isValidID(id));
return SkData::MakeWithCopy(&id, sizeof(id));
}
return nullptr;
};
compressedPictureData = inputPicture->serialize(&procs);
if (!compressedPictureData) {
return SkStringPrintf("ViaDDL: Couldn't deflate SkPicture");
}
}
helper.lock(); // after this point no more images should be added to the helper
return draw_to_canvas(fSink.get(), bitmap, stream, log, size,
[&](SkCanvas* canvas) -> Error {
GrContext* context = canvas->getGrContext();
if (!context || !context->contextPriv().getGpu()) {
return SkStringPrintf("DDLs are GPU only");
}
helper.uploadAllToGPU(context);
int xTileSize = viewport.width()/fNumDivisions;
int yTileSize = viewport.height()/fNumDivisions;
SkTArray<TileData> tileData;
tileData.reserve(fNumDivisions*fNumDivisions);
// First, create the destination tiles
for (int y = 0, yOff = 0; y < fNumDivisions; ++y, yOff += yTileSize) {
int ySize = (y < fNumDivisions-1) ? yTileSize : viewport.height()-yOff;
for (int x = 0, xOff = 0; x < fNumDivisions; ++x, xOff += xTileSize) {
int xSize = (x < fNumDivisions-1) ? xTileSize : viewport.width()-xOff;
SkIRect clip = SkIRect::MakeXYWH(xOff, yOff, xSize, ySize);
SkASSERT(viewport.contains(clip));
SkImageInfo tileII = SkImageInfo::MakeN32Premul(xSize, ySize);
tileData.push_back(TileData(canvas->makeSurface(tileII), clip));
}
}
// Second, run the cpu pre-processing in threads
SkTaskGroup().batch(tileData.count(), [&](int i) {
tileData[i].preprocess(compressedPictureData.get(), helper);
});
// Third, synchronously render the display lists into the dest tiles
// TODO: it would be cool to not wait until all the tiles are drawn to begin
// drawing to the GPU and composing to the final surface
for (int i = 0; i < tileData.count(); ++i) {
tileData[i].draw();
}
// Finally, compose the drawn tiles into the result
// Note: the separation between the tiles and the final composition better
// matches Chrome but costs us a copy
for (int i = 0; i < tileData.count(); ++i) {
tileData[i].compose(canvas);
}
// All promise images need to be fulfilled before leaving this method since we
// are about to delete their backing GrBackendTextures
// DDL TODO: remove the cleanUpVRAM method and use the release & done
// callbacks.
GrGpu* gpu = context->contextPriv().getGpu();
gpu->testingOnly_flushGpuAndSync();
helper.cleanUpVRAM(context);
return "";
});
}
#else
ViaDDL::ViaDDL(int numDivisions, Sink* sink) : Via(sink) { }
Error ViaDDL::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
return "ViaDDL is GPU only";
}
#endif
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
Error ViaPicture::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
auto size = src.size();
return draw_to_canvas(fSink.get(), bitmap, stream, log, size, [&](SkCanvas* canvas) -> Error {

View File

@ -537,6 +537,19 @@ private:
std::unique_ptr<SkBBHFactory> fFactory;
};
class ViaDDL : public Via {
public:
ViaDDL(int numDivisions, Sink* sink);
Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
private:
#if SK_SUPPORT_GPU
class PromiseImageHelper;
class TileData;
const int fNumDivisions;
#endif
};
class ViaSVG : public Via {
public:
explicit ViaSVG(Sink* sink) : Via(sink) {}

View File

@ -63,6 +63,8 @@ DEFINE_string(jpgs, "jpgs", "Directory to read jpgs from.");
DEFINE_string(jsons, "jsons", "Directory to read (Bodymovin) jsons from.");
#endif
DEFINE_int32(skpViewportSize, 1000, "Width & height of the viewport used to crop skp rendering.");
DEFINE_int32(ddl, 0, "If > 0, the # of x & y divisions used for DeferredDisplayList-based "
"GPU SKP rendering.");

View File

@ -24,6 +24,7 @@ DECLARE_bool(preAbandonGpuContext);
DECLARE_bool(abandonGpuContext);
DECLARE_bool(releaseAndAbandonGpuContext);
DECLARE_string(skps);
DECLARE_int32(skpViewportSize);
DECLARE_int32(ddl);
DECLARE_string(jpgs);
DECLARE_string(jsons);