From bd2e2a0cafb0944255635b95663e1d242064b9c3 Mon Sep 17 00:00:00 2001 From: Robert Phillips Date: Mon, 26 Mar 2018 15:30:34 -0400 Subject: Switch DDL rendering to be a Via in DM This will let us also render the GMs via DDLs. Change-Id: I866a5af66d737473f4760dbc9d45053460c42e6e Reviewed-on: https://skia-review.googlesource.com/116021 Commit-Queue: Robert Phillips Reviewed-by: Greg Daniel Reviewed-by: Mike Klein --- dm/DMSrcSink.cpp | 375 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 371 insertions(+), 4 deletions(-) (limited to 'dm/DMSrcSink.cpp') diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp index 1d8aab1fc1..b97e9f64a5 100644 --- a/dm/DMSrcSink.cpp +++ b/dm/DMSrcSink.cpp @@ -1153,8 +1153,6 @@ Name ColorCodecSrc::name() const { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ -static const SkRect kSKPViewport = {0, 0, 1000, 1000}; - SKPSrc::SKPSrc(Path path) : fPath(path) { } static sk_sp read_skp(const char* path, const SkDeserialProcs* procs = nullptr) { @@ -1177,7 +1175,7 @@ Error SKPSrc::draw(SkCanvas* canvas) const { return SkStringPrintf("Couldn't read %s.", fPath.c_str()); } - canvas->clipRect(kSKPViewport); + canvas->clipRect(SkRect::MakeWH(FLAGS_skpViewportSize, FLAGS_skpViewportSize)); canvas->drawPicture(pic); return ""; } @@ -1197,7 +1195,7 @@ static SkRect get_cull_rect_for_skp(const char* path) { SkISize SKPSrc::size() const { SkRect viewport = get_cull_rect_for_skp(fPath.c_str()); - if (!viewport.intersect(kSKPViewport)) { + if (!viewport.intersect((SkRect::MakeWH(FLAGS_skpViewportSize, FLAGS_skpViewportSize)))) { return {0, 0}; } return viewport.roundOut().size(); @@ -2325,6 +2323,375 @@ Error ViaTiles::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkStri /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +ViaDDL::ViaDDL(int numDivisions, Sink* sink) + : Via(sink) + , fNumDivisions(numDivisions) { +} + +// This class consolidates tracking & extraction of the original image data from the sources, +// the upload of said data to the GPU and the fulfillment of promise images. +class ViaDDL::PromiseImageHelper { +public: + class PromiseImageInfo { + public: + int fIndex; // index in the 'fImageInfo' array + uint32_t fOriginalUniqueID; // original ID for deduping + SkBitmap fBitmap; // CPU-side cache of the contents +#if SK_SUPPORT_GPU + GrBackendTexture fBackendTexture; // GPU-side version +#endif + }; + + PromiseImageHelper() : fLocked(false) { } + + // This class will hand out pointers to its PromiseImageInfo. This is just some insurance + // we won't be moving them around. + void lock() { fLocked = true; } + + bool isValidID(int id) const { + return id >= 0 && id < fImageInfo.count(); + } + + const PromiseImageInfo* getInfo(int id) const { + SkASSERT(fLocked); + return &fImageInfo[id]; + } + + // returns -1 on failure + int findOrDefineImage(SkImage* image) { + int preExistingID = this->findImage(image); + if (preExistingID >= 0) { + SkASSERT(this->isValidID(preExistingID)); + return preExistingID; + } + + int newID = this->addImage(image); + SkASSERT(this->isValidID(newID)); + return newID; + } + + void uploadAllToGPU(GrContext* context) { +#if SK_SUPPORT_GPU + GrGpu* gpu = context->contextPriv().getGpu(); + SkASSERT(gpu); + + for (int i = 0; i < fImageInfo.count(); ++i) { + // DDL TODO: how can we tell if we need mipmapping! + fImageInfo[i].fBackendTexture = gpu->createTestingOnlyBackendTexture( + fImageInfo[i].fBitmap.getPixels(), + fImageInfo[i].fBitmap.width(), + fImageInfo[i].fBitmap.height(), + fImageInfo[i].fBitmap.colorType(), + false, GrMipMapped::kNo); + SkAssertResult(fImageInfo[i].fBackendTexture.isValid()); + } +#endif + } + + void cleanUpVRAM(GrContext* context) { +#if SK_SUPPORT_GPU + GrGpu* gpu = context->contextPriv().getGpu(); + SkASSERT(gpu); + + for (int i = 0; i < fImageInfo.count(); ++i) { + gpu->deleteTestingOnlyBackendTexture(fImageInfo[i].fBackendTexture); + } +#endif + } + + static void PromiseImageFulfillProc(void* textureContext, GrBackendTexture* outTexture) { +#if SK_SUPPORT_GPU + auto imgInfo = static_cast(textureContext); + + SkASSERT(imgInfo->fBackendTexture.isValid()); + *outTexture = imgInfo->fBackendTexture; +#endif + } + + static void PromiseImageReleaseProc(void* textureContext) { + // Do nothing. We free all the backend textures at the end in cleanUpVRAM. + } + + static void PromiseImageDoneProc(void* textureContext) { + // Do nothing. + } + +private: + // returns -1 if not found + int findImage(SkImage* image) const { + for (int i = 0; i < fImageInfo.count(); ++i) { + if (fImageInfo[i].fOriginalUniqueID == image->uniqueID()) { + SkASSERT(fImageInfo[i].fIndex == i); + SkASSERT(this->isValidID(i) && this->isValidID(fImageInfo[i].fIndex)); + return i; + } + } + return -1; + } + + // returns -1 on failure + int addImage(SkImage* image) { + SkASSERT(!fLocked); + + sk_sp rasterImage = image->makeRasterImage(); // force decoding of lazy images + + SkImageInfo ii = SkImageInfo::Make(rasterImage->width(), rasterImage->height(), + rasterImage->colorType(), rasterImage->alphaType(), + rasterImage->refColorSpace()); + + SkBitmap bm; + bm.allocPixels(ii); + + if (!rasterImage->readPixels(bm.pixmap(), 0, 0)) { + return -1; + } + + bm.setImmutable(); + + PromiseImageInfo newImageInfo; + newImageInfo.fIndex = fImageInfo.count(); + newImageInfo.fOriginalUniqueID = image->uniqueID(); + newImageInfo.fBitmap = bm; + /* fBackendTexture is filled in by uploadAllToGPU */ + + fImageInfo.push_back(newImageInfo); + SkASSERT(newImageInfo.fIndex == fImageInfo.count()-1); + return fImageInfo.count()-1; + } + + SkTArray fImageInfo; + bool fLocked; // are additions still allowed +}; + +// TileData class encapsulates the information and behavior for a single tile/thread in +// a DDL rendering. +class ViaDDL::TileData { +public: + // Note: we could just pass in surface characterization + TileData(sk_sp 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; + + { + PromiseImageCallbackContext callbackCtx = { &helper, &recorder }; + + SkDeserialProcs procs; + procs.fImageCtx = &callbackCtx; + procs.fImageProc = PromiseImageCreator; + + reconstitutedPicture = SkPicture::MakeFromData(compressedPictureData, &procs); + if (!reconstitutedPicture) { + return; + } + } + + subCanvas->clipRect(SkRect::MakeWH(fClip.width(), fClip.height())); + subCanvas->translate(-fClip.fLeft, -fClip.fTop); + + // Note: in this use case we only render a picture to the deferred canvas + // but, more generally, clients will use arbitrary draw calls. + subCanvas->drawPicture(reconstitutedPicture); + + fDisplayList = recorder.detach(); + } + + // This method operates serially and replays the recorded DDL into the tile surface. + void draw() { + fSurface->draw(fDisplayList.get()); + } + + // This method also operates serially and composes the results of replaying the DDL into + // the final destination surface. + void compose(SkCanvas* dst) { + sk_sp img = fSurface->makeImageSnapshot(); + dst->save(); + dst->clipRect(SkRect::Make(fClip)); + dst->drawImage(std::move(img), fClip.fLeft, fClip.fTop); + dst->restore(); + } + +private: + // This class lets us pass the collected image information and the DDLRecorder to the + // promise_image_creator callback when reconstituting a deflated SKP for a particular tile + // (i.e., in a thread). + class PromiseImageCallbackContext { + public: + const PromiseImageHelper* fHelper; + SkDeferredDisplayListRecorder* fRecorder; + }; + + // This generates promise images to replace the indices in the compressed picture. This + // reconstitution is performed separately in each thread so we end of with multiple + // promise image referring to the same GrBackendTexture. + // DDL TODO: Having multiple promise images using the same GrBackendTexture won't work in + // Vulkan! Move creation of the promise images to the main thread & SkImage. + static sk_sp PromiseImageCreator(const void* rawData, size_t length, void* ctxIn) { +#if SK_SUPPORT_GPU + PromiseImageCallbackContext* ctx = static_cast(ctxIn); + const PromiseImageHelper* helper = ctx->fHelper; + SkDeferredDisplayListRecorder* recorder = ctx->fRecorder; + + SkASSERT(length == sizeof(int)); + + const int* indexPtr = static_cast(rawData); + SkASSERT(helper->isValidID(*indexPtr)); + + const PromiseImageHelper::PromiseImageInfo* curImage = helper->getInfo(*indexPtr); + SkASSERT(curImage->fIndex == *indexPtr); + + GrBackendFormat backendFormat = curImage->fBackendTexture.format(); + + // DDL TODO: sort out mipmapping + sk_sp image = recorder->makePromiseTexture( + backendFormat, + curImage->fBitmap.width(), + curImage->fBitmap.height(), + GrMipMapped::kNo, + GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin, + curImage->fBitmap.colorType(), + curImage->fBitmap.alphaType(), + curImage->fBitmap.refColorSpace(), + PromiseImageHelper::PromiseImageFulfillProc, + PromiseImageHelper::PromiseImageReleaseProc, + PromiseImageHelper::PromiseImageDoneProc, + (void*) curImage); + SkASSERT(image); + return image; +#else + return nullptr; +#endif + } + + 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 { +#if SK_SUPPORT_GPU + auto size = src.size(); + SkPictureRecorder recorder; + Error err = src.draw(recorder.beginRecording(SkIntToScalar(size.width()), + SkIntToScalar(size.height()))); + if (!err.isEmpty()) { + return err; + } + sk_sp inputPicture(recorder.finishRecordingAsPicture()); + + // 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"); + } + } + + helper.lock(); // after this point no more images should be added to the helper + + return draw_to_canvas(fSink.get(), bitmap, stream, log, size, + [&](SkCanvas* canvas) -> Error { + GrContext* context = canvas->getGrContext(); + if (!context || !context->contextPriv().getGpu()) { + return SkStringPrintf("DDLs are GPU only"); + } + + helper.uploadAllToGPU(context); + + int xTileSize = viewport.width()/fNumDivisions; + int yTileSize = viewport.height()/fNumDivisions; + + SkTArray tileData; + tileData.reserve(fNumDivisions*fNumDivisions); + + // First, create the destination tiles + for (int y = 0, yOff = 0; y < fNumDivisions; ++y, yOff += yTileSize) { + int ySize = (y < fNumDivisions-1) ? yTileSize : viewport.height()-yOff; + + for (int x = 0, xOff = 0; x < fNumDivisions; ++x, xOff += xTileSize) { + int xSize = (x < fNumDivisions-1) ? xTileSize : viewport.width()-xOff; + + SkIRect clip = SkIRect::MakeXYWH(xOff, yOff, xSize, ySize); + + SkASSERT(viewport.contains(clip)); + + SkImageInfo tileII = SkImageInfo::MakeN32Premul(xSize, ySize); + + tileData.push_back(TileData(canvas->makeSurface(tileII), clip)); + } + } + + // Second, run the cpu pre-processing in threads + SkTaskGroup().batch(tileData.count(), [&](int i) { + tileData[i].preprocess(compressedPictureData.get(), helper); + }); + + // Third, synchronously render the display lists into the dest tiles + // TODO: it would be cool to not wait until all the tiles are drawn to begin + // drawing to the GPU and composing to the final surface + for (int i = 0; i < tileData.count(); ++i) { + tileData[i].draw(); + } + + // Finally, compose the drawn tiles into the result + // Note: the separation between the tiles and the final composition better + // matches Chrome but costs us a copy + for (int i = 0; i < tileData.count(); ++i) { + tileData[i].compose(canvas); + } + + // All promise images need to be fulfilled before leaving this method since we + // are about to delete their backing GrBackendTextures + // DDL TODO: remove the cleanUpVRAM method and use the release & done + // callbacks. + GrGpu* gpu = context->contextPriv().getGpu(); + gpu->testingOnly_flushGpuAndSync(); + + helper.cleanUpVRAM(context); + return ""; + }); +#else + return ""; +#endif +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + Error ViaPicture::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const { auto size = src.size(); return draw_to_canvas(fSink.get(), bitmap, stream, log, size, [&](SkCanvas* canvas) -> Error { -- cgit v1.2.3