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:
parent
16f558ddee
commit
96601084b3
2
BUILD.gn
2
BUILD.gn
@ -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",
|
||||
|
373
dm/DMSrcSink.cpp
373
dm/DMSrcSink.cpp
@ -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 "";
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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;
|
||||
|
181
tools/DDLPromiseImageHelper.cpp
Normal file
181
tools/DDLPromiseImageHelper.cpp
Normal 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
|
144
tools/DDLPromiseImageHelper.h
Normal file
144
tools/DDLPromiseImageHelper.h
Normal 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
166
tools/DDLTileHelper.cpp
Normal 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
80
tools/DDLTileHelper.h
Normal 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
|
@ -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");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user