1bcacfdcab
Test: No code changes, just ran through clang-format Change-Id: Id23aa4ec7eebc0446fe3a30260f33e7fd455bb8c
289 lines
11 KiB
C++
289 lines
11 KiB
C++
/*
|
|
* 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 "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<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) {
|
|
if (!renderthread::RenderThread::isCurrent()) {
|
|
{
|
|
AutoMutex _lock(mReleaseKeyLock);
|
|
mKeysForRelease.push_back(atlasKey);
|
|
}
|
|
// invoke releaseEntry on the renderthread
|
|
renderthread::RenderProxy::releaseVDAtlasEntries();
|
|
return;
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
void VectorDrawableAtlas::delayedReleaseEntries() {
|
|
AutoMutex _lock(mReleaseKeyLock);
|
|
for (auto key : mKeysForRelease) {
|
|
releaseEntry(key);
|
|
}
|
|
mKeysForRelease.clear();
|
|
}
|
|
|
|
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);
|
|
// 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 */
|