From 96601084b3f7b108c1faf12a2ea12eb7ea8688a0 Mon Sep 17 00:00:00 2001 From: Robert Phillips Date: Tue, 29 May 2018 16:13:26 -0400 Subject: 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 Reviewed-by: Greg Daniel Commit-Queue: Robert Phillips --- BUILD.gn | 2 + dm/DMSrcSink.cpp | 373 ++-------------------------------------- dm/DMSrcSink.h | 3 - src/image/SkImage_Gpu.h | 5 + tools/DDLPromiseImageHelper.cpp | 181 +++++++++++++++++++ tools/DDLPromiseImageHelper.h | 144 ++++++++++++++++ tools/DDLTileHelper.cpp | 166 ++++++++++++++++++ tools/DDLTileHelper.h | 80 +++++++++ tools/skpbench/skpbench.cpp | 98 ++++++++++- 9 files changed, 690 insertions(+), 362 deletions(-) create mode 100644 tools/DDLPromiseImageHelper.cpp create mode 100644 tools/DDLPromiseImageHelper.h create mode 100644 tools/DDLTileHelper.cpp create mode 100644 tools/DDLTileHelper.h 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 #include #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 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 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(textureContext); - SkASSERT(callbackContext->backendTexture().isValid()); - *outTexture = callbackContext->backendTexture(); - } - - static void PromiseImageReleaseProc(void* textureContext) { -#ifdef SK_DEBUG - auto callbackContext = static_cast(textureContext); - SkASSERT(callbackContext->backendTexture().isValid()); -#endif - } - - static void PromiseImageDoneProc(void* textureContext) { - auto callbackContext = static_cast(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 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 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 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 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 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 PromiseImageCreator(const void* rawData, size_t length, void* ctxIn) { - PerRecorderContext* perRecorderContext = static_cast(ctxIn); - const PromiseImageHelper* helper = perRecorderContext->fHelper; - SkDeferredDisplayListRecorder* recorder = perRecorderContext->fRecorder; - - SkASSERT(length == sizeof(int)); - - const int* indexPtr = static_cast(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 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 fSurface; - SkIRect fClip; // in the device space of the dest canvas - std::unique_ptr 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 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 { - auto helper = static_cast(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 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.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 newContext) { + SkASSERT(fContext->uniqueID() == newContext->uniqueID()); + fContext = newContext; + } + private: sk_sp fContext; sk_sp 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 DDLPromiseImageHelper::deflateSKP(const SkPicture* inputPicture) { + SkSerialProcs procs; + + procs.fImageCtx = this; + procs.fImageProc = [](SkImage* image, void* ctx) -> sk_sp { + auto helper = static_cast(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 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 DDLPromiseImageHelper::reinflateSKP( + SkDeferredDisplayListRecorder* recorder, + SkData* compressedPictureData, + SkTArray>* 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 DDLPromiseImageHelper::PromiseImageCreator(const void* rawData, + size_t length, void* ctxIn) { + PerRecorderContext* perRecorderContext = static_cast(ctxIn); + const DDLPromiseImageHelper* helper = perRecorderContext->fHelper; + SkDeferredDisplayListRecorder* recorder = perRecorderContext->fRecorder; + + SkASSERT(length == sizeof(int)); + + const int* indexPtr = static_cast(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 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 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 deflateSKP(const SkPicture* inputPicture); + + void uploadAllToGPU(GrContext* context); + + // reinflate a deflated SKP, replacing all the indices with promise images. + sk_sp reinflateSKP(SkDeferredDisplayListRecorder*, + SkData* compressedPicture, + SkTArray>* 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 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>* fPromiseImages; + }; + + static void PromiseImageFulfillProc(void* textureContext, GrBackendTexture* outTexture) { + auto callbackContext = static_cast(textureContext); + SkASSERT(callbackContext->backendTexture().isValid()); + *outTexture = callbackContext->backendTexture(); + } + + static void PromiseImageReleaseProc(void* textureContext) { +#ifdef SK_DEBUG + auto callbackContext = static_cast(textureContext); + SkASSERT(callbackContext->backendTexture().isValid()); +#endif + } + + static void PromiseImageDoneProc(void* textureContext) { + auto callbackContext = static_cast(textureContext); + callbackContext->unref(); + } + + static sk_sp 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 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 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 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 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, 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 fSurface; + SkSurfaceCharacterization fCharacterization; + SkIRect fClip; // in the device space of the dest canvas + sk_sp fReconstitutedPicture; + SkTArray> fPromiseImages; // All the promise images in the + // reconstituted picture + std::unique_ptr 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 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 #include #include +#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* 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 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* 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 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"); } -- cgit v1.2.3