Add DDL to SKPBench

Most of this CL is just repackaging the promise image and tile
code from ViaDDL for reuse by SKPBench.

Change-Id: Ie5003c36fe85cc5be9639552f9488b8e92dcdbbf
Reviewed-on: https://skia-review.googlesource.com/129805
Reviewed-by: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
This commit is contained in:
Robert Phillips 2018-05-29 16:13:26 -04:00 committed by Skia Commit-Bot
parent 16f558ddee
commit 96601084b3
9 changed files with 690 additions and 362 deletions

View File

@ -1324,6 +1324,8 @@ if (skia_enable_tools) {
sources = [
"tools/AndroidSkDebugToStdOut.cpp",
"tools/CrashHandler.cpp",
"tools/DDLPromiseImageHelper.cpp",
"tools/DDLTileHelper.cpp",
"tools/LsanSuppressions.cpp",
"tools/ProcStats.cpp",
"tools/Resources.cpp",

View File

@ -9,6 +9,8 @@
#include <cmath>
#include <functional>
#include "../src/jumper/SkJumper.h"
#include "DDLPromiseImageHelper.h"
#include "DDLTileHelper.h"
#include "Resources.h"
#include "SkAndroidCodec.h"
#include "SkAutoMalloc.h"
@ -2019,302 +2021,6 @@ ViaDDL::ViaDDL(int numDivisions, Sink* 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.
//
// The way this works is:
// the original skp is converted to SkData and all its image info is extracted into this
// class and only indices into this class are left in the SkData
// Prior to replaying in threads, all the images stored in this class are uploaded to the
// gpu and PromiseImageCallbackContexts are created for them
// Each thread reinflates the SkData into an SkPicture replacing all the indices w/
// promise images (all using the same GrBackendTexture and getting a ref to the
// appropriate PromiseImageCallbackContext) and then creates a DDL.
// 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
// from VRAM
//
class ViaDDL::PromiseImageHelper {
public:
// This class acts as a proxy for the single GrBackendTexture representing 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.
// 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").
class PromiseImageCallbackContext : public SkRefCnt {
public:
PromiseImageCallbackContext(GrContext* context) : fContext(context) {}
~PromiseImageCallbackContext() {
GrGpu* gpu = fContext->contextPriv().getGpu();
if (fBackendTexture.isValid()) {
gpu->deleteTestingOnlyBackendTexture(fBackendTexture);
}
}
void setBackendTexture(const GrBackendTexture& backendTexture) {
fBackendTexture = backendTexture;
}
const GrBackendTexture& backendTexture() const { return fBackendTexture; }
private:
GrContext* fContext;
GrBackendTexture fBackendTexture;
typedef SkRefCnt INHERITED;
};
// This is the information extracted into this class from the parsing of the skp file.
// Once it has all been uploaded to the GPU and distributed to the promise images, it
// 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;
};
PromiseImageHelper() { }
void reset() { fImageInfo.reset(); }
bool isValidID(int id) const {
return id >= 0 && id < fImageInfo.count();
}
const PromiseImageInfo& getInfo(int id) const {
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) {
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(),
info.fBitmap.colorSpace(),
false, GrMipMapped::kNo));
// The GMs sometimes request too large an image
//SkAssertResult(callbackContext->backendTexture().isValid());
// The fImageInfo array gets the creation ref
fImageInfo[i].fCallbackContext = std::move(callbackContext);
}
}
static void PromiseImageFulfillProc(void* textureContext, GrBackendTexture* outTexture) {
auto callbackContext = static_cast<PromiseImageCallbackContext*>(textureContext);
SkASSERT(callbackContext->backendTexture().isValid());
*outTexture = callbackContext->backendTexture();
}
static void PromiseImageReleaseProc(void* textureContext) {
#ifdef SK_DEBUG
auto callbackContext = static_cast<PromiseImageCallbackContext*>(textureContext);
SkASSERT(callbackContext->backendTexture().isValid());
#endif
}
static void PromiseImageDoneProc(void* textureContext) {
auto callbackContext = static_cast<PromiseImageCallbackContext*>(textureContext);
callbackContext->unref();
}
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) {
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;
/* fCallbackContext is filled in by uploadAllToGPU */
fImageInfo.push_back(newImageInfo);
SkASSERT(newImageInfo.fIndex == fImageInfo.count()-1);
return fImageInfo.count()-1;
}
SkTArray<PromiseImageInfo> fImageInfo;
};
// 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;
{
PerRecorderContext perRecorderContext { &recorder, &helper };
SkDeserialProcs procs;
procs.fImageCtx = (void*) &perRecorderContext;
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 stack-based context allows each thread to re-inflate the image indices into
// promise images while still using the same GrBackendTexture.
struct PerRecorderContext {
SkDeferredDisplayListRecorder* fRecorder;
const PromiseImageHelper* fHelper;
};
// This generates promise images to replace the indices in the compressed picture. This
// reconstitution is performed separately in each thread so we end up with multiple
// promise images referring to the same GrBackendTexture.
static sk_sp<SkImage> PromiseImageCreator(const void* rawData, size_t length, void* ctxIn) {
PerRecorderContext* perRecorderContext = static_cast<PerRecorderContext*>(ctxIn);
const PromiseImageHelper* helper = perRecorderContext->fHelper;
SkDeferredDisplayListRecorder* recorder = perRecorderContext->fRecorder;
SkASSERT(length == sizeof(int));
const int* indexPtr = static_cast<const int*>(rawData);
SkASSERT(helper->isValidID(*indexPtr));
const PromiseImageHelper::PromiseImageInfo& curImage = helper->getInfo(*indexPtr);
if (!curImage.fCallbackContext->backendTexture().isValid()) {
// 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.
// Note: we would like to share the same bitmap between all the threads but
// SkBitmap is not thread-safe.
return SkImage::MakeRasterCopy(curImage.fBitmap.pixmap());
}
SkASSERT(curImage.fIndex == *indexPtr);
GrBackendFormat backendFormat = curImage.fCallbackContext->backendTexture().format();
// 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(),
PromiseImageHelper::PromiseImageFulfillProc,
PromiseImageHelper::PromiseImageReleaseProc,
PromiseImageHelper::PromiseImageDoneProc,
(void*) SkSafeRef(curImage.fCallbackContext.get()));
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;
@ -2328,30 +2034,10 @@ Error ViaDDL::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString
// 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");
}
DDLPromiseImageHelper promiseImageHelper;
sk_sp<SkData> compressedPictureData = promiseImageHelper.deflateSKP(inputPicture.get());
if (!compressedPictureData) {
return SkStringPrintf("ViaDDL: Couldn't deflate SkPicture");
}
return draw_to_canvas(fSink.get(), bitmap, stream, log, size,
@ -2362,52 +2048,29 @@ Error ViaDDL::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString
}
// This is here bc this is the first point where we have access to the context
helper.uploadAllToGPU(context);
promiseImageHelper.uploadAllToGPU(context);
int xTileSize = viewport.width()/fNumDivisions;
int yTileSize = viewport.height()/fNumDivisions;
// First, create all the tiles (including their individual dest surfaces)
DDLTileHelper tiles(canvas, viewport, fNumDivisions);
SkTArray<TileData> tileData;
tileData.reserve(fNumDivisions*fNumDivisions);
// Second, reinflate the compressed picture individually for each thread
tiles.createSKPPerTile(compressedPictureData.get(), promiseImageHelper);
// 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;
// Third, create the DDLs in parallel
tiles.createDDLsInParallel();
for (int x = 0, xOff = 0; x < fNumDivisions; ++x, xOff += xTileSize) {
int xSize = (x < fNumDivisions-1) ? xTileSize : viewport.width()-xOff;
// This drops the promiseImageHelper's refs on all the promise images
promiseImageHelper.reset();
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);
});
// This drops the helper's refs on all the promise images
helper.reset();
// Third, synchronously render the display lists into the dest tiles
// Fourth, 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();
}
tiles.drawAllTilesAndFlush(context, false);
// 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);
}
tiles.composeAllTiles(canvas);
context->flush();
return "";

