Loading libs/hwui/Android.bp +3 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ cc_defaults { "external/skia/src/effects", "external/skia/src/image", "external/skia/src/utils", "external/skia/src/gpu", ], product_variables: { Loading Loading @@ -133,6 +134,7 @@ cc_defaults { "pipeline/skia/SkiaProfileRenderer.cpp", "pipeline/skia/SkiaRecordingCanvas.cpp", "pipeline/skia/SkiaVulkanPipeline.cpp", "pipeline/skia/VectorDrawableAtlas.cpp", "renderstate/Blend.cpp", "renderstate/MeshState.cpp", "renderstate/OffscreenBufferPool.cpp", Loading Loading @@ -340,6 +342,7 @@ cc_test { "tests/unit/TextureCacheTests.cpp", "tests/unit/TypefaceTests.cpp", "tests/unit/VectorDrawableTests.cpp", "tests/unit/VectorDrawableAtlasTests.cpp", ], } Loading libs/hwui/VectorDrawable.cpp +89 −20 Original line number Diff line number Diff line Loading @@ -491,34 +491,103 @@ Bitmap& Tree::getBitmapUpdateIfDirty() { return *mCache.bitmap; } void Tree::updateCache(sk_sp<SkSurface> surface) { if (surface.get()) { mCache.surface = surface; } if (surface.get() || mCache.dirty) { SkSurface* vdSurface = mCache.surface.get(); SkCanvas* canvas = vdSurface->getCanvas(); float scaleX = vdSurface->width() / mProperties.getViewportWidth(); float scaleY = vdSurface->height() / mProperties.getViewportHeight(); void Tree::updateCache(sp<skiapipeline::VectorDrawableAtlas>& atlas, GrContext* context) { SkRect dst; sk_sp<SkSurface> surface = mCache.getSurface(&dst); bool canReuseSurface = surface && dst.width() >= mProperties.getScaledWidth() && dst.height() >= mProperties.getScaledHeight(); if (!canReuseSurface) { int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth()); int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight()); auto atlasEntry = atlas->requestNewEntry(scaledWidth, scaledHeight, context); if (INVALID_ATLAS_KEY != atlasEntry.key) { dst = atlasEntry.rect; surface = atlasEntry.surface; mCache.setAtlas(atlas, atlasEntry.key); } else { //don't draw, if we failed to allocate an offscreen buffer mCache.clear(); surface.reset(); } } if (!canReuseSurface || mCache.dirty) { draw(surface.get(), dst); mCache.dirty = false; } } void Tree::draw(SkSurface* surface, const SkRect& dst) { if (surface) { SkCanvas* canvas = surface->getCanvas(); float scaleX = dst.width() / mProperties.getViewportWidth(); float scaleY = dst.height() / mProperties.getViewportHeight(); SkAutoCanvasRestore acr(canvas, true); canvas->translate(dst.fLeft, dst.fTop); canvas->clipRect(SkRect::MakeWH(dst.width(), dst.height())); canvas->clear(SK_ColorTRANSPARENT); canvas->scale(scaleX, scaleY); mRootNode->draw(canvas, false); mCache.dirty = false; canvas->flush(); } } void Tree::Cache::setAtlas(sp<skiapipeline::VectorDrawableAtlas> newAtlas, skiapipeline::AtlasKey newAtlasKey) { LOG_ALWAYS_FATAL_IF(newAtlasKey == INVALID_ATLAS_KEY); clear(); mAtlas = newAtlas; mAtlasKey = newAtlasKey; } sk_sp<SkSurface> Tree::Cache::getSurface(SkRect* bounds) { sk_sp<SkSurface> surface; sp<skiapipeline::VectorDrawableAtlas> atlas = mAtlas.promote(); if (atlas.get() && mAtlasKey != INVALID_ATLAS_KEY) { auto atlasEntry = atlas->getEntry(mAtlasKey); *bounds = atlasEntry.rect; surface = atlasEntry.surface; mAtlasKey = atlasEntry.key; } return surface; } void Tree::Cache::clear() { sp<skiapipeline::VectorDrawableAtlas> lockAtlas = mAtlas.promote(); if (lockAtlas.get()) { lockAtlas->releaseEntry(mAtlasKey); } mAtlas = nullptr; mAtlasKey = INVALID_ATLAS_KEY; } void Tree::draw(SkCanvas* canvas) { /* * TODO address the following... * * 1) figure out how to set path's as volatile during animation * 2) if mRoot->getPaint() != null either promote to layer (during * animation) or cache in SkSurface (for static content) */ canvas->drawImageRect(mCache.surface->makeImageSnapshot().get(), SkRect src; sk_sp<SkSurface> vdSurface = mCache.getSurface(&src); if (vdSurface) { canvas->drawImageRect(vdSurface->makeImageSnapshot().get(), src, mutateProperties()->getBounds(), getPaint()); } else { // Handle the case when VectorDrawableAtlas has been destroyed, because of memory pressure. // We render the VD into a temporary standalone buffer and mark the frame as dirty. Next // frame will be cached into the atlas. int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth()); int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight()); SkRect src = SkRect::MakeWH(scaledWidth, scaledHeight); #ifndef ANDROID_ENABLE_LINEAR_BLENDING sk_sp<SkColorSpace> colorSpace = nullptr; #else sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB(); #endif SkImageInfo info = SkImageInfo::MakeN32(scaledWidth, scaledHeight, kPremul_SkAlphaType, colorSpace); sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(canvas->getGrContext(), SkBudgeted::kYes, info); draw(surface.get(), src); mCache.clear(); canvas->drawImageRect(surface->makeImageSnapshot().get(), mutateProperties()->getBounds(), getPaint()); markDirty(); } } void Tree::updateBitmapCache(Bitmap& bitmap, bool useStagingData) { Loading libs/hwui/VectorDrawable.h +43 −16 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ #include "hwui/Canvas.h" #include "hwui/Bitmap.h" #include "renderthread/CacheManager.h" #include "DisplayList.h" #include <SkBitmap.h> Loading Loading @@ -687,35 +688,61 @@ public: bool getPropertyChangeWillBeConsumed() const { return mWillBeConsumed; } void setPropertyChangeWillBeConsumed(bool willBeConsumed) { mWillBeConsumed = willBeConsumed; } // Returns true if VD cache surface is big enough. This should always be called from RT and it // works with Skia pipelines only. bool canReuseSurface() { SkSurface* surface = mCache.surface.get(); return surface && surface->width() >= mProperties.getScaledWidth() && surface->height() >= mProperties.getScaledHeight(); } // Draws VD cache into a canvas. This should always be called from RT and it works with Skia // pipelines only. /** * Draws VD cache into a canvas. This should always be called from RT and it works with Skia * pipelines only. */ void draw(SkCanvas* canvas); // Draws VD into a GPU backed surface. If canReuseSurface returns false, then "surface" must // contain a new surface. This should always be called from RT and it works with Skia pipelines // only. void updateCache(sk_sp<SkSurface> surface); /** * Draws VD into a GPU backed surface. * This should always be called from RT and it works with Skia pipeline only. */ void updateCache(sp<skiapipeline::VectorDrawableAtlas>& atlas, GrContext* context); private: struct Cache { class Cache { public: sk_sp<Bitmap> bitmap; //used by HWUI pipeline and software //TODO: use surface instead of bitmap when drawing in software canvas sk_sp<SkSurface> surface; //used only by Skia pipelines bool dirty = true; // the rest of the code in Cache is used by Skia pipelines only ~Cache() { clear(); } /** * Stores a weak pointer to the atlas and a key. */ void setAtlas(sp<skiapipeline::VectorDrawableAtlas> atlas, skiapipeline::AtlasKey newAtlasKey); /** * Gets a surface and bounds from the atlas. * * @return nullptr if the altas has been deleted. */ sk_sp<SkSurface> getSurface(SkRect* bounds); /** * Releases atlas key from the atlas, which makes it available for reuse. */ void clear(); private: wp<skiapipeline::VectorDrawableAtlas> mAtlas; skiapipeline::AtlasKey mAtlasKey = INVALID_ATLAS_KEY; }; SkPaint* updatePaint(SkPaint* outPaint, TreeProperties* prop); bool allocateBitmapIfNeeded(Cache& cache, int width, int height); bool canReuseBitmap(Bitmap*, int width, int height); void updateBitmapCache(Bitmap& outCache, bool useStagingData); /** * Draws the root node into "surface" at a given "dst" position. */ void draw(SkSurface* surface, const SkRect& dst); // Cap the bitmap size, such that it won't hurt the performance too much // and it won't crash due to a very large scale. // The drawable will look blurry above this size. Loading libs/hwui/pipeline/skia/SkiaPipeline.cpp +8 −18 Original line number Diff line number Diff line Loading @@ -183,27 +183,17 @@ public: }; void SkiaPipeline::renderVectorDrawableCache() { //render VectorDrawables into offscreen buffers if (!mVectorDrawables.empty()) { sp<VectorDrawableAtlas> atlas = mRenderThread.cacheManager().acquireVectorDrawableAtlas(); auto grContext = mRenderThread.getGrContext(); atlas->prepareForDraw(grContext); for (auto vd : mVectorDrawables) { sk_sp<SkSurface> surface; if (!vd->canReuseSurface()) { #ifndef ANDROID_ENABLE_LINEAR_BLENDING sk_sp<SkColorSpace> colorSpace = nullptr; #else sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB(); #endif int scaledWidth = SkScalarCeilToInt(vd->properties().getScaledWidth()); int scaledHeight = SkScalarCeilToInt(vd->properties().getScaledHeight()); SkImageInfo info = SkImageInfo::MakeN32(scaledWidth, scaledHeight, kPremul_SkAlphaType, colorSpace); SkASSERT(mRenderThread.getGrContext() != nullptr); surface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes, info); } vd->updateCache(surface); vd->updateCache(atlas, grContext); } grContext->flush(); mVectorDrawables.clear(); } } void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, bool opaque, bool wideColorGamut, Loading libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp 0 → 100644 +269 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "VectorDrawableAtlas.h" #include <GrRectanizer_pow2.h> #include <SkCanvas.h> #include <cmath> #include "utils/TraceUtils.h" #include "renderthread/RenderProxy.h" namespace android { namespace uirenderer { namespace skiapipeline { VectorDrawableAtlas::VectorDrawableAtlas(size_t surfaceArea, StorageMode storageMode) : mWidth((int)std::sqrt(surfaceArea)) , mHeight((int)std::sqrt(surfaceArea)) , mStorageMode(storageMode) { } void VectorDrawableAtlas::prepareForDraw(GrContext* context) { if (StorageMode::allowSharedSurface == mStorageMode) { if (!mSurface) { mSurface = createSurface(mWidth, mHeight, context); mRectanizer = std::make_unique<GrRectanizerPow2>(mWidth, mHeight); mPixelUsedByVDs = 0; mPixelAllocated = 0; mConsecutiveFailures = 0; mFreeRects.clear(); } else { if (isFragmented()) { // Invoke repack outside renderFrame to avoid jank. renderthread::RenderProxy::repackVectorDrawableAtlas(); } } } } #define MAX_CONSECUTIVE_FAILURES 5 #define MAX_UNUSED_RATIO 2.0f bool VectorDrawableAtlas::isFragmented() { return mConsecutiveFailures > MAX_CONSECUTIVE_FAILURES && mPixelUsedByVDs*MAX_UNUSED_RATIO < mPixelAllocated; } void VectorDrawableAtlas::repackIfNeeded(GrContext* context) { // We repackage when atlas failed to allocate space MAX_CONSECUTIVE_FAILURES consecutive // times and the atlas allocated pixels are at least MAX_UNUSED_RATIO times higher than pixels // used by atlas VDs. if (isFragmented() && mSurface) { repack(context); } } // compare to CacheEntry objects based on VD area. bool VectorDrawableAtlas::compareCacheEntry(const CacheEntry& first, const CacheEntry& second) { return first.VDrect.width()*first.VDrect.height() < second.VDrect.width()*second.VDrect.height(); } void VectorDrawableAtlas::repack(GrContext* context) { ATRACE_CALL(); sk_sp<SkSurface> newSurface; SkCanvas* canvas = nullptr; if (StorageMode::allowSharedSurface == mStorageMode) { newSurface = createSurface(mWidth, mHeight, context); if (!newSurface) { return; } canvas = newSurface->getCanvas(); canvas->clear(SK_ColorTRANSPARENT); mRectanizer = std::make_unique<GrRectanizerPow2>(mWidth, mHeight); } else { if (!mSurface) { return; //nothing to repack } mRectanizer.reset(); } mFreeRects.clear(); SkImage* sourceImageAtlas = nullptr; if (mSurface) { sourceImageAtlas = mSurface->makeImageSnapshot().get(); } // Sort the list by VD size, which allows for the smallest VDs to get first in the atlas. // Sorting is safe, because it does not affect iterator validity. if (mRects.size() <= 100) { mRects.sort(compareCacheEntry); } for (CacheEntry& entry : mRects) { SkRect currentVDRect = entry.VDrect; SkImage* sourceImage; //copy either from the atlas or from a standalone surface if (entry.surface) { if (!fitInAtlas(currentVDRect.width(), currentVDRect.height())) { continue; //don't even try to repack huge VD } sourceImage = entry.surface->makeImageSnapshot().get(); } else { sourceImage = sourceImageAtlas; } size_t VDRectArea = currentVDRect.width()*currentVDRect.height(); SkIPoint16 pos; if (canvas && mRectanizer->addRect(currentVDRect.width(), currentVDRect.height(), &pos)) { SkRect newRect = SkRect::MakeXYWH(pos.fX, pos.fY, currentVDRect.width(), currentVDRect.height()); canvas->drawImageRect(sourceImage, currentVDRect, newRect, nullptr); entry.VDrect = newRect; entry.rect = newRect; if (entry.surface) { // A rectangle moved from a standalone surface to the atlas. entry.surface = nullptr; mPixelUsedByVDs += VDRectArea; } } else { // Repack failed for this item. If it is not already, store it in a standalone // surface. if (!entry.surface) { // A rectangle moved from an atlas to a standalone surface. mPixelUsedByVDs -= VDRectArea; SkRect newRect = SkRect::MakeWH(currentVDRect.width(), currentVDRect.height()); entry.surface = createSurface(newRect.width(), newRect.height(), context); auto tempCanvas = entry.surface->getCanvas(); tempCanvas->clear(SK_ColorTRANSPARENT); tempCanvas->drawImageRect(sourceImageAtlas, currentVDRect, newRect, nullptr); entry.VDrect = newRect; entry.rect = newRect; } } } mPixelAllocated = mPixelUsedByVDs; context->flush(); mSurface = newSurface; mConsecutiveFailures = 0; } AtlasEntry VectorDrawableAtlas::requestNewEntry(int width, int height, GrContext* context) { AtlasEntry result; if (width <= 0 || height <= 0) { return result; } if (mSurface) { const size_t area = width*height; // Use a rectanizer to allocate unused space from the atlas surface. bool notTooBig = fitInAtlas(width, height); SkIPoint16 pos; if (notTooBig && mRectanizer->addRect(width, height, &pos)) { mPixelUsedByVDs += area; mPixelAllocated += area; result.rect = SkRect::MakeXYWH(pos.fX, pos.fY, width, height); result.surface = mSurface; auto eraseIt = mRects.emplace(mRects.end(), result.rect, result.rect, nullptr); CacheEntry* entry = &(*eraseIt); entry->eraseIt = eraseIt; result.key = reinterpret_cast<AtlasKey>(entry); mConsecutiveFailures = 0; return result; } // Try to reuse atlas memory from rectangles freed by "releaseEntry". auto freeRectIt = mFreeRects.lower_bound(area); while (freeRectIt != mFreeRects.end()) { SkRect& freeRect = freeRectIt->second; if (freeRect.width() >= width && freeRect.height() >= height) { result.rect = SkRect::MakeXYWH(freeRect.fLeft, freeRect.fTop, width, height); result.surface = mSurface; auto eraseIt = mRects.emplace(mRects.end(), result.rect, freeRect, nullptr); CacheEntry* entry = &(*eraseIt); entry->eraseIt = eraseIt; result.key = reinterpret_cast<AtlasKey>(entry); mPixelUsedByVDs += area; mFreeRects.erase(freeRectIt); mConsecutiveFailures = 0; return result; } freeRectIt++; } if (notTooBig && mConsecutiveFailures <= MAX_CONSECUTIVE_FAILURES) { mConsecutiveFailures++; } } // Allocate a surface for a rectangle that is too big or if atlas is full. if (nullptr != context) { result.rect = SkRect::MakeWH(width, height); result.surface = createSurface(width, height, context); auto eraseIt = mRects.emplace(mRects.end(), result.rect, result.rect, result.surface); CacheEntry* entry = &(*eraseIt); entry->eraseIt = eraseIt; result.key = reinterpret_cast<AtlasKey>(entry); } return result; } AtlasEntry VectorDrawableAtlas::getEntry(AtlasKey atlasKey) { AtlasEntry result; if (INVALID_ATLAS_KEY != atlasKey) { CacheEntry* entry = reinterpret_cast<CacheEntry*>(atlasKey); result.rect = entry->VDrect; result.surface = entry->surface; if (!result.surface) { result.surface = mSurface; } result.key = atlasKey; } return result; } void VectorDrawableAtlas::releaseEntry(AtlasKey atlasKey) { if (INVALID_ATLAS_KEY != atlasKey) { CacheEntry* entry = reinterpret_cast<CacheEntry*>(atlasKey); if (!entry->surface) { // Store freed atlas rectangles in "mFreeRects" and try to reuse them later, when atlas // is full. SkRect& removedRect = entry->rect; size_t rectArea = removedRect.width()*removedRect.height(); mFreeRects.emplace(rectArea, removedRect); SkRect& removedVDRect = entry->VDrect; size_t VDRectArea = removedVDRect.width()*removedVDRect.height(); mPixelUsedByVDs -= VDRectArea; mConsecutiveFailures = 0; } auto eraseIt = entry->eraseIt; mRects.erase(eraseIt); } } sk_sp<SkSurface> VectorDrawableAtlas::createSurface(int width, int height, GrContext* context) { #ifndef ANDROID_ENABLE_LINEAR_BLENDING sk_sp<SkColorSpace> colorSpace = nullptr; #else sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB(); #endif SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType, colorSpace); return SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info); } void VectorDrawableAtlas::setStorageMode(StorageMode mode) { mStorageMode = mode; if (StorageMode::disallowSharedSurface == mStorageMode && mSurface) { mSurface.reset(); mRectanizer.reset(); mFreeRects.clear(); } } } /* namespace skiapipeline */ } /* namespace uirenderer */ } /* namespace android */ Loading
libs/hwui/Android.bp +3 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ cc_defaults { "external/skia/src/effects", "external/skia/src/image", "external/skia/src/utils", "external/skia/src/gpu", ], product_variables: { Loading Loading @@ -133,6 +134,7 @@ cc_defaults { "pipeline/skia/SkiaProfileRenderer.cpp", "pipeline/skia/SkiaRecordingCanvas.cpp", "pipeline/skia/SkiaVulkanPipeline.cpp", "pipeline/skia/VectorDrawableAtlas.cpp", "renderstate/Blend.cpp", "renderstate/MeshState.cpp", "renderstate/OffscreenBufferPool.cpp", Loading Loading @@ -340,6 +342,7 @@ cc_test { "tests/unit/TextureCacheTests.cpp", "tests/unit/TypefaceTests.cpp", "tests/unit/VectorDrawableTests.cpp", "tests/unit/VectorDrawableAtlasTests.cpp", ], } Loading
libs/hwui/VectorDrawable.cpp +89 −20 Original line number Diff line number Diff line Loading @@ -491,34 +491,103 @@ Bitmap& Tree::getBitmapUpdateIfDirty() { return *mCache.bitmap; } void Tree::updateCache(sk_sp<SkSurface> surface) { if (surface.get()) { mCache.surface = surface; } if (surface.get() || mCache.dirty) { SkSurface* vdSurface = mCache.surface.get(); SkCanvas* canvas = vdSurface->getCanvas(); float scaleX = vdSurface->width() / mProperties.getViewportWidth(); float scaleY = vdSurface->height() / mProperties.getViewportHeight(); void Tree::updateCache(sp<skiapipeline::VectorDrawableAtlas>& atlas, GrContext* context) { SkRect dst; sk_sp<SkSurface> surface = mCache.getSurface(&dst); bool canReuseSurface = surface && dst.width() >= mProperties.getScaledWidth() && dst.height() >= mProperties.getScaledHeight(); if (!canReuseSurface) { int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth()); int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight()); auto atlasEntry = atlas->requestNewEntry(scaledWidth, scaledHeight, context); if (INVALID_ATLAS_KEY != atlasEntry.key) { dst = atlasEntry.rect; surface = atlasEntry.surface; mCache.setAtlas(atlas, atlasEntry.key); } else { //don't draw, if we failed to allocate an offscreen buffer mCache.clear(); surface.reset(); } } if (!canReuseSurface || mCache.dirty) { draw(surface.get(), dst); mCache.dirty = false; } } void Tree::draw(SkSurface* surface, const SkRect& dst) { if (surface) { SkCanvas* canvas = surface->getCanvas(); float scaleX = dst.width() / mProperties.getViewportWidth(); float scaleY = dst.height() / mProperties.getViewportHeight(); SkAutoCanvasRestore acr(canvas, true); canvas->translate(dst.fLeft, dst.fTop); canvas->clipRect(SkRect::MakeWH(dst.width(), dst.height())); canvas->clear(SK_ColorTRANSPARENT); canvas->scale(scaleX, scaleY); mRootNode->draw(canvas, false); mCache.dirty = false; canvas->flush(); } } void Tree::Cache::setAtlas(sp<skiapipeline::VectorDrawableAtlas> newAtlas, skiapipeline::AtlasKey newAtlasKey) { LOG_ALWAYS_FATAL_IF(newAtlasKey == INVALID_ATLAS_KEY); clear(); mAtlas = newAtlas; mAtlasKey = newAtlasKey; } sk_sp<SkSurface> Tree::Cache::getSurface(SkRect* bounds) { sk_sp<SkSurface> surface; sp<skiapipeline::VectorDrawableAtlas> atlas = mAtlas.promote(); if (atlas.get() && mAtlasKey != INVALID_ATLAS_KEY) { auto atlasEntry = atlas->getEntry(mAtlasKey); *bounds = atlasEntry.rect; surface = atlasEntry.surface; mAtlasKey = atlasEntry.key; } return surface; } void Tree::Cache::clear() { sp<skiapipeline::VectorDrawableAtlas> lockAtlas = mAtlas.promote(); if (lockAtlas.get()) { lockAtlas->releaseEntry(mAtlasKey); } mAtlas = nullptr; mAtlasKey = INVALID_ATLAS_KEY; } void Tree::draw(SkCanvas* canvas) { /* * TODO address the following... * * 1) figure out how to set path's as volatile during animation * 2) if mRoot->getPaint() != null either promote to layer (during * animation) or cache in SkSurface (for static content) */ canvas->drawImageRect(mCache.surface->makeImageSnapshot().get(), SkRect src; sk_sp<SkSurface> vdSurface = mCache.getSurface(&src); if (vdSurface) { canvas->drawImageRect(vdSurface->makeImageSnapshot().get(), src, mutateProperties()->getBounds(), getPaint()); } else { // Handle the case when VectorDrawableAtlas has been destroyed, because of memory pressure. // We render the VD into a temporary standalone buffer and mark the frame as dirty. Next // frame will be cached into the atlas. int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth()); int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight()); SkRect src = SkRect::MakeWH(scaledWidth, scaledHeight); #ifndef ANDROID_ENABLE_LINEAR_BLENDING sk_sp<SkColorSpace> colorSpace = nullptr; #else sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB(); #endif SkImageInfo info = SkImageInfo::MakeN32(scaledWidth, scaledHeight, kPremul_SkAlphaType, colorSpace); sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(canvas->getGrContext(), SkBudgeted::kYes, info); draw(surface.get(), src); mCache.clear(); canvas->drawImageRect(surface->makeImageSnapshot().get(), mutateProperties()->getBounds(), getPaint()); markDirty(); } } void Tree::updateBitmapCache(Bitmap& bitmap, bool useStagingData) { Loading
libs/hwui/VectorDrawable.h +43 −16 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ #include "hwui/Canvas.h" #include "hwui/Bitmap.h" #include "renderthread/CacheManager.h" #include "DisplayList.h" #include <SkBitmap.h> Loading Loading @@ -687,35 +688,61 @@ public: bool getPropertyChangeWillBeConsumed() const { return mWillBeConsumed; } void setPropertyChangeWillBeConsumed(bool willBeConsumed) { mWillBeConsumed = willBeConsumed; } // Returns true if VD cache surface is big enough. This should always be called from RT and it // works with Skia pipelines only. bool canReuseSurface() { SkSurface* surface = mCache.surface.get(); return surface && surface->width() >= mProperties.getScaledWidth() && surface->height() >= mProperties.getScaledHeight(); } // Draws VD cache into a canvas. This should always be called from RT and it works with Skia // pipelines only. /** * Draws VD cache into a canvas. This should always be called from RT and it works with Skia * pipelines only. */ void draw(SkCanvas* canvas); // Draws VD into a GPU backed surface. If canReuseSurface returns false, then "surface" must // contain a new surface. This should always be called from RT and it works with Skia pipelines // only. void updateCache(sk_sp<SkSurface> surface); /** * Draws VD into a GPU backed surface. * This should always be called from RT and it works with Skia pipeline only. */ void updateCache(sp<skiapipeline::VectorDrawableAtlas>& atlas, GrContext* context); private: struct Cache { class Cache { public: sk_sp<Bitmap> bitmap; //used by HWUI pipeline and software //TODO: use surface instead of bitmap when drawing in software canvas sk_sp<SkSurface> surface; //used only by Skia pipelines bool dirty = true; // the rest of the code in Cache is used by Skia pipelines only ~Cache() { clear(); } /** * Stores a weak pointer to the atlas and a key. */ void setAtlas(sp<skiapipeline::VectorDrawableAtlas> atlas, skiapipeline::AtlasKey newAtlasKey); /** * Gets a surface and bounds from the atlas. * * @return nullptr if the altas has been deleted. */ sk_sp<SkSurface> getSurface(SkRect* bounds); /** * Releases atlas key from the atlas, which makes it available for reuse. */ void clear(); private: wp<skiapipeline::VectorDrawableAtlas> mAtlas; skiapipeline::AtlasKey mAtlasKey = INVALID_ATLAS_KEY; }; SkPaint* updatePaint(SkPaint* outPaint, TreeProperties* prop); bool allocateBitmapIfNeeded(Cache& cache, int width, int height); bool canReuseBitmap(Bitmap*, int width, int height); void updateBitmapCache(Bitmap& outCache, bool useStagingData); /** * Draws the root node into "surface" at a given "dst" position. */ void draw(SkSurface* surface, const SkRect& dst); // Cap the bitmap size, such that it won't hurt the performance too much // and it won't crash due to a very large scale. // The drawable will look blurry above this size. Loading
libs/hwui/pipeline/skia/SkiaPipeline.cpp +8 −18 Original line number Diff line number Diff line Loading @@ -183,27 +183,17 @@ public: }; void SkiaPipeline::renderVectorDrawableCache() { //render VectorDrawables into offscreen buffers if (!mVectorDrawables.empty()) { sp<VectorDrawableAtlas> atlas = mRenderThread.cacheManager().acquireVectorDrawableAtlas(); auto grContext = mRenderThread.getGrContext(); atlas->prepareForDraw(grContext); for (auto vd : mVectorDrawables) { sk_sp<SkSurface> surface; if (!vd->canReuseSurface()) { #ifndef ANDROID_ENABLE_LINEAR_BLENDING sk_sp<SkColorSpace> colorSpace = nullptr; #else sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB(); #endif int scaledWidth = SkScalarCeilToInt(vd->properties().getScaledWidth()); int scaledHeight = SkScalarCeilToInt(vd->properties().getScaledHeight()); SkImageInfo info = SkImageInfo::MakeN32(scaledWidth, scaledHeight, kPremul_SkAlphaType, colorSpace); SkASSERT(mRenderThread.getGrContext() != nullptr); surface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes, info); } vd->updateCache(surface); vd->updateCache(atlas, grContext); } grContext->flush(); mVectorDrawables.clear(); } } void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, bool opaque, bool wideColorGamut, Loading
libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp 0 → 100644 +269 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "VectorDrawableAtlas.h" #include <GrRectanizer_pow2.h> #include <SkCanvas.h> #include <cmath> #include "utils/TraceUtils.h" #include "renderthread/RenderProxy.h" namespace android { namespace uirenderer { namespace skiapipeline { VectorDrawableAtlas::VectorDrawableAtlas(size_t surfaceArea, StorageMode storageMode) : mWidth((int)std::sqrt(surfaceArea)) , mHeight((int)std::sqrt(surfaceArea)) , mStorageMode(storageMode) { } void VectorDrawableAtlas::prepareForDraw(GrContext* context) { if (StorageMode::allowSharedSurface == mStorageMode) { if (!mSurface) { mSurface = createSurface(mWidth, mHeight, context); mRectanizer = std::make_unique<GrRectanizerPow2>(mWidth, mHeight); mPixelUsedByVDs = 0; mPixelAllocated = 0; mConsecutiveFailures = 0; mFreeRects.clear(); } else { if (isFragmented()) { // Invoke repack outside renderFrame to avoid jank. renderthread::RenderProxy::repackVectorDrawableAtlas(); } } } } #define MAX_CONSECUTIVE_FAILURES 5 #define MAX_UNUSED_RATIO 2.0f bool VectorDrawableAtlas::isFragmented() { return mConsecutiveFailures > MAX_CONSECUTIVE_FAILURES && mPixelUsedByVDs*MAX_UNUSED_RATIO < mPixelAllocated; } void VectorDrawableAtlas::repackIfNeeded(GrContext* context) { // We repackage when atlas failed to allocate space MAX_CONSECUTIVE_FAILURES consecutive // times and the atlas allocated pixels are at least MAX_UNUSED_RATIO times higher than pixels // used by atlas VDs. if (isFragmented() && mSurface) { repack(context); } } // compare to CacheEntry objects based on VD area. bool VectorDrawableAtlas::compareCacheEntry(const CacheEntry& first, const CacheEntry& second) { return first.VDrect.width()*first.VDrect.height() < second.VDrect.width()*second.VDrect.height(); } void VectorDrawableAtlas::repack(GrContext* context) { ATRACE_CALL(); sk_sp<SkSurface> newSurface; SkCanvas* canvas = nullptr; if (StorageMode::allowSharedSurface == mStorageMode) { newSurface = createSurface(mWidth, mHeight, context); if (!newSurface) { return; } canvas = newSurface->getCanvas(); canvas->clear(SK_ColorTRANSPARENT); mRectanizer = std::make_unique<GrRectanizerPow2>(mWidth, mHeight); } else { if (!mSurface) { return; //nothing to repack } mRectanizer.reset(); } mFreeRects.clear(); SkImage* sourceImageAtlas = nullptr; if (mSurface) { sourceImageAtlas = mSurface->makeImageSnapshot().get(); } // Sort the list by VD size, which allows for the smallest VDs to get first in the atlas. // Sorting is safe, because it does not affect iterator validity. if (mRects.size() <= 100) { mRects.sort(compareCacheEntry); } for (CacheEntry& entry : mRects) { SkRect currentVDRect = entry.VDrect; SkImage* sourceImage; //copy either from the atlas or from a standalone surface if (entry.surface) { if (!fitInAtlas(currentVDRect.width(), currentVDRect.height())) { continue; //don't even try to repack huge VD } sourceImage = entry.surface->makeImageSnapshot().get(); } else { sourceImage = sourceImageAtlas; } size_t VDRectArea = currentVDRect.width()*currentVDRect.height(); SkIPoint16 pos; if (canvas && mRectanizer->addRect(currentVDRect.width(), currentVDRect.height(), &pos)) { SkRect newRect = SkRect::MakeXYWH(pos.fX, pos.fY, currentVDRect.width(), currentVDRect.height()); canvas->drawImageRect(sourceImage, currentVDRect, newRect, nullptr); entry.VDrect = newRect; entry.rect = newRect; if (entry.surface) { // A rectangle moved from a standalone surface to the atlas. entry.surface = nullptr; mPixelUsedByVDs += VDRectArea; } } else { // Repack failed for this item. If it is not already, store it in a standalone // surface. if (!entry.surface) { // A rectangle moved from an atlas to a standalone surface. mPixelUsedByVDs -= VDRectArea; SkRect newRect = SkRect::MakeWH(currentVDRect.width(), currentVDRect.height()); entry.surface = createSurface(newRect.width(), newRect.height(), context); auto tempCanvas = entry.surface->getCanvas(); tempCanvas->clear(SK_ColorTRANSPARENT); tempCanvas->drawImageRect(sourceImageAtlas, currentVDRect, newRect, nullptr); entry.VDrect = newRect; entry.rect = newRect; } } } mPixelAllocated = mPixelUsedByVDs; context->flush(); mSurface = newSurface; mConsecutiveFailures = 0; } AtlasEntry VectorDrawableAtlas::requestNewEntry(int width, int height, GrContext* context) { AtlasEntry result; if (width <= 0 || height <= 0) { return result; } if (mSurface) { const size_t area = width*height; // Use a rectanizer to allocate unused space from the atlas surface. bool notTooBig = fitInAtlas(width, height); SkIPoint16 pos; if (notTooBig && mRectanizer->addRect(width, height, &pos)) { mPixelUsedByVDs += area; mPixelAllocated += area; result.rect = SkRect::MakeXYWH(pos.fX, pos.fY, width, height); result.surface = mSurface; auto eraseIt = mRects.emplace(mRects.end(), result.rect, result.rect, nullptr); CacheEntry* entry = &(*eraseIt); entry->eraseIt = eraseIt; result.key = reinterpret_cast<AtlasKey>(entry); mConsecutiveFailures = 0; return result; } // Try to reuse atlas memory from rectangles freed by "releaseEntry". auto freeRectIt = mFreeRects.lower_bound(area); while (freeRectIt != mFreeRects.end()) { SkRect& freeRect = freeRectIt->second; if (freeRect.width() >= width && freeRect.height() >= height) { result.rect = SkRect::MakeXYWH(freeRect.fLeft, freeRect.fTop, width, height); result.surface = mSurface; auto eraseIt = mRects.emplace(mRects.end(), result.rect, freeRect, nullptr); CacheEntry* entry = &(*eraseIt); entry->eraseIt = eraseIt; result.key = reinterpret_cast<AtlasKey>(entry); mPixelUsedByVDs += area; mFreeRects.erase(freeRectIt); mConsecutiveFailures = 0; return result; } freeRectIt++; } if (notTooBig && mConsecutiveFailures <= MAX_CONSECUTIVE_FAILURES) { mConsecutiveFailures++; } } // Allocate a surface for a rectangle that is too big or if atlas is full. if (nullptr != context) { result.rect = SkRect::MakeWH(width, height); result.surface = createSurface(width, height, context); auto eraseIt = mRects.emplace(mRects.end(), result.rect, result.rect, result.surface); CacheEntry* entry = &(*eraseIt); entry->eraseIt = eraseIt; result.key = reinterpret_cast<AtlasKey>(entry); } return result; } AtlasEntry VectorDrawableAtlas::getEntry(AtlasKey atlasKey) { AtlasEntry result; if (INVALID_ATLAS_KEY != atlasKey) { CacheEntry* entry = reinterpret_cast<CacheEntry*>(atlasKey); result.rect = entry->VDrect; result.surface = entry->surface; if (!result.surface) { result.surface = mSurface; } result.key = atlasKey; } return result; } void VectorDrawableAtlas::releaseEntry(AtlasKey atlasKey) { if (INVALID_ATLAS_KEY != atlasKey) { CacheEntry* entry = reinterpret_cast<CacheEntry*>(atlasKey); if (!entry->surface) { // Store freed atlas rectangles in "mFreeRects" and try to reuse them later, when atlas // is full. SkRect& removedRect = entry->rect; size_t rectArea = removedRect.width()*removedRect.height(); mFreeRects.emplace(rectArea, removedRect); SkRect& removedVDRect = entry->VDrect; size_t VDRectArea = removedVDRect.width()*removedVDRect.height(); mPixelUsedByVDs -= VDRectArea; mConsecutiveFailures = 0; } auto eraseIt = entry->eraseIt; mRects.erase(eraseIt); } } sk_sp<SkSurface> VectorDrawableAtlas::createSurface(int width, int height, GrContext* context) { #ifndef ANDROID_ENABLE_LINEAR_BLENDING sk_sp<SkColorSpace> colorSpace = nullptr; #else sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB(); #endif SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType, colorSpace); return SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info); } void VectorDrawableAtlas::setStorageMode(StorageMode mode) { mStorageMode = mode; if (StorageMode::disallowSharedSurface == mStorageMode && mSurface) { mSurface.reset(); mRectanizer.reset(); mFreeRects.clear(); } } } /* namespace skiapipeline */ } /* namespace uirenderer */ } /* namespace android */