/* * 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 "GrLayerCache.h" #include "GrLayerHoister.h" #include "SkCanvas.h" #include "SkRecordDraw.h" #include "GrRecordReplaceDraw.h" #include "SkGrPixelRef.h" #include "SkSurface.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 GrAccelData::SaveLayerInfo& info, SkTDArray* atlased, SkTDArray* nonAtlased, SkTDArray* recycled) { const SkPicture* pict = info.fPicture ? info.fPicture : topLevelPicture; GrCachedLayer* layer = layerCache->findLayerOrCreate(pict->uniqueID(), info.fSaveLayerOpID, info.fRestoreOpID, info.fOriginXform, info.fPaint); GrTextureDesc desc; desc.fFlags = kRenderTarget_GrTextureFlagBit; desc.fWidth = info.fSize.fWidth; desc.fHeight = info.fSize.fHeight; desc.fConfig = kSkia8888_GrPixelConfig; // TODO: need to deal with sample count bool needsRendering = layerCache->lock(layer, desc, info.fHasNestedLayers || info.fIsNested); if (NULL == layer->texture()) { // GPU resources could not be secured for the hoisting of this layer return; } GrHoistedLayer* hl; if (needsRendering) { if (layer->isAtlased()) { hl = atlased->append(); } else { hl = nonAtlased->append(); } } else { hl = recycled->append(); } layerCache->addUse(layer); hl->fLayer = layer; hl->fPicture = pict; hl->fOffset = info.fOffset; hl->fCTM = info.fOriginXform; } // Return true if any layers are suitable for hoisting bool GrLayerHoister::FindLayersToHoist(GrContext* context, const SkPicture* topLevelPicture, const SkRect& query, SkTDArray* atlased, SkTDArray* nonAtlased, SkTDArray* recycled) { GrLayerCache* layerCache = context->getLayerCache(); layerCache->processDeletedPictures(); SkPicture::AccelData::Key key = GrAccelData::ComputeAccelDataKey(); const SkPicture::AccelData* topLevelData = topLevelPicture->EXPERIMENTAL_getAccelData(key); if (!topLevelData) { return false; } const GrAccelData *topLevelGPUData = static_cast(topLevelData); if (0 == topLevelGPUData->numSaveLayers()) { return false; } bool anyHoisted = false; // The layer hoisting code will pre-render and cache an entire layer if most // of it is being used (~70%) and it will fit in a texture. This is to allow // such layers to be re-used for different clips/tiles. // Small layers will additionally be atlased. // The only limitation right now is that nested layers are currently not hoisted. // Parent layers are hoisted but are never atlased (so that we never swap // away from the atlas rendertarget when generating the hoisted layers). atlased->setReserve(atlased->count() + topLevelGPUData->numSaveLayers()); // Find and prepare for hoisting all the layers that intersect the query rect for (int i = 0; i < topLevelGPUData->numSaveLayers(); ++i) { const GrAccelData::SaveLayerInfo& info = topLevelGPUData->saveLayerInfo(i); SkRect layerRect = SkRect::MakeXYWH(SkIntToScalar(info.fOffset.fX), SkIntToScalar(info.fOffset.fY), SkIntToScalar(info.fSize.fWidth), SkIntToScalar(info.fSize.fHeight)); if (!SkRect::Intersects(query, layerRect)) { continue; } // TODO: ignore perspective projected layers here! // TODO: once this code is more stable unsuitable layers can // just be omitted during the optimization stage if (info.fIsNested) { continue; } prepare_for_hoisting(layerCache, topLevelPicture, info, atlased, nonAtlased, recycled); anyHoisted = true; } return anyHoisted; } static void wrap_texture(GrTexture* texture, int width, int height, SkBitmap* result) { SkImageInfo info = SkImageInfo::MakeN32Premul(width, height); result->setInfo(info); result->setPixelRef(SkNEW_ARGS(SkGrPixelRef, (info, texture)))->unref(); } static void convert_layers_to_replacements(const SkTDArray& layers, GrReplacements* replacements) { // TODO: just replace GrReplacements::ReplacementInfo with GrCachedLayer? for (int i = 0; i < layers.count(); ++i) { GrCachedLayer* layer = layers[i].fLayer; const SkPicture* picture = layers[i].fPicture; GrReplacements::ReplacementInfo* layerInfo = replacements->newReplacement(picture->uniqueID(), layer->start(), layers[i].fCTM); layerInfo->fStop = layer->stop(); layerInfo->fPos = layers[i].fOffset; SkBitmap bm; wrap_texture(layers[i].fLayer->texture(), !layers[i].fLayer->isAtlased() ? layers[i].fLayer->rect().width() : layers[i].fLayer->texture()->width(), !layers[i].fLayer->isAtlased() ? layers[i].fLayer->rect().height() : layers[i].fLayer->texture()->height(), &bm); layerInfo->fImage = SkImage::NewTexture(bm); layerInfo->fPaint = layers[i].fLayer->paint() ? SkNEW_ARGS(SkPaint, (*layers[i].fLayer->paint())) : NULL; layerInfo->fSrcRect = SkIRect::MakeXYWH(layers[i].fLayer->rect().fLeft, layers[i].fLayer->rect().fTop, layers[i].fLayer->rect().width(), layers[i].fLayer->rect().height()); } } void GrLayerHoister::DrawLayers(const SkTDArray& atlased, const SkTDArray& nonAtlased, const SkTDArray& recycled, GrReplacements* replacements) { // Render the atlased layers that require it if (atlased.count() > 0) { // All the atlased layers are rendered into the same GrTexture SkAutoTUnref surface(SkSurface::NewRenderTargetDirect( atlased[0].fLayer->texture()->asRenderTarget(), NULL)); SkCanvas* atlasCanvas = surface->getCanvas(); SkPaint paint; paint.setColor(SK_ColorTRANSPARENT); paint.setXfermode(SkXfermode::Create(SkXfermode::kSrc_Mode))->unref(); for (int i = 0; i < atlased.count(); ++i) { GrCachedLayer* layer = atlased[i].fLayer; const SkPicture* pict = atlased[i].fPicture; const SkIPoint offset = atlased[i].fOffset; atlasCanvas->save(); // Add a rect clip to make sure the rendering doesn't // extend beyond the boundaries of the atlased sub-rect SkRect bound = SkRect::MakeXYWH(SkIntToScalar(layer->rect().fLeft), SkIntToScalar(layer->rect().fTop), SkIntToScalar(layer->rect().width()), SkIntToScalar(layer->rect().height())); atlasCanvas->clipRect(bound); // Since 'clear' doesn't respect the clip we need to draw a rect // TODO: ensure none of the atlased layers contain a clear call! atlasCanvas->drawRect(bound, paint); // info.fCTM 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.postTranslate(bound.fLeft, bound.fTop); atlasCanvas->translate(SkIntToScalar(-offset.fX), SkIntToScalar(-offset.fY)); atlasCanvas->translate(bound.fLeft, bound.fTop); atlasCanvas->concat(atlased[i].fCTM); SkRecordPartialDraw(*pict->fRecord.get(), atlasCanvas, bound, layer->start()+1, layer->stop(), initialCTM); atlasCanvas->restore(); } atlasCanvas->flush(); } // Render the non-atlased layers that require it for (int i = 0; i < nonAtlased.count(); ++i) { GrCachedLayer* layer = nonAtlased[i].fLayer; const SkPicture* pict = nonAtlased[i].fPicture; const SkIPoint offset = nonAtlased[i].fOffset; // Each non-atlased layer has its own GrTexture SkAutoTUnref surface(SkSurface::NewRenderTargetDirect( layer->texture()->asRenderTarget(), NULL)); SkCanvas* layerCanvas = surface->getCanvas(); // Add a rect clip to make sure the rendering doesn't // extend beyond the boundaries of the atlased sub-rect SkRect bound = SkRect::MakeXYWH(SkIntToScalar(layer->rect().fLeft), SkIntToScalar(layer->rect().fTop), SkIntToScalar(layer->rect().width()), SkIntToScalar(layer->rect().height())); layerCanvas->clipRect(bound); // TODO: still useful? layerCanvas->clear(SK_ColorTRANSPARENT); SkMatrix initialCTM; initialCTM.setTranslate(SkIntToScalar(-offset.fX), SkIntToScalar(-offset.fY)); layerCanvas->translate(SkIntToScalar(-offset.fX), SkIntToScalar(-offset.fY)); layerCanvas->concat(nonAtlased[i].fCTM); SkRecordPartialDraw(*pict->fRecord.get(), layerCanvas, bound, layer->start()+1, layer->stop(), initialCTM); layerCanvas->flush(); } convert_layers_to_replacements(atlased, replacements); convert_layers_to_replacements(nonAtlased, replacements); convert_layers_to_replacements(recycled, replacements); } void GrLayerHoister::UnlockLayers(GrContext* context, const SkTDArray& atlased, const SkTDArray& nonAtlased, const SkTDArray& recycled) { GrLayerCache* layerCache = context->getLayerCache(); for (int i = 0; i < atlased.count(); ++i) { layerCache->removeUse(atlased[i].fLayer); } for (int i = 0; i < nonAtlased.count(); ++i) { layerCache->removeUse(nonAtlased[i].fLayer); } for (int i = 0; i < recycled.count(); ++i) { layerCache->removeUse(recycled[i].fLayer); } #if DISABLE_CACHING // 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 SkDEBUGCODE(layerCache->validate();) }