View File

@ -529,9 +529,6 @@ public:
Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
private:
#if SK_SUPPORT_GPU
class PromiseImageHelper;
class TileData;
const int fNumDivisions;
#endif
};

View File

@ -135,6 +135,11 @@ public:
bool onIsValid(GrContext*) const override;
void resetContext(sk_sp<GrContext> newContext) {
SkASSERT(fContext->uniqueID() == newContext->uniqueID());
fContext = newContext;
}
private:
sk_sp<GrContext> fContext;
sk_sp<GrTextureProxy> fProxy;

View File

@ -0,0 +1,181 @@
/*
* Copyright 2018 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "DDLPromiseImageHelper.h"
#if SK_SUPPORT_GPU
#include "GrContext.h"
#include "GrContextPriv.h"
#include "GrGpu.h"
#include "SkDeferredDisplayListRecorder.h"
DDLPromiseImageHelper::PromiseImageCallbackContext::~PromiseImageCallbackContext() {
GrGpu* gpu = fContext->contextPriv().getGpu();
if (fBackendTexture.isValid()) {
gpu->deleteTestingOnlyBackendTexture(fBackendTexture);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
sk_sp<SkData> DDLPromiseImageHelper::deflateSKP(const SkPicture* inputPicture) {
SkSerialProcs procs;
procs.fImageCtx = this;
procs.fImageProc = [](SkImage* image, void* ctx) -> sk_sp<SkData> {
auto helper = static_cast<DDLPromiseImageHelper*>(ctx);
int id = helper->findOrDefineImage(image);
if (id >= 0) {
SkASSERT(helper->isValidID(id));
return SkData::MakeWithCopy(&id, sizeof(id));
}
return nullptr;
};
return inputPicture->serialize(&procs);
}
void DDLPromiseImageHelper::uploadAllToGPU(GrContext* context) {
GrGpu* gpu = context->contextPriv().getGpu();
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(),
info.fBitmap.colorSpace(),
false, GrMipMapped::kNo));
// The GMs sometimes request too large an image
//SkAssertResult(callbackContext->backendTexture().isValid());
// The fImageInfo array gets the creation ref
fImageInfo[i].fCallbackContext = std::move(callbackContext);
}
}
sk_sp<SkPicture> DDLPromiseImageHelper::reinflateSKP(
SkDeferredDisplayListRecorder* recorder,
SkData* compressedPictureData,
SkTArray<sk_sp<SkImage>>* promiseImages) const {
PerRecorderContext perRecorderContext { recorder, this, promiseImages };
SkDeserialProcs procs;
procs.fImageCtx = (void*) &perRecorderContext;
procs.fImageProc = PromiseImageCreator;
return SkPicture::MakeFromData(compressedPictureData, &procs);
}
// This generates promise images to replace the indices in the compressed picture. This
// reconstitution is performed separately in each thread so we end up with multiple
// promise images referring to the same GrBackendTexture.
sk_sp<SkImage> DDLPromiseImageHelper::PromiseImageCreator(const void* rawData,
size_t length, void* ctxIn) {
PerRecorderContext* perRecorderContext = static_cast<PerRecorderContext*>(ctxIn);
const DDLPromiseImageHelper* helper = perRecorderContext->fHelper;
SkDeferredDisplayListRecorder* recorder = perRecorderContext->fRecorder;
SkASSERT(length == sizeof(int));
const int* indexPtr = static_cast<const int*>(rawData);
SkASSERT(helper->isValidID(*indexPtr));
const DDLPromiseImageHelper::PromiseImageInfo& curImage = helper->getInfo(*indexPtr);
if (!curImage.fCallbackContext->backendTexture().isValid()) {
// 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.
// Note: we would like to share the same bitmap between all the threads but
// SkBitmap is not thread-safe.
return SkImage::MakeRasterCopy(curImage.fBitmap.pixmap());
}
SkASSERT(curImage.fIndex == *indexPtr);
GrBackendFormat backendFormat = curImage.fCallbackContext->backendTexture().format();
// 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()));
perRecorderContext->fPromiseImages->push_back(image);
SkASSERT(image);
return image;
}
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));
return i;
}
}
return -1;
}
int DDLPromiseImageHelper::addImage(SkImage* image) {
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 = fImageInfo.push_back();
newImageInfo.fIndex = fImageInfo.count()-1;
newImageInfo.fOriginalUniqueID = image->uniqueID();
newImageInfo.fBitmap = bm;
/* fCallbackContext is filled in by uploadAllToGPU */
return fImageInfo.count()-1;
}
int DDLPromiseImageHelper::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;
}
#endif

