|  | /* | 
|  | * Copyright 2014 Google Inc. | 
|  | * | 
|  | * Use of this source code is governed by a BSD-style license that can be | 
|  | * found in the LICENSE file. | 
|  | */ | 
|  |  | 
|  | #include "SkBigPicture.h" | 
|  | #include "SkCanvas.h" | 
|  | #include "SkImageFilterCache.h" | 
|  | #include "SkLayerInfo.h" | 
|  | #include "SkRecordDraw.h" | 
|  | #include "SkSpecialImage.h" | 
|  | #include "SkSurface.h" | 
|  |  | 
|  | #include "GrLayerHoister.h" | 
|  |  | 
|  | #if !defined(SK_IGNORE_GPU_LAYER_HOISTING) && SK_SUPPORT_GPU | 
|  |  | 
|  | #include "GrContext.h" | 
|  | #include "GrLayerCache.h" | 
|  | #include "GrRecordReplaceDraw.h" | 
|  |  | 
|  | // Create the layer information for the hoisted layer and secure the | 
|  | // required texture/render target resources. | 
|  | static void prepare_for_hoisting(GrLayerCache* layerCache, | 
|  | const SkPicture* topLevelPicture, | 
|  | const SkMatrix& initialMat, | 
|  | const SkLayerInfo::BlockInfo& info, | 
|  | const SkIRect& srcIR, | 
|  | const SkIRect& dstIR, | 
|  | SkTDArray<GrHoistedLayer>* needRendering, | 
|  | SkTDArray<GrHoistedLayer>* recycled, | 
|  | bool attemptToAtlas, | 
|  | int numSamples) { | 
|  | const SkPicture* pict = info.fPicture ? info.fPicture : topLevelPicture; | 
|  |  | 
|  | GrCachedLayer* layer = layerCache->findLayerOrCreate(topLevelPicture->uniqueID(), | 
|  | SkToInt(info.fSaveLayerOpID), | 
|  | SkToInt(info.fRestoreOpID), | 
|  | srcIR, | 
|  | dstIR, | 
|  | initialMat, | 
|  | info.fKey, | 
|  | info.fKeySize, | 
|  | info.fPaint); | 
|  | GrSurfaceDesc desc; | 
|  | desc.fFlags = kRenderTarget_GrSurfaceFlag; | 
|  | desc.fWidth = srcIR.width(); | 
|  | desc.fHeight = srcIR.height(); | 
|  | desc.fConfig = kSkia8888_GrPixelConfig; | 
|  | desc.fSampleCnt = numSamples; | 
|  |  | 
|  | bool locked, needsRendering; | 
|  | if (attemptToAtlas) { | 
|  | locked = layerCache->tryToAtlas(layer, desc, &needsRendering); | 
|  | } else { | 
|  | locked = layerCache->lock(layer, desc, &needsRendering); | 
|  | } | 
|  | if (!locked) { | 
|  | // GPU resources could not be secured for the hoisting of this layer | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (attemptToAtlas) { | 
|  | SkASSERT(layer->isAtlased()); | 
|  | } | 
|  |  | 
|  | GrHoistedLayer* hl; | 
|  |  | 
|  | if (needsRendering) { | 
|  | if (!attemptToAtlas) { | 
|  | SkASSERT(!layer->isAtlased()); | 
|  | } | 
|  | hl = needRendering->append(); | 
|  | } else { | 
|  | hl = recycled->append(); | 
|  | } | 
|  |  | 
|  | layerCache->addUse(layer); | 
|  | hl->fLayer = layer; | 
|  | hl->fPicture = pict; | 
|  | hl->fLocalMat = info.fLocalMat; | 
|  | hl->fInitialMat = initialMat; | 
|  | hl->fPreMat = initialMat; | 
|  | hl->fPreMat.preConcat(info.fPreMat); | 
|  | } | 
|  |  | 
|  | // Compute the source rect and return false if it is empty. | 
|  | static bool compute_source_rect(const SkLayerInfo::BlockInfo& info, const SkMatrix& initialMat, | 
|  | const SkIRect& dstIR, SkIRect* srcIR) { | 
|  | SkIRect clipBounds = dstIR; | 
|  |  | 
|  | SkMatrix totMat = initialMat; | 
|  | totMat.preConcat(info.fPreMat); | 
|  | totMat.preConcat(info.fLocalMat); | 
|  |  | 
|  | if (info.fPaint && info.fPaint->getImageFilter()) { | 
|  | clipBounds = info.fPaint->getImageFilter()->filterBounds(clipBounds, totMat); | 
|  | } | 
|  |  | 
|  | if (!info.fSrcBounds.isEmpty()) { | 
|  | SkRect r; | 
|  |  | 
|  | totMat.mapRect(&r, info.fSrcBounds); | 
|  | r.roundOut(srcIR); | 
|  |  | 
|  | if (!srcIR->intersect(clipBounds)) { | 
|  | return false; | 
|  | } | 
|  | } else { | 
|  | *srcIR = clipBounds; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Atlased layers must be small enough to fit in the atlas, not have a | 
|  | // paint with an image filter and be neither nested nor nesting. | 
|  | // TODO: allow leaf nested layers to appear in the atlas. | 
|  | void GrLayerHoister::FindLayersToAtlas(GrContext* context, | 
|  | const SkPicture* topLevelPicture, | 
|  | const SkMatrix& initialMat, | 
|  | const SkRect& query, | 
|  | SkTDArray<GrHoistedLayer>* atlased, | 
|  | SkTDArray<GrHoistedLayer>* recycled, | 
|  | int numSamples) { | 
|  | if (0 != numSamples) { | 
|  | // MSAA layers are currently never atlased | 
|  | return; | 
|  | } | 
|  |  | 
|  | GrLayerCache* layerCache = context->getLayerCache(); | 
|  | layerCache->processDeletedPictures(); | 
|  |  | 
|  | const SkBigPicture::AccelData* topLevelData = nullptr; | 
|  | if (const SkBigPicture* bp = topLevelPicture->asSkBigPicture()) { | 
|  | topLevelData = bp->accelData(); | 
|  | } | 
|  | if (!topLevelData) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | const SkLayerInfo *topLevelGPUData = static_cast<const SkLayerInfo*>(topLevelData); | 
|  | if (0 == topLevelGPUData->numBlocks()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | atlased->setReserve(atlased->count() + topLevelGPUData->numBlocks()); | 
|  |  | 
|  | for (int i = 0; i < topLevelGPUData->numBlocks(); ++i) { | 
|  | const SkLayerInfo::BlockInfo& info = topLevelGPUData->block(i); | 
|  |  | 
|  | // TODO: ignore perspective projected layers here? | 
|  | bool disallowAtlasing = info.fHasNestedLayers || info.fIsNested || | 
|  | (info.fPaint && info.fPaint->getImageFilter()); | 
|  |  | 
|  | if (disallowAtlasing) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | SkRect layerRect; | 
|  | initialMat.mapRect(&layerRect, info.fBounds); | 
|  | if (!layerRect.intersect(query)) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | const SkIRect dstIR = layerRect.roundOut(); | 
|  |  | 
|  | SkIRect srcIR; | 
|  |  | 
|  | if (!compute_source_rect(info, initialMat, dstIR, &srcIR) || | 
|  | !GrLayerCache::PlausiblyAtlasable(srcIR.width(), srcIR.height())) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | prepare_for_hoisting(layerCache, topLevelPicture, initialMat, | 
|  | info, srcIR, dstIR, atlased, recycled, true, 0); | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | void GrLayerHoister::FindLayersToHoist(GrContext* context, | 
|  | const SkPicture* topLevelPicture, | 
|  | const SkMatrix& initialMat, | 
|  | const SkRect& query, | 
|  | SkTDArray<GrHoistedLayer>* needRendering, | 
|  | SkTDArray<GrHoistedLayer>* recycled, | 
|  | int numSamples) { | 
|  | GrLayerCache* layerCache = context->getLayerCache(); | 
|  |  | 
|  | layerCache->processDeletedPictures(); | 
|  |  | 
|  | const SkBigPicture::AccelData* topLevelData = nullptr; | 
|  | if (const SkBigPicture* bp = topLevelPicture->asSkBigPicture()) { | 
|  | topLevelData = bp->accelData(); | 
|  | } | 
|  | if (!topLevelData) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | const SkLayerInfo *topLevelGPUData = static_cast<const SkLayerInfo*>(topLevelData); | 
|  | if (0 == topLevelGPUData->numBlocks()) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Find and prepare for hoisting all the layers that intersect the query rect | 
|  | for (int i = 0; i < topLevelGPUData->numBlocks(); ++i) { | 
|  | const SkLayerInfo::BlockInfo& info = topLevelGPUData->block(i); | 
|  | if (info.fIsNested) { | 
|  | // Parent layers are currently hoisted while nested layers are not. | 
|  | continue; | 
|  | } | 
|  |  | 
|  | SkRect layerRect; | 
|  | initialMat.mapRect(&layerRect, info.fBounds); | 
|  | if (!layerRect.intersect(query)) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | const SkIRect dstIR = layerRect.roundOut(); | 
|  |  | 
|  | SkIRect srcIR; | 
|  | if (!compute_source_rect(info, initialMat, dstIR, &srcIR)) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | prepare_for_hoisting(layerCache, topLevelPicture, initialMat, info, srcIR, dstIR, | 
|  | needRendering, recycled, false, numSamples); | 
|  | } | 
|  | } | 
|  |  | 
|  | void GrLayerHoister::DrawLayersToAtlas(GrContext* context, | 
|  | const SkTDArray<GrHoistedLayer>& atlased) { | 
|  | if (atlased.count() > 0) { | 
|  | // All the atlased layers are rendered into the same GrTexture | 
|  | SkSurfaceProps props(0, kUnknown_SkPixelGeometry); | 
|  | sk_sp<SkSurface> surface(SkSurface::MakeRenderTargetDirect( | 
|  | atlased[0].fLayer->texture()->asRenderTarget(), &props)); | 
|  |  | 
|  | SkCanvas* atlasCanvas = surface->getCanvas(); | 
|  |  | 
|  | for (int i = 0; i < atlased.count(); ++i) { | 
|  | const GrCachedLayer* layer = atlased[i].fLayer; | 
|  | const SkBigPicture* pict = atlased[i].fPicture->asSkBigPicture(); | 
|  | if (!pict) { | 
|  | // TODO: can we assume / assert this? | 
|  | continue; | 
|  | } | 
|  | const SkIPoint offset = SkIPoint::Make(layer->srcIR().fLeft, layer->srcIR().fTop); | 
|  | SkDEBUGCODE(const SkPaint* layerPaint = layer->paint();) | 
|  |  | 
|  | SkASSERT(!layerPaint || !layerPaint->getImageFilter()); | 
|  | SkASSERT(!layer->filter()); | 
|  |  | 
|  | atlasCanvas->save(); | 
|  |  | 
|  | // Add a rect clip to make sure the rendering doesn't | 
|  | // extend beyond the boundaries of the atlased sub-rect | 
|  | const SkRect bound = SkRect::Make(layer->rect()); | 
|  | atlasCanvas->clipRect(bound); | 
|  | atlasCanvas->clear(0); | 
|  |  | 
|  | // '-offset' maps the layer's top/left to the origin. | 
|  | // Since this layer is atlased, the top/left corner needs | 
|  | // to be offset to the correct location in the backing texture. | 
|  | SkMatrix initialCTM; | 
|  | initialCTM.setTranslate(SkIntToScalar(-offset.fX), SkIntToScalar(-offset.fY)); | 
|  | initialCTM.preTranslate(bound.fLeft, bound.fTop); | 
|  | initialCTM.preConcat(atlased[i].fPreMat); | 
|  |  | 
|  | atlasCanvas->setMatrix(initialCTM); | 
|  | atlasCanvas->concat(atlased[i].fLocalMat); | 
|  |  | 
|  | pict->partialPlayback(atlasCanvas, layer->start() + 1, layer->stop(), initialCTM); | 
|  | atlasCanvas->restore(); | 
|  | } | 
|  |  | 
|  | atlasCanvas->flush(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void GrLayerHoister::FilterLayer(GrContext* context, | 
|  | const SkSurfaceProps* props, | 
|  | const GrHoistedLayer& info) { | 
|  | GrCachedLayer* layer = info.fLayer; | 
|  |  | 
|  | SkASSERT(layer->filter()); | 
|  |  | 
|  | static const int kDefaultCacheSize = 32 * 1024 * 1024; | 
|  |  | 
|  | const SkIPoint filterOffset = SkIPoint::Make(layer->srcIR().fLeft, layer->srcIR().fTop); | 
|  |  | 
|  | SkMatrix totMat(info.fPreMat); | 
|  | totMat.preConcat(info.fLocalMat); | 
|  | totMat.postTranslate(-SkIntToScalar(filterOffset.fX), -SkIntToScalar(filterOffset.fY)); | 
|  |  | 
|  | SkASSERT(0 == layer->rect().fLeft && 0 == layer->rect().fTop); | 
|  | const SkIRect& clipBounds = layer->rect(); | 
|  |  | 
|  | // This cache is transient, and is freed (along with all its contained | 
|  | // textures) when it goes out of scope. | 
|  | SkAutoTUnref<SkImageFilterCache> cache(SkImageFilterCache::Create(kDefaultCacheSize)); | 
|  | SkImageFilter::Context filterContext(totMat, clipBounds, cache); | 
|  |  | 
|  | // TODO: should the layer hoister store stand alone layers as SkSpecialImages internally? | 
|  | SkASSERT(layer->rect().width() == layer->texture()->width() && | 
|  | layer->rect().height() == layer->texture()->height()); | 
|  | const SkIRect subset = SkIRect::MakeWH(layer->rect().width(), layer->rect().height()); | 
|  | sk_sp<SkSpecialImage> img(SkSpecialImage::MakeFromGpu(subset, | 
|  | kNeedNewImageUniqueID_SpecialImage, | 
|  | sk_ref_sp(layer->texture()), | 
|  | props)); | 
|  |  | 
|  | SkIPoint offset = SkIPoint::Make(0, 0); | 
|  | sk_sp<SkSpecialImage> result(layer->filter()->filterImage(img.get(), | 
|  | filterContext, | 
|  | &offset)); | 
|  | if (!result) { | 
|  | // Filtering failed. Press on with the unfiltered version. | 
|  | return; | 
|  | } | 
|  |  | 
|  | SkASSERT(result->isTextureBacked()); | 
|  | sk_sp<GrTexture> texture(result->asTextureRef(context)); | 
|  | layer->setTexture(texture.get(), result->subset(), false); | 
|  | layer->setOffset(offset); | 
|  | } | 
|  |  | 
|  | void GrLayerHoister::DrawLayers(GrContext* context, const SkTDArray<GrHoistedLayer>& layers) { | 
|  | for (int i = 0; i < layers.count(); ++i) { | 
|  | GrCachedLayer* layer = layers[i].fLayer; | 
|  | const SkBigPicture* pict = layers[i].fPicture->asSkBigPicture(); | 
|  | if (!pict) { | 
|  | // TODO: can we assume / assert this? | 
|  | continue; | 
|  | } | 
|  | const SkIPoint offset = SkIPoint::Make(layer->srcIR().fLeft, layer->srcIR().fTop); | 
|  |  | 
|  | // Each non-atlased layer has its own GrTexture | 
|  | SkSurfaceProps props(0, kUnknown_SkPixelGeometry); | 
|  | auto surface(SkSurface::MakeRenderTargetDirect( | 
|  | layer->texture()->asRenderTarget(), &props)); | 
|  |  | 
|  | SkCanvas* layerCanvas = surface->getCanvas(); | 
|  |  | 
|  | SkASSERT(0 == layer->rect().fLeft && 0 == layer->rect().fTop); | 
|  |  | 
|  | // Add a rect clip to make sure the rendering doesn't | 
|  | // extend beyond the boundaries of the layer | 
|  | const SkRect bound = SkRect::Make(layer->rect()); | 
|  | layerCanvas->clipRect(bound); | 
|  | layerCanvas->clear(SK_ColorTRANSPARENT); | 
|  |  | 
|  | SkMatrix initialCTM; | 
|  | initialCTM.setTranslate(SkIntToScalar(-offset.fX), SkIntToScalar(-offset.fY)); | 
|  | initialCTM.preConcat(layers[i].fPreMat); | 
|  |  | 
|  | layerCanvas->setMatrix(initialCTM); | 
|  | layerCanvas->concat(layers[i].fLocalMat); | 
|  |  | 
|  | pict->partialPlayback(layerCanvas, layer->start()+1, layer->stop(), initialCTM); | 
|  | layerCanvas->flush(); | 
|  |  | 
|  | if (layer->filter()) { | 
|  | FilterLayer(context, &surface->props(), layers[i]); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void GrLayerHoister::UnlockLayers(GrContext* context, | 
|  | const SkTDArray<GrHoistedLayer>& layers) { | 
|  | GrLayerCache* layerCache = context->getLayerCache(); | 
|  |  | 
|  | for (int i = 0; i < layers.count(); ++i) { | 
|  | layerCache->removeUse(layers[i].fLayer); | 
|  | } | 
|  |  | 
|  | SkDEBUGCODE(layerCache->validate();) | 
|  | } | 
|  |  | 
|  | void GrLayerHoister::Begin(GrContext* context) { | 
|  | GrLayerCache* layerCache = context->getLayerCache(); | 
|  |  | 
|  | layerCache->begin(); | 
|  | } | 
|  |  | 
|  | void GrLayerHoister::End(GrContext* context) { | 
|  | GrLayerCache* layerCache = context->getLayerCache(); | 
|  |  | 
|  | #if !GR_CACHE_HOISTED_LAYERS | 
|  |  | 
|  | // This code completely clears out the atlas. It is required when | 
|  | // caching is disabled so the atlas doesn't fill up and force more | 
|  | // free floating layers | 
|  | layerCache->purgeAll(); | 
|  | #endif | 
|  |  | 
|  | layerCache->end(); | 
|  | } | 
|  |  | 
|  | #endif |