aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Robert Phillips <robertphillips@google.com>2018-05-29 16:13:26 -0400
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2018-05-30 10:12:08 +0000
commit96601084b3f7b108c1faf12a2ea12eb7ea8688a0 (patch)
tree222f8ec851bd0156b32a382f458226170f7b33c0
parent16f558ddeed33c816f3d3dad03997b2ea523c5b9 (diff)
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>
-rw-r--r--BUILD.gn2
-rw-r--r--dm/DMSrcSink.cpp373
-rw-r--r--dm/DMSrcSink.h3
-rw-r--r--src/image/SkImage_Gpu.h5
-rw-r--r--tools/DDLPromiseImageHelper.cpp181
-rw-r--r--tools/DDLPromiseImageHelper.h144
-rw-r--r--tools/DDLTileHelper.cpp166
-rw-r--r--tools/DDLTileHelper.h80
-rw-r--r--tools/skpbench/skpbench.cpp98
9 files changed, 690 insertions, 362 deletions
diff --git a/BUILD.gn b/BUILD.gn
index d871e782b5..a0eabd4aac 100644
--- a/BUILD.gn
+++ b/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",
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index 9def615f2b..ffe01dbd97 100644
--- a/dm/DMSrcSink.cpp
+++ b/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);
-
- 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;
+ promiseImageHelper.uploadAllToGPU(context);
- SkIRect clip = SkIRect::MakeXYWH(xOff, yOff, xSize, ySize);
+ // First, create all the tiles (including their individual dest surfaces)
+ DDLTileHelper tiles(canvas, viewport, fNumDivisions);
- SkASSERT(viewport.contains(clip));
+ // Second, reinflate the compressed picture individually for each thread
+ tiles.createSKPPerTile(compressedPictureData.get(), promiseImageHelper);
- SkImageInfo tileII = SkImageInfo::MakeN32Premul(xSize, ySize);
+ // Third, create the DDLs in parallel
+ tiles.createDDLsInParallel();
- 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 promiseImageHelper's refs on all the promise images
+ promiseImageHelper.reset();
- // 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 "";
diff --git a/dm/DMSrcSink.h b/dm/DMSrcSink.h
index cffca6e760..982040469c 100644
--- a/dm/DMSrcSink.h
+++ b/dm/DMSrcSink.h
@@ -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
};
diff --git a/src/image/SkImage_Gpu.h b/src/image/SkImage_Gpu.h
index ff2aaaf07c..c79e5295be 100644
--- a/src/image/SkImage_Gpu.h
+++ b/src/image/SkImage_Gpu.h
@@ -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;
diff --git a/tools/DDLPromiseImageHelper.cpp b/tools/DDLPromiseImageHelper.cpp
new file mode 100644
index 0000000000..1eed565331
--- /dev/null
+++ b/tools/DDLPromiseImageHelper.cpp
@@ -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
diff --git a/tools/DDLPromiseImageHelper.h b/tools/DDLPromiseImageHelper.h
new file mode 100644
index 0000000000..b4b51a4f5b
--- /dev/null
+++ b/tools/DDLPromiseImageHelper.h
@@ -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
diff --git a/tools/DDLTileHelper.cpp b/tools/DDLTileHelper.cpp
new file mode 100644
index 0000000000..462915aafc
--- /dev/null
+++ b/tools/DDLTileHelper.cpp
@@ -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
diff --git a/tools/DDLTileHelper.h b/tools/DDLTileHelper.h
new file mode 100644
index 0000000000..fa6f8115bc
--- /dev/null
+++ b/tools/DDLTileHelper.h
@@ -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
diff --git a/tools/skpbench/skpbench.cpp b/tools/skpbench/skpbench.cpp
index 452095fe06..f8a6e9b1a8 100644
--- a/tools/skpbench/skpbench.cpp
+++ b/tools/skpbench/skpbench.cpp
@@ -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");
}