View File

@ -0,0 +1,144 @@
/*
* 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 PromiseImageHelper_DEFINED
#define PromiseImageHelper_DEFINED
#include "SkBitmap.h"
#include "SkTArray.h"
#if SK_SUPPORT_GPU
#include "GrBackendSurface.h"
class GrContext;
class SkDeferredDisplayListRecorder;
class SkImage;
class SkPicture;
// This class consolidates tracking & extraction of the original image data from an skp,
// the upload of said data to the GPU and the fulfillment of promise images.
//
// The way this works is:
// the original skp is converted to SkData and all its image info is extracted into this
// class and only indices into this class are left in the SkData (via deflateSKP)
//
// Prior to replaying in threads, all the images stored in this class are uploaded to the
// gpu and PromiseImageCallbackContexts are created for them (via uploadAllToGPU)
//
// Each thread reinflates the SkData into an SkPicture replacing all the indices w/
// promise images (all using the same GrBackendTexture and getting a ref to the
// appropriate PromiseImageCallbackContext) (via reinflateSKP).
//
// 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
// from VRAM
//
// Note: if DDLs are going to be replayed multiple times, the reset call can be delayed until
// all the replaying is complete. This will pin the GrBackendTextures in VRAM.
class DDLPromiseImageHelper {
public:
DDLPromiseImageHelper() { }
// Convert the SkPicture into SkData replacing all the SkImages with an index.
sk_sp<SkData> deflateSKP(const SkPicture* inputPicture);
void uploadAllToGPU(GrContext* context);
// reinflate a deflated SKP, replacing all the indices with promise images.
sk_sp<SkPicture> reinflateSKP(SkDeferredDisplayListRecorder*,
SkData* compressedPicture,
SkTArray<sk_sp<SkImage>>* promiseImages) const;
// Remove this class' refs on the PromiseImageCallbackContexts
void reset() { fImageInfo.reset(); }
private:
// This class acts as a proxy for the single GrBackendTexture representing 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.
// 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").
class PromiseImageCallbackContext : public SkRefCnt {
public:
PromiseImageCallbackContext(GrContext* context) : fContext(context) {}
~PromiseImageCallbackContext();
void setBackendTexture(const GrBackendTexture& backendTexture) {
fBackendTexture = backendTexture;
}
const GrBackendTexture& backendTexture() const { return fBackendTexture; }
private:
GrContext* fContext;
GrBackendTexture fBackendTexture;
typedef SkRefCnt INHERITED;
};
// This is the information extracted into this class from the parsing of the skp file.
// Once it has all been uploaded to the GPU and distributed to the promise images, it
// 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;
};
// This stack-based context allows each thread to re-inflate the image indices into
// promise images while still using the same GrBackendTexture.
struct PerRecorderContext {
SkDeferredDisplayListRecorder* fRecorder;
const DDLPromiseImageHelper* fHelper;
SkTArray<sk_sp<SkImage>>* fPromiseImages;
};
static void PromiseImageFulfillProc(void* textureContext, GrBackendTexture* outTexture) {
auto callbackContext = static_cast<PromiseImageCallbackContext*>(textureContext);
SkASSERT(callbackContext->backendTexture().isValid());
*outTexture = callbackContext->backendTexture();
}
static void PromiseImageReleaseProc(void* textureContext) {
#ifdef SK_DEBUG
auto callbackContext = static_cast<PromiseImageCallbackContext*>(textureContext);
SkASSERT(callbackContext->backendTexture().isValid());
#endif
}
static void PromiseImageDoneProc(void* textureContext) {
auto callbackContext = static_cast<PromiseImageCallbackContext*>(textureContext);
callbackContext->unref();
}
static sk_sp<SkImage> PromiseImageCreator(const void* rawData, size_t length, void* ctxIn);
bool isValidID(int id) const { return id >= 0 && id < fImageInfo.count(); }
const PromiseImageInfo& getInfo(int id) const { return fImageInfo[id]; }
// returns -1 if not found
int findImage(SkImage* image) const;
// returns -1 on failure
int addImage(SkImage* image);
// returns -1 on failure
int findOrDefineImage(SkImage* image);
SkTArray<PromiseImageInfo> fImageInfo;
};
#endif
#endif

166
tools/DDLTileHelper.cpp Normal file
View File

@ -0,0 +1,166 @@
/*
* Copyright 2018 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "DDLTileHelper.h"
#if SK_SUPPORT_GPU
#include "DDLPromiseImageHelper.h"
#include "SkCanvas.h"
#include "SkDeferredDisplayListRecorder.h"
#include "SkImage_Gpu.h"
#include "SkPicture.h"
#include "SkSurface.h"
#include "SkSurfaceCharacterization.h"
#include "SkTaskGroup.h"
DDLTileHelper::TileData::TileData(sk_sp<SkSurface> s, const SkIRect& clip)
: fSurface(std::move(s))
, fClip(clip) {
SkAssertResult(fSurface->characterize(&fCharacterization));
}
void DDLTileHelper::TileData::createTileSpecificSKP(SkData* compressedPictureData,
const DDLPromiseImageHelper& helper) {
SkASSERT(!fReconstitutedPicture);
// This is bending the DDLRecorder contract! The promise images in the SKP should be
// created by the same recorder used to create the matching DDL.
SkDeferredDisplayListRecorder recorder(fCharacterization);
fReconstitutedPicture = helper.reinflateSKP(&recorder, compressedPictureData, &fPromiseImages);
}
void DDLTileHelper::TileData::createDDL() {
SkASSERT(fReconstitutedPicture);
SkASSERT(!fDisplayList);
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();
// Because we cheated in createTileSpecificSKP and used the wrong DDLRecorder, the GrContext's
// stored in fReconstitutedPicture's promise images are incorrect. Patch them with the correct
// one now.
for (int i = 0; i < fPromiseImages.count(); ++i) {
GrContext* newContext = subCanvas->getGrContext();
if (fPromiseImages[i]->isTextureBacked()) {
SkImage_Gpu* gpuImage = (SkImage_Gpu*) fPromiseImages[i].get();
gpuImage->resetContext(sk_ref_sp(newContext));
}
}
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(fReconstitutedPicture);
fDisplayList = recorder.detach();
}
void DDLTileHelper::TileData::draw() {
SkASSERT(fDisplayList);
fSurface->draw(fDisplayList.get());
}
void DDLTileHelper::TileData::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();
}
void DDLTileHelper::TileData::reset() {
// TODO: when DDLs are re-renderable we don't need to do this
fDisplayList = nullptr;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
DDLTileHelper::DDLTileHelper(SkCanvas* canvas, const SkIRect& viewport, int numDivisions)
: fNumDivisions(numDivisions) {
SkASSERT(fNumDivisions > 0);
fTiles.reserve(fNumDivisions*fNumDivisions);
int xTileSize = viewport.width()/fNumDivisions;
int yTileSize = viewport.height()/fNumDivisions;
// 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);
sk_sp<SkSurface> tileSurface = canvas->makeSurface(tileII);
// TODO: this is here to deal w/ a resource allocator bug (skbug.com/8007). If all
// the DDLs are flushed at the same time (w/o the composition draws) the allocator
// feels free to reuse the backing GrSurfaces!
tileSurface->flush();
fTiles.push_back(TileData(std::move(tileSurface), clip));
}
}
}
void DDLTileHelper::createSKPPerTile(SkData* compressedPictureData,
const DDLPromiseImageHelper& helper) {
for (int i = 0; i < fTiles.count(); ++i) {
fTiles[i].createTileSpecificSKP(compressedPictureData, helper);
}
}
void DDLTileHelper::createDDLsInParallel() {
#if 1
SkTaskGroup().batch(fTiles.count(), [&](int i) {
fTiles[i].createDDL();
});
#else
// Use this code path to debug w/o threads
for (int i = 0; i < fTiles.count(); ++i) {
fTiles[i].createDDL();
}
#endif
}
void DDLTileHelper::drawAllTilesAndFlush(GrContext* context, bool flush) {
for (int i = 0; i < fTiles.count(); ++i) {
fTiles[i].draw();
}
if (flush) {
context->flush();
}
}
void DDLTileHelper::composeAllTiles(SkCanvas* dstCanvas) {
for (int i = 0; i < fTiles.count(); ++i) {
fTiles[i].compose(dstCanvas);
}
}
void DDLTileHelper::resetAllTiles() {
for (int i = 0; i < fTiles.count(); ++i) {
fTiles[i].reset();
}
}
#endif

80
tools/DDLTileHelper.h Normal file
View File

@ -0,0 +1,80 @@
/*
* 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 DDLTileHelper_DEFINED
#define DDLTileHelper_DEFINED
#include "SkRect.h"
#include "SkRefCnt.h"
#include "SkSurfaceCharacterization.h"
#if SK_SUPPORT_GPU
class DDLPromiseImageHelper;
class SkCanvas;
class SkData;
class SkDeferredDisplayList;
class SkPicture;
class SkSurface;
class SkSurfaceCharacterization;
class DDLTileHelper {
public:
// TileData class encapsulates the information and behavior for a single tile/thread in
// a DDL rendering.
class TileData {
public:
TileData(sk_sp<SkSurface>, const SkIRect& clip);
// This method can be invoked in parallel
// In each thread we will reconvert the compressedPictureData into an SkPicture
// replacing each image-index with a promise image.
void createTileSpecificSKP(SkData* compressedPictureData,
const DDLPromiseImageHelper& helper);
// This method can be invoked in parallel
// Create the per-tile DDL from the per-tile SKP
void createDDL();
// This method operates serially and replays the recorded DDL into the tile surface.
void draw();
// This method also operates serially and composes the results of replaying the DDL into
// the final destination surface.
void compose(SkCanvas* dst);
void reset();
private:
sk_sp<SkSurface> fSurface;
SkSurfaceCharacterization fCharacterization;
SkIRect fClip; // in the device space of the dest canvas
sk_sp<SkPicture> fReconstitutedPicture;
SkTArray<sk_sp<SkImage>> fPromiseImages; // All the promise images in the
// reconstituted picture
std::unique_ptr<SkDeferredDisplayList> fDisplayList;
};
DDLTileHelper(SkCanvas* canvas, const SkIRect& viewport, int numDivisions);
void createSKPPerTile(SkData* compressedPictureData, const DDLPromiseImageHelper& helper);
void createDDLsInParallel();
void drawAllTilesAndFlush(GrContext*, bool flush);
void composeAllTiles(SkCanvas* dstCanvas);
void resetAllTiles();
private:
int fNumDivisions; // number of tiles along a side
SkTArray<TileData> fTiles;
};
#endif
#endif

View File

@ -11,6 +11,8 @@
#include <chrono>
#include <cmath>
#include <vector>
#include "DDLPromiseImageHelper.h"
#include "DDLTileHelper.h"
#include "GpuTimer.h"
#include "GrCaps.h"
#include "GrContextFactory.h"
@ -18,6 +20,8 @@
#include "SkCanvas.h"
#include "SkCommonFlags.h"
#include "SkCommonFlagsGpu.h"
#include "SkDeferredDisplayList.h"
#include "SkGraphics.h"
#include "SkGr.h"
#include "SkOSFile.h"
#include "SkOSPath.h"
@ -27,6 +31,7 @@
#include "SkStream.h"
#include "SkSurface.h"
#include "SkSurfaceProps.h"
#include "SkTaskGroup.h"
#include "flags/SkCommandLineFlags.h"
#include "flags/SkCommonFlagsConfig.h"
#include "picture_utils.h"
@ -43,6 +48,10 @@
* Currently, only GPU configs are supported.
*/
DEFINE_bool(ddl, false, "record the skp into DDLs before rendering");
DEFINE_int32(ddlNumAdditionalThreads, 0, "number of DDL recording threads in addition to main one");
DEFINE_int32(ddlTilingWidthHeight, 0, "number of tiles along one edge when in DDL mode");
DEFINE_int32(duration, 5000, "number of milliseconds to run the benchmark");
DEFINE_int32(sampleMs, 50, "minimum duration of a sample");
DEFINE_bool(gpuClock, false, "time on the gpu clock (gpu work only)");
@ -100,17 +109,87 @@ static bool mkdir_p(const SkString& name);
static SkString join(const SkCommandLineFlags::StringArray&);
static void exitf(ExitErr, const char* format, ...);
static void ddl_sample(GrContext* context, DDLTileHelper* tiles, GpuSync* gpuSync, Sample* sample,
std::chrono::high_resolution_clock::time_point* startStopTime) {
using clock = std::chrono::high_resolution_clock;
clock::time_point start = *startStopTime;
tiles->createDDLsInParallel();
tiles->drawAllTilesAndFlush(context, true);
if (gpuSync) {
gpuSync->syncToPreviousFrame();
}
*startStopTime = clock::now();
tiles->resetAllTiles();
if (sample) {
SkASSERT(gpuSync);
sample->fDuration += *startStopTime - start;
sample->fFrames++;
}
}
static void run_ddl_benchmark(const sk_gpu_test::FenceSync* fenceSync,
GrContext* context, SkCanvas* finalCanvas,
SkPicture* inputPicture, std::vector<Sample>* samples) {
using clock = std::chrono::high_resolution_clock;
const Sample::duration sampleDuration = std::chrono::milliseconds(FLAGS_sampleMs);
const clock::duration benchDuration = std::chrono::milliseconds(FLAGS_duration);
SkIRect viewport = finalCanvas->imageInfo().bounds();
DDLPromiseImageHelper promiseImageHelper;
sk_sp<SkData> compressedPictureData = promiseImageHelper.deflateSKP(inputPicture);
if (!compressedPictureData) {
exitf(ExitErr::kUnavailable, "DDL: conversion of skp failed");
}
promiseImageHelper.uploadAllToGPU(context);
DDLTileHelper tiles(finalCanvas, viewport, FLAGS_ddlTilingWidthHeight);
tiles.createSKPPerTile(compressedPictureData.get(), promiseImageHelper);
clock::time_point startStopTime = clock::now();
ddl_sample(context, &tiles, nullptr, nullptr, &startStopTime);
GpuSync gpuSync(fenceSync);
ddl_sample(context, &tiles, &gpuSync, nullptr, &startStopTime);
clock::duration cumulativeDuration = std::chrono::milliseconds(0);
do {
samples->emplace_back();
Sample& sample = samples->back();
do {
ddl_sample(context, &tiles, &gpuSync, &sample, &startStopTime);
} while (sample.fDuration < sampleDuration);
cumulativeDuration += sample.fDuration;
} while (cumulativeDuration < benchDuration || 0 == samples->size() % 2);
if (!FLAGS_png.isEmpty()) {
// The user wants to see the final result
tiles.composeAllTiles(finalCanvas);
}
}
static void run_benchmark(const sk_gpu_test::FenceSync* fenceSync, SkCanvas* canvas,
const SkPicture* skp, std::vector<Sample>* samples) {
using clock = std::chrono::high_resolution_clock;
const Sample::duration sampleDuration = std::chrono::milliseconds(FLAGS_sampleMs);
const clock::duration benchDuration = std::chrono::milliseconds(FLAGS_duration);
draw_skp_and_flush(canvas, skp);
draw_skp_and_flush(canvas, skp); // draw1
GpuSync gpuSync(fenceSync);
draw_skp_and_flush(canvas, skp);
gpuSync.syncToPreviousFrame();
draw_skp_and_flush(canvas, skp); // draw2
gpuSync.syncToPreviousFrame(); // waits for draw1 to finish (after draw2's cpu work is done).
clock::time_point now = clock::now();
const clock::time_point endTime = now + benchDuration;
@ -249,6 +328,10 @@ int main(int argc, char** argv) {
exitf(ExitErr::kUsage, "invalid skp '%s': must specify a single skp file, or 'warmup'",
join(FLAGS_skp).c_str());
}
SkGraphics::Init();
SkTaskGroup::Enabler enabled(FLAGS_ddlNumAdditionalThreads);
sk_sp<SkPicture> skp;
SkString skpname;
if (0 == strcmp(FLAGS_skp[0], "warmup")) {
@ -339,8 +422,15 @@ int main(int argc, char** argv) {
SkCanvas* canvas = surface->getCanvas();
canvas->translate(-skp->cullRect().x(), -skp->cullRect().y());
if (!FLAGS_gpuClock) {
run_benchmark(testCtx->fenceSync(), canvas, skp.get(), &samples);
if (FLAGS_ddl) {
run_ddl_benchmark(testCtx->fenceSync(), ctx, canvas, skp.get(), &samples);
} else {
run_benchmark(testCtx->fenceSync(), canvas, skp.get(), &samples);
}
} else {
if (FLAGS_ddl) {
exitf(ExitErr::kUnavailable, "DDL: GPU-only timing not supported");
}
if (!testCtx->gpuTimingSupport()) {
exitf(ExitErr::kUnavailable, "GPU does not support timing");
}