diff options
Diffstat (limited to 'libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp')
-rw-r--r-- | libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp b/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp new file mode 100644 index 000000000000..437653a8dfa8 --- /dev/null +++ b/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp @@ -0,0 +1,269 @@ +/* + * 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 */ |