/* * 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 #include #include #include "renderthread/RenderProxy.h" #include "renderthread/RenderThread.h" #include "utils/TraceUtils.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(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 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(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(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(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(entry); } return result; } AtlasEntry VectorDrawableAtlas::getEntry(AtlasKey atlasKey) { AtlasEntry result; if (INVALID_ATLAS_KEY != atlasKey) { CacheEntry* entry = reinterpret_cast(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) { if (!renderthread::RenderThread::isCurrent()) { { AutoMutex _lock(mReleaseKeyLock); mKeysForRelease.push_back(atlasKey); } // invoke releaseEntry on the renderthread renderthread::RenderProxy::releaseVDAtlasEntries(); return; } CacheEntry* entry = reinterpret_cast(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); } } void VectorDrawableAtlas::delayedReleaseEntries() { AutoMutex _lock(mReleaseKeyLock); for (auto key : mKeysForRelease) { releaseEntry(key); } mKeysForRelease.clear(); } sk_sp VectorDrawableAtlas::createSurface(int width, int height, GrContext* context) { SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType); // This must have a top-left origin so that calls to surface->canvas->writePixels // performs a basic texture upload instead of a more complex drawing operation return SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info, 0, kTopLeft_GrSurfaceOrigin, nullptr); } void VectorDrawableAtlas::setStorageMode(StorageMode mode) { mStorageMode = mode; if (StorageMode::disallowSharedSurface == mStorageMode && mSurface) { mSurface.reset(); mRectanizer.reset(); mFreeRects.clear(); } } } /* namespace skiapipeline */ } /* namespace uirenderer */ } /* namespace android */