Merge "Delete VectorDrawableAtlas"
This commit is contained in:
@@ -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",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -150,10 +150,6 @@ public:
|
||||
|
||||
ANDROID_API static void preload();
|
||||
|
||||
static void repackVectorDrawableAtlas();
|
||||
|
||||
static void releaseVDAtlasEntries();
|
||||
|
||||
private:
|
||||
RenderThread& mRenderThread;
|
||||
CanvasContext* mContext;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
Reference in New Issue
Block a user