Merge "Delete VectorDrawableAtlas"

This commit is contained in:
John Reck
2019-10-25 22:28:40 +00:00
committed by Android (Google) Code Review
16 changed files with 7 additions and 939 deletions

View File

@@ -221,7 +221,6 @@ cc_defaults {
"pipeline/skia/SkiaPipeline.cpp",
"pipeline/skia/SkiaProfileRenderer.cpp",
"pipeline/skia/SkiaVulkanPipeline.cpp",
"pipeline/skia/VectorDrawableAtlas.cpp",
"pipeline/skia/VkFunctorDrawable.cpp",
"pipeline/skia/VkInteropFunctorDrawable.cpp",
"renderstate/RenderState.cpp",
@@ -347,7 +346,6 @@ cc_test {
"tests/unit/ThreadBaseTests.cpp",
"tests/unit/TypefaceTests.cpp",
"tests/unit/VectorDrawableTests.cpp",
"tests/unit/VectorDrawableAtlasTests.cpp",
"tests/unit/WebViewFunctorManagerTests.cpp",
],
}

View File

@@ -496,87 +496,6 @@ Bitmap& Tree::getBitmapUpdateIfDirty() {
return *mCache.bitmap;
}
void Tree::updateCache(sp<skiapipeline::VectorDrawableAtlas>& atlas, GrContext* context) {
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
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) {
if (surface) {
Bitmap& bitmap = getBitmapUpdateIfDirty();
SkBitmap skiaBitmap;
bitmap.getSkBitmap(&skiaBitmap);
surface->writePixels(skiaBitmap, dst.fLeft, dst.fTop);
}
mCache.dirty = false;
}
#endif
}
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;
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
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;
}
#endif
return surface;
}
void Tree::Cache::clear() {
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
if (mAtlasKey != INVALID_ATLAS_KEY) {
if (renderthread::RenderThread::isCurrent()) {
sp<skiapipeline::VectorDrawableAtlas> lockAtlas = mAtlas.promote();
if (lockAtlas.get()) {
lockAtlas->releaseEntry(mAtlasKey);
}
} else {
// VectorDrawableAtlas can be accessed only on RenderThread.
// Use by-copy capture of the current Cache variables, because "this" may not be valid
// by the time the lambda is evaluated on RenderThread.
renderthread::RenderThread::getInstance().queue().post(
[atlas = mAtlas, atlasKey = mAtlasKey]() {
sp<skiapipeline::VectorDrawableAtlas> lockAtlas = atlas.promote();
if (lockAtlas.get()) {
lockAtlas->releaseEntry(atlasKey);
}
});
}
mAtlasKey = INVALID_ATLAS_KEY;
}
mAtlas = nullptr;
#endif
}
void Tree::draw(SkCanvas* canvas, const SkRect& bounds, const SkPaint& inPaint) {
if (canvas->quickReject(bounds)) {
// The RenderNode is on screen, but the AVD is not.
@@ -587,39 +506,14 @@ void Tree::draw(SkCanvas* canvas, const SkRect& bounds, const SkPaint& inPaint)
SkPaint paint = inPaint;
paint.setAlpha(mProperties.getRootAlpha() * 255);
if (canvas->getGrContext() == nullptr) {
// Recording to picture, don't use the SkSurface which won't work off of renderthread.
Bitmap& bitmap = getBitmapUpdateIfDirty();
SkBitmap skiaBitmap;
bitmap.getSkBitmap(&skiaBitmap);
Bitmap& bitmap = getBitmapUpdateIfDirty();
SkBitmap skiaBitmap;
bitmap.getSkBitmap(&skiaBitmap);
int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds,
&paint, SkCanvas::kFast_SrcRectConstraint);
return;
}
SkRect src;
sk_sp<SkSurface> vdSurface = mCache.getSurface(&src);
if (vdSurface) {
canvas->drawImageRect(vdSurface->makeImageSnapshot().get(), src, bounds, &paint,
SkCanvas::kFast_SrcRectConstraint);
} 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.
Bitmap& bitmap = getBitmapUpdateIfDirty();
SkBitmap skiaBitmap;
bitmap.getSkBitmap(&skiaBitmap);
int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds,
&paint, SkCanvas::kFast_SrcRectConstraint);
mCache.clear();
markDirty();
}
int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth());
int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight());
canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds,
&paint, SkCanvas::kFast_SrcRectConstraint);
}
void Tree::updateBitmapCache(Bitmap& bitmap, bool useStagingData) {

View File

@@ -651,46 +651,13 @@ public:
void getPaintFor(SkPaint* outPaint, const TreeProperties &props) const;
BitmapPalette computePalette();
/**
* 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);
void setAntiAlias(bool aa) { mRootNode->setAntiAlias(aa); }
private:
class Cache {
public:
sk_sp<Bitmap> bitmap; // used by HWUI pipeline and software
// TODO: use surface instead of bitmap when drawing in software canvas
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;
};
bool allocateBitmapIfNeeded(Cache& cache, int width, int height);

View File

@@ -150,12 +150,7 @@ bool SkiaDisplayList::prepareListAndChildren(
const SkRect& bounds = vectorDrawable->properties().getBounds();
if (intersects(info.screenSize, totalMatrix, bounds)) {
isDirty = true;
#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
static_cast<SkiaPipeline*>(info.canvasContext.getRenderPipeline())
->getVectorDrawables()
->push_back(vectorDrawable);
vectorDrawable->setPropertyChangeWillBeConsumed(true);
#endif
}
}
}

View File

@@ -43,7 +43,6 @@ namespace uirenderer {
namespace skiapipeline {
SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) {
mVectorDrawables.reserve(30);
}
SkiaPipeline::~SkiaPipeline() {
@@ -73,18 +72,11 @@ void SkiaPipeline::unpinImages() {
mPinnedImages.clear();
}
void SkiaPipeline::onPrepareTree() {
// The only time mVectorDrawables is not empty is if prepare tree was called 2 times without
// a renderFrame in the middle.
mVectorDrawables.clear();
}
void SkiaPipeline::renderLayers(const LightGeometry& lightGeometry,
LayerUpdateQueue* layerUpdateQueue, bool opaque,
const LightInfo& lightInfo) {
LightingInfo::updateLighting(lightGeometry, lightInfo);
ATRACE_NAME("draw layers");
renderVectorDrawableCache();
renderLayersImpl(*layerUpdateQueue, opaque);
layerUpdateQueue->clear();
}
@@ -213,19 +205,6 @@ void SkiaPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
}
}
void SkiaPipeline::renderVectorDrawableCache() {
if (!mVectorDrawables.empty()) {
sp<VectorDrawableAtlas> atlas = mRenderThread.cacheManager().acquireVectorDrawableAtlas();
auto grContext = mRenderThread.getGrContext();
atlas->prepareForDraw(grContext);
ATRACE_NAME("Update VectorDrawables");
for (auto vd : mVectorDrawables) {
vd->updateCache(atlas, grContext);
}
mVectorDrawables.clear();
}
}
static void savePictureAsync(const sk_sp<SkData>& data, const std::string& filename) {
CommonPool::post([data, filename] {
if (0 == access(filename.c_str(), F_OK)) {
@@ -380,8 +359,6 @@ void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& cli
Properties::skpCaptureEnabled = true;
}
renderVectorDrawableCache();
// draw all layers up front
renderLayersImpl(layers, opaque);

View File

@@ -41,7 +41,6 @@ public:
bool pinImages(std::vector<SkImage*>& mutableImages) override;
bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
void unpinImages() override;
void onPrepareTree() override;
void renderLayers(const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
bool opaque, const LightInfo& lightInfo) override;
@@ -57,8 +56,6 @@ public:
const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
const SkMatrix& preTransform);
std::vector<VectorDrawableRoot*>* getVectorDrawables() { return &mVectorDrawables; }
static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque);
@@ -93,11 +90,6 @@ private:
const std::vector<sp<RenderNode>>& nodes, const Rect& contentDrawBounds,
sk_sp<SkSurface> surface, const SkMatrix& preTransform);
/**
* Render mVectorDrawables into offscreen buffers.
*/
void renderVectorDrawableCache();
// Called every frame. Normally returns early with screen canvas.
// But when capture is enabled, returns an nwaycanvas where commands are also recorded.
SkCanvas* tryCapture(SkSurface* surface);
@@ -113,11 +105,6 @@ private:
std::vector<sk_sp<SkImage>> mPinnedImages;
/**
* populated by prepareTree with dirty VDs
*/
std::vector<VectorDrawableRoot*> mVectorDrawables;
// Block of properties used only for debugging to record a SkPicture and save it in a file.
// There are three possible ways of recording drawing commands.
enum class CaptureMode {

View File

@@ -1,283 +0,0 @@
/*
* 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) {
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 */

View File

@@ -1,212 +0,0 @@
/*
* 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.
*/
#pragma once
#include <SkSurface.h>
#include <utils/FatVector.h>
#include <utils/RefBase.h>
#include <utils/Thread.h>
#include <list>
#include <map>
class GrRectanizer;
namespace android {
namespace uirenderer {
namespace skiapipeline {
typedef uintptr_t AtlasKey;
#define INVALID_ATLAS_KEY 0
struct AtlasEntry {
sk_sp<SkSurface> surface;
SkRect rect;
AtlasKey key = INVALID_ATLAS_KEY;
};
/**
* VectorDrawableAtlas provides offscreen buffers used to draw VD and AnimatedVD.
* VectorDrawableAtlas can allocate a standalone surface or provide a subrect from a shared surface.
* VectorDrawableAtlas is owned by the CacheManager and weak pointers are kept by each
* VectorDrawable that is using it. VectorDrawableAtlas and its surface can be deleted at any time,
* except during a renderFrame call. VectorDrawable does not contain a pointer to atlas SkSurface
* nor any coordinates into the atlas, but instead holds a rectangle "id", which is resolved only
* when drawing. This design makes VectorDrawableAtlas free to move the data internally.
* At draw time a VectorDrawable may find, that its atlas has been deleted, which will make it
* draw in a standalone cache surface not part of an atlas. In this case VD won't use
* VectorDrawableAtlas until the next frame.
* VectorDrawableAtlas tries to fit VDs in the atlas SkSurface. If there is not enough space in
* the atlas, VectorDrawableAtlas creates a standalone surface for each VD.
* When a VectorDrawable is deleted, it invokes VectorDrawableAtlas::releaseEntry, which is keeping
* track of free spaces and allow to reuse the surface for another VD.
*/
// TODO: Check if not using atlas for AnimatedVD is more efficient.
// TODO: For low memory situations, when there are no paint effects in VD, we may render without an
// TODO: offscreen surface.
class VectorDrawableAtlas : public virtual RefBase {
public:
enum class StorageMode { allowSharedSurface, disallowSharedSurface };
explicit VectorDrawableAtlas(size_t surfaceArea,
StorageMode storageMode = StorageMode::allowSharedSurface);
/**
* "prepareForDraw" may allocate a new surface if needed. It may schedule to repack the
* atlas at a later time.
*/
void prepareForDraw(GrContext* context);
/**
* Repack the atlas if needed, by moving used rectangles into a new atlas surface.
* The goal of repacking is to fix a fragmented atlas.
*/
void repackIfNeeded(GrContext* context);
/**
* Returns true if atlas is fragmented and repack is needed.
*/
bool isFragmented();
/**
* "requestNewEntry" is called by VectorDrawable to allocate a new rectangle area from the atlas
* or create a standalone surface if atlas is full.
* On success it returns a non-negative unique id, which can be used later with "getEntry" and
* "releaseEntry".
*/
AtlasEntry requestNewEntry(int width, int height, GrContext* context);
/**
* "getEntry" extracts coordinates and surface of a previously created rectangle.
* "atlasKey" is an unique id created by "requestNewEntry". Passing a non-existing "atlasKey" is
* causing an undefined behaviour.
* On success it returns a rectangle Id -> may be same or different from "atlasKey" if
* implementation decides to move the record internally.
*/
AtlasEntry getEntry(AtlasKey atlasKey);
/**
* "releaseEntry" is invoked when a VectorDrawable is deleted. Passing a non-existing "atlasKey"
* is causing an undefined behaviour. This is the only function in the class that can be
* invoked from any thread. It will marshal internally to render thread if needed.
*/
void releaseEntry(AtlasKey atlasKey);
void setStorageMode(StorageMode mode);
/**
* "delayedReleaseEntries" is indirectly invoked by "releaseEntry", when "releaseEntry" is
* invoked from a non render thread.
*/
void delayedReleaseEntries();
private:
struct CacheEntry {
CacheEntry(const SkRect& newVDrect, const SkRect& newRect,
const sk_sp<SkSurface>& newSurface)
: VDrect(newVDrect), rect(newRect), surface(newSurface) {}
/**
* size and position of VectorDrawable into the atlas or in "this.surface"
*/
SkRect VDrect;
/**
* rect allocated in atlas surface or "this.surface". It may be bigger than "VDrect"
*/
SkRect rect;
/**
* this surface is used if atlas is full or VD is too big
*/
sk_sp<SkSurface> surface;
/**
* iterator is used to delete self with a constant complexity (without traversing the list)
*/
std::list<CacheEntry>::iterator eraseIt;
};
/**
* atlas surface shared by all VDs
*/
sk_sp<SkSurface> mSurface;
std::unique_ptr<GrRectanizer> mRectanizer;
const int mWidth;
const int mHeight;
/**
* "mRects" keeps records only for rectangles used by VDs. List has nice properties: constant
* complexity to insert and erase and references are not invalidated by insert/erase.
*/
std::list<CacheEntry> mRects;
/**
* Rectangles freed by "releaseEntry" are removed from "mRects" and added to "mFreeRects".
* "mFreeRects" is using for an index the rectangle area. There could be more than one free
* rectangle with the same area, which is the reason to use "multimap" instead of "map".
*/
std::multimap<size_t, SkRect> mFreeRects;
/**
* area in atlas used by VectorDrawables (area in standalone surface not counted)
*/
int mPixelUsedByVDs = 0;
/**
* area allocated in mRectanizer
*/
int mPixelAllocated = 0;
/**
* Consecutive times we had to allocate standalone surfaces, because atlas was full.
*/
int mConsecutiveFailures = 0;
/**
* mStorageMode allows using a shared surface to store small vector drawables.
* Using a shared surface can boost the performance by allowing GL ops to be batched, but may
* consume more memory.
*/
StorageMode mStorageMode;
/**
* mKeysForRelease is used by releaseEntry implementation to pass atlas keys from an arbitrary
* calling thread to the render thread.
*/
std::vector<AtlasKey> mKeysForRelease;
/**
* A lock used to protect access to mKeysForRelease.
*/
Mutex mReleaseKeyLock;
sk_sp<SkSurface> createSurface(int width, int height, GrContext* context);
inline bool fitInAtlas(int width, int height) {
return 2 * width < mWidth && 2 * height < mHeight;
}
void repack(GrContext* context);
static bool compareCacheEntry(const CacheEntry& first, const CacheEntry& second);
};
} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */

View File

@@ -56,10 +56,6 @@ CacheManager::CacheManager(const DisplayInfo& display)
, mBackgroundCpuFontCacheBytes(mMaxCpuFontCacheBytes * BACKGROUND_RETENTION_PERCENTAGE) {
SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
mVectorDrawableAtlas = new skiapipeline::VectorDrawableAtlas(
mMaxSurfaceArea / 2,
skiapipeline::VectorDrawableAtlas::StorageMode::disallowSharedSurface);
}
void CacheManager::reset(sk_sp<GrContext> context) {
@@ -76,9 +72,6 @@ void CacheManager::reset(sk_sp<GrContext> context) {
void CacheManager::destroy() {
// cleanup any caches here as the GrContext is about to go away...
mGrContext.reset(nullptr);
mVectorDrawableAtlas = new skiapipeline::VectorDrawableAtlas(
mMaxSurfaceArea / 2,
skiapipeline::VectorDrawableAtlas::StorageMode::disallowSharedSurface);
}
class CommonPoolExecutor : public SkExecutor {
@@ -109,7 +102,6 @@ void CacheManager::trimMemory(TrimMemoryMode mode) {
switch (mode) {
case TrimMemoryMode::Complete:
mVectorDrawableAtlas = new skiapipeline::VectorDrawableAtlas(mMaxSurfaceArea / 2);
mGrContext->freeGpuResources();
SkGraphics::PurgeAllCaches();
break;
@@ -138,16 +130,6 @@ void CacheManager::trimStaleResources() {
mGrContext->purgeResourcesNotUsedInMs(std::chrono::seconds(30));
}
sp<skiapipeline::VectorDrawableAtlas> CacheManager::acquireVectorDrawableAtlas() {
LOG_ALWAYS_FATAL_IF(mVectorDrawableAtlas.get() == nullptr);
LOG_ALWAYS_FATAL_IF(mGrContext == nullptr);
/**
* TODO: define memory conditions where we clear the cache (e.g. surface->reset())
*/
return mVectorDrawableAtlas;
}
void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) {
if (!mGrContext) {
log.appendFormat("No valid cache instance.\n");
@@ -176,8 +158,6 @@ void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState)
log.appendFormat("Other Caches:\n");
log.appendFormat(" Current / Maximum\n");
log.appendFormat(" VectorDrawableAtlas %6.2f kB / %6.2f KB (entries = %zu)\n", 0.0f, 0.0f,
(size_t)0);
if (renderState) {
if (renderState->mActiveLayers.size() > 0) {

View File

@@ -25,8 +25,6 @@
#include <utils/String8.h>
#include <vector>
#include "pipeline/skia/VectorDrawableAtlas.h"
namespace android {
class Surface;
@@ -51,8 +49,6 @@ public:
void trimStaleResources();
void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr);
sp<skiapipeline::VectorDrawableAtlas> acquireVectorDrawableAtlas();
size_t getCacheSize() const { return mMaxResourceBytes; }
size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; }
@@ -77,13 +73,6 @@ private:
const size_t mMaxGpuFontAtlasBytes;
const size_t mMaxCpuFontCacheBytes;
const size_t mBackgroundCpuFontCacheBytes;
struct PipelineProps {
const void* pipelineKey = nullptr;
size_t surfaceArea = 0;
};
sp<skiapipeline::VectorDrawableAtlas> mVectorDrawableAtlas;
};
} /* namespace renderthread */

View File

@@ -306,7 +306,6 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy
info.out.canDrawThisFrame = true;
mAnimationContext->startFrame(info.mode);
mRenderPipeline->onPrepareTree();
for (const sp<RenderNode>& node : mRenderNodes) {
// Only the primary target node will be drawn full - all other nodes would get drawn in
// real time mode. In case of a window, the primary node is the window content and the other

View File

@@ -80,7 +80,6 @@ public:
virtual bool pinImages(std::vector<SkImage*>& mutableImages) = 0;
virtual bool pinImages(LsaVector<sk_sp<Bitmap>>& images) = 0;
virtual void unpinImages() = 0;
virtual void onPrepareTree() = 0;
virtual SkColorType getSurfaceColorType() const = 0;
virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0;
virtual GrSurfaceOrigin getSurfaceOrigin() = 0;

View File

@@ -24,7 +24,6 @@
#include "Readback.h"
#include "Rect.h"
#include "WebViewFunctorManager.h"
#include "pipeline/skia/VectorDrawableAtlas.h"
#include "renderthread/CanvasContext.h"
#include "renderthread/RenderTask.h"
#include "renderthread/RenderThread.h"
@@ -365,27 +364,6 @@ void RenderProxy::disableVsync() {
Properties::disableVsync = true;
}
void RenderProxy::repackVectorDrawableAtlas() {
RenderThread& thread = RenderThread::getInstance();
thread.queue().post([&thread]() {
// The context may be null if trimMemory executed, but then the atlas was deleted too.
if (thread.getGrContext() != nullptr) {
thread.cacheManager().acquireVectorDrawableAtlas()->repackIfNeeded(
thread.getGrContext());
}
});
}
void RenderProxy::releaseVDAtlasEntries() {
RenderThread& thread = RenderThread::getInstance();
thread.queue().post([&thread]() {
// The context may be null if trimMemory executed, but then the atlas was deleted too.
if (thread.getGrContext() != nullptr) {
thread.cacheManager().acquireVectorDrawableAtlas()->delayedReleaseEntries();
}
});
}
void RenderProxy::preload() {
// Create RenderThread object and start the thread. Then preload Vulkan/EGL driver.
auto& thread = RenderThread::getInstance();

View File

@@ -150,10 +150,6 @@ public:
ANDROID_API static void preload();
static void repackVectorDrawableAtlas();
static void releaseVDAtlasEntries();
private:
RenderThread& mRenderThread;
CanvasContext* mContext;

View File

@@ -61,39 +61,6 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) {
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
}
RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, testOnPrepareTree) {
auto redNode = TestUtils::createSkiaNode(
0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
});
LayerUpdateQueue layerUpdateQueue;
SkRect dirty = SkRectMakeLargest();
std::vector<sp<RenderNode>> renderNodes;
renderNodes.push_back(redNode);
bool opaque = true;
android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1);
auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
{
// add a pointer to a deleted vector drawable object in the pipeline
sp<VectorDrawableRoot> dirtyVD(new VectorDrawableRoot(new VectorDrawable::Group()));
dirtyVD->mutateProperties()->setScaledSize(5, 5);
pipeline->getVectorDrawables()->push_back(dirtyVD.get());
}
// pipeline should clean list of dirty vector drawables before prepare tree
pipeline->onPrepareTree();
auto surface = SkSurface::MakeRasterN32Premul(1, 1);
surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
// drawFrame will crash if "SkiaPipeline::onPrepareTree" did not clean invalid VD pointer
pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
SkMatrix::I());
ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
}
RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) {
auto halfGreenNode = TestUtils::createSkiaNode(
0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) {

View File

@@ -1,163 +0,0 @@
/*
* 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 <gtest/gtest.h>
#include <GrRectanizer.h>
#include "pipeline/skia/VectorDrawableAtlas.h"
#include "tests/common/TestUtils.h"
using namespace android;
using namespace android::uirenderer;
using namespace android::uirenderer::renderthread;
using namespace android::uirenderer::skiapipeline;
RENDERTHREAD_SKIA_PIPELINE_TEST(VectorDrawableAtlas, addGetRemove) {
VectorDrawableAtlas atlas(100 * 100);
atlas.prepareForDraw(renderThread.getGrContext());
// create 150 rects 10x10, which won't fit in the atlas (atlas can fit no more than 100 rects)
const int MAX_RECTS = 150;
AtlasEntry VDRects[MAX_RECTS];
sk_sp<SkSurface> atlasSurface;
// check we are able to allocate new rects
// check that rects in the atlas do not intersect
for (uint32_t i = 0; i < MAX_RECTS; i++) {
VDRects[i] = atlas.requestNewEntry(10, 10, renderThread.getGrContext());
if (0 == i) {
atlasSurface = VDRects[0].surface;
}
ASSERT_TRUE(VDRects[i].key != INVALID_ATLAS_KEY);
ASSERT_TRUE(VDRects[i].surface.get() != nullptr);
ASSERT_TRUE(VDRects[i].rect.width() == 10 && VDRects[i].rect.height() == 10);
// nothing in the atlas should intersect
if (atlasSurface.get() == VDRects[i].surface.get()) {
for (uint32_t j = 0; j < i; j++) {
if (atlasSurface.get() == VDRects[j].surface.get()) {
ASSERT_FALSE(VDRects[i].rect.intersect(VDRects[j].rect));
}
}
}
}
// first 1/3 rects should all be in the same surface
for (uint32_t i = 1; i < MAX_RECTS / 3; i++) {
ASSERT_NE(VDRects[i].key, VDRects[0].key);
ASSERT_EQ(VDRects[i].surface.get(), atlasSurface.get());
}
// first rect is using atlas and last is a standalone surface
ASSERT_NE(VDRects[0].surface.get(), VDRects[MAX_RECTS - 1].surface.get());
// check getEntry returns the same surfaces that we had created
for (uint32_t i = 0; i < MAX_RECTS; i++) {
auto VDRect = atlas.getEntry(VDRects[i].key);
ASSERT_TRUE(VDRect.key != INVALID_ATLAS_KEY);
ASSERT_EQ(VDRects[i].key, VDRect.key);
ASSERT_EQ(VDRects[i].surface.get(), VDRect.surface.get());
ASSERT_EQ(VDRects[i].rect, VDRect.rect);
atlas.releaseEntry(VDRect.key);
}
// check that any new rects will be allocated in the atlas, even that rectanizer is full.
// rects in the atlas should not intersect.
for (uint32_t i = 0; i < MAX_RECTS / 3; i++) {
VDRects[i] = atlas.requestNewEntry(10, 10, renderThread.getGrContext());
ASSERT_TRUE(VDRects[i].key != INVALID_ATLAS_KEY);
ASSERT_EQ(VDRects[i].surface.get(), atlasSurface.get());
ASSERT_TRUE(VDRects[i].rect.width() == 10 && VDRects[i].rect.height() == 10);
for (uint32_t j = 0; j < i; j++) {
ASSERT_FALSE(VDRects[i].rect.intersect(VDRects[j].rect));
}
}
}
RENDERTHREAD_SKIA_PIPELINE_TEST(VectorDrawableAtlas, disallowSharedSurface) {
VectorDrawableAtlas atlas(100 * 100);
// don't allow to use a shared surface
atlas.setStorageMode(VectorDrawableAtlas::StorageMode::disallowSharedSurface);
atlas.prepareForDraw(renderThread.getGrContext());
// create 150 rects 10x10, which won't fit in the atlas (atlas can fit no more than 100 rects)
const int MAX_RECTS = 150;
AtlasEntry VDRects[MAX_RECTS];
// check we are able to allocate new rects
// check that rects in the atlas use unique surfaces
for (uint32_t i = 0; i < MAX_RECTS; i++) {
VDRects[i] = atlas.requestNewEntry(10, 10, renderThread.getGrContext());
ASSERT_TRUE(VDRects[i].key != INVALID_ATLAS_KEY);
ASSERT_TRUE(VDRects[i].surface.get() != nullptr);
ASSERT_TRUE(VDRects[i].rect.width() == 10 && VDRects[i].rect.height() == 10);
// nothing in the atlas should use the same surface
for (uint32_t j = 0; j < i; j++) {
ASSERT_NE(VDRects[i].surface.get(), VDRects[j].surface.get());
}
}
}
RENDERTHREAD_SKIA_PIPELINE_TEST(VectorDrawableAtlas, repack) {
VectorDrawableAtlas atlas(100 * 100);
ASSERT_FALSE(atlas.isFragmented());
atlas.prepareForDraw(renderThread.getGrContext());
ASSERT_FALSE(atlas.isFragmented());
// create 150 rects 10x10, which won't fit in the atlas (atlas can fit no more than 100 rects)
const int MAX_RECTS = 150;
AtlasEntry VDRects[MAX_RECTS];
sk_sp<SkSurface> atlasSurface;
// fill the atlas with check we are able to allocate new rects
for (uint32_t i = 0; i < MAX_RECTS; i++) {
VDRects[i] = atlas.requestNewEntry(10, 10, renderThread.getGrContext());
if (0 == i) {
atlasSurface = VDRects[0].surface;
}
ASSERT_TRUE(VDRects[i].key != INVALID_ATLAS_KEY);
}
ASSERT_FALSE(atlas.isFragmented());
// first 1/3 rects should all be in the same surface
for (uint32_t i = 1; i < MAX_RECTS / 3; i++) {
ASSERT_NE(VDRects[i].key, VDRects[0].key);
ASSERT_EQ(VDRects[i].surface.get(), atlasSurface.get());
}
// release all entries
for (uint32_t i = 0; i < MAX_RECTS; i++) {
auto VDRect = atlas.getEntry(VDRects[i].key);
ASSERT_TRUE(VDRect.key != INVALID_ATLAS_KEY);
atlas.releaseEntry(VDRect.key);
}
ASSERT_FALSE(atlas.isFragmented());
// allocate 4x4 rects, which will fragment the atlas badly, because each entry occupies a 10x10
// area
for (uint32_t i = 0; i < 4 * MAX_RECTS; i++) {
AtlasEntry entry = atlas.requestNewEntry(4, 4, renderThread.getGrContext());
ASSERT_TRUE(entry.key != INVALID_ATLAS_KEY);
}
ASSERT_TRUE(atlas.isFragmented());
atlas.repackIfNeeded(renderThread.getGrContext());
ASSERT_FALSE(atlas.isFragmented());
}