From 83161dcd6aa15c7da161b4ae561b06d20edd2510 Mon Sep 17 00:00:00 2001 From: John Reck Date: Fri, 4 Oct 2019 14:48:27 -0700 Subject: [PATCH] Delete VectorDrawableAtlas Poking around in a few apps it doesn't appear that the VectorDrawableAtlas is achieving sufficient utilization to justify its existence. The potential for draw call merging doesn't seem warranted for the RAM cost of the atlas. Bug: 137853925 Test: builds Change-Id: Id2419bc6dccb6316636d50c568f8fac75a2d563f --- libs/hwui/Android.bp | 2 - libs/hwui/VectorDrawable.cpp | 120 +------- libs/hwui/VectorDrawable.h | 33 -- libs/hwui/pipeline/skia/SkiaDisplayList.cpp | 5 - libs/hwui/pipeline/skia/SkiaPipeline.cpp | 23 -- libs/hwui/pipeline/skia/SkiaPipeline.h | 13 - .../pipeline/skia/VectorDrawableAtlas.cpp | 283 ------------------ libs/hwui/pipeline/skia/VectorDrawableAtlas.h | 212 ------------- libs/hwui/renderthread/CacheManager.cpp | 20 -- libs/hwui/renderthread/CacheManager.h | 11 - libs/hwui/renderthread/CanvasContext.cpp | 1 - libs/hwui/renderthread/IRenderPipeline.h | 1 - libs/hwui/renderthread/RenderProxy.cpp | 22 -- libs/hwui/renderthread/RenderProxy.h | 4 - libs/hwui/tests/unit/SkiaPipelineTests.cpp | 33 -- .../tests/unit/VectorDrawableAtlasTests.cpp | 163 ---------- 16 files changed, 7 insertions(+), 939 deletions(-) delete mode 100644 libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp delete mode 100644 libs/hwui/pipeline/skia/VectorDrawableAtlas.h delete mode 100644 libs/hwui/tests/unit/VectorDrawableAtlasTests.cpp diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index ae90f117d448e..1bcef7314bbf9 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -219,7 +219,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", @@ -345,7 +344,6 @@ cc_test { "tests/unit/ThreadBaseTests.cpp", "tests/unit/TypefaceTests.cpp", "tests/unit/VectorDrawableTests.cpp", - "tests/unit/VectorDrawableAtlasTests.cpp", "tests/unit/WebViewFunctorManagerTests.cpp", ], } diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index 61403aa93c975..217b0c4a8b989 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -496,87 +496,6 @@ Bitmap& Tree::getBitmapUpdateIfDirty() { return *mCache.bitmap; } -void Tree::updateCache(sp& atlas, GrContext* context) { -#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration - SkRect dst; - sk_sp 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 newAtlas, - skiapipeline::AtlasKey newAtlasKey) { - LOG_ALWAYS_FATAL_IF(newAtlasKey == INVALID_ATLAS_KEY); - clear(); - mAtlas = newAtlas; - mAtlasKey = newAtlasKey; -} - -sk_sp Tree::Cache::getSurface(SkRect* bounds) { - sk_sp surface; -#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration - sp 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 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 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 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) { diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h index 9c0bb161380cf..e1b6f2adde74f 100644 --- a/libs/hwui/VectorDrawable.h +++ b/libs/hwui/VectorDrawable.h @@ -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& atlas, GrContext* context); - void setAntiAlias(bool aa) { mRootNode->setAntiAlias(aa); } private: class Cache { public: sk_sp 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 atlas, - skiapipeline::AtlasKey newAtlasKey); - - /** - * Gets a surface and bounds from the atlas. - * - * @return nullptr if the altas has been deleted. - */ - sk_sp getSurface(SkRect* bounds); - - /** - * Releases atlas key from the atlas, which makes it available for reuse. - */ - void clear(); - - private: - wp mAtlas; - skiapipeline::AtlasKey mAtlasKey = INVALID_ATLAS_KEY; }; bool allocateBitmapIfNeeded(Cache& cache, int width, int height); diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp index d7076d4cf4248..158c3493a90c9 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp @@ -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(info.canvasContext.getRenderPipeline()) - ->getVectorDrawables() - ->push_back(vectorDrawable); vectorDrawable->setPropertyChangeWillBeConsumed(true); -#endif } } } diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 3010206cdc5bb..87ef7fc9a6e18 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -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 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& 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); diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index 37b559f92f05f..215ff36ebb2b9 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -41,7 +41,6 @@ public: bool pinImages(std::vector& mutableImages) override; bool pinImages(LsaVector>& 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 surface, const SkMatrix& preTransform); - std::vector* 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>& nodes, const Rect& contentDrawBounds, sk_sp 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> mPinnedImages; - /** - * populated by prepareTree with dirty VDs - */ - std::vector 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 { diff --git a/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp b/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp deleted file mode 100644 index e783f389feb8b..0000000000000 --- a/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp +++ /dev/null @@ -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 -#include -#include -#include "renderthread/RenderProxy.h" -#include "renderthread/RenderThread.h" -#include "utils/TraceUtils.h" - -namespace android { -namespace uirenderer { -namespace skiapipeline { - -VectorDrawableAtlas::VectorDrawableAtlas(size_t surfaceArea, StorageMode storageMode) - : mWidth((int)std::sqrt(surfaceArea)) - , mHeight((int)std::sqrt(surfaceArea)) - , mStorageMode(storageMode) {} - -void VectorDrawableAtlas::prepareForDraw(GrContext* context) { - if (StorageMode::allowSharedSurface == mStorageMode) { - if (!mSurface) { - mSurface = createSurface(mWidth, mHeight, context); - mRectanizer = std::make_unique(mWidth, mHeight); - mPixelUsedByVDs = 0; - mPixelAllocated = 0; - mConsecutiveFailures = 0; - mFreeRects.clear(); - } else { - if (isFragmented()) { - // Invoke repack outside renderFrame to avoid jank. - renderthread::RenderProxy::repackVectorDrawableAtlas(); - } - } - } -} - -#define MAX_CONSECUTIVE_FAILURES 5 -#define MAX_UNUSED_RATIO 2.0f - -bool VectorDrawableAtlas::isFragmented() { - return mConsecutiveFailures > MAX_CONSECUTIVE_FAILURES && - mPixelUsedByVDs * MAX_UNUSED_RATIO < mPixelAllocated; -} - -void VectorDrawableAtlas::repackIfNeeded(GrContext* context) { - // We repackage when atlas failed to allocate space MAX_CONSECUTIVE_FAILURES consecutive - // times and the atlas allocated pixels are at least MAX_UNUSED_RATIO times higher than pixels - // used by atlas VDs. - if (isFragmented() && mSurface) { - repack(context); - } -} - -// compare to CacheEntry objects based on VD area. -bool VectorDrawableAtlas::compareCacheEntry(const CacheEntry& first, const CacheEntry& second) { - return first.VDrect.width() * first.VDrect.height() < - second.VDrect.width() * second.VDrect.height(); -} - -void VectorDrawableAtlas::repack(GrContext* context) { - ATRACE_CALL(); - sk_sp newSurface; - SkCanvas* canvas = nullptr; - if (StorageMode::allowSharedSurface == mStorageMode) { - newSurface = createSurface(mWidth, mHeight, context); - if (!newSurface) { - return; - } - canvas = newSurface->getCanvas(); - canvas->clear(SK_ColorTRANSPARENT); - mRectanizer = std::make_unique(mWidth, mHeight); - } else { - if (!mSurface) { - return; // nothing to repack - } - mRectanizer.reset(); - } - mFreeRects.clear(); - SkImage* sourceImageAtlas = nullptr; - if (mSurface) { - sourceImageAtlas = mSurface->makeImageSnapshot().get(); - } - - // Sort the list by VD size, which allows for the smallest VDs to get first in the atlas. - // Sorting is safe, because it does not affect iterator validity. - if (mRects.size() <= 100) { - mRects.sort(compareCacheEntry); - } - - for (CacheEntry& entry : mRects) { - SkRect currentVDRect = entry.VDrect; - SkImage* sourceImage; // copy either from the atlas or from a standalone surface - if (entry.surface) { - if (!fitInAtlas(currentVDRect.width(), currentVDRect.height())) { - continue; // don't even try to repack huge VD - } - sourceImage = entry.surface->makeImageSnapshot().get(); - } else { - sourceImage = sourceImageAtlas; - } - size_t VDRectArea = currentVDRect.width() * currentVDRect.height(); - SkIPoint16 pos; - if (canvas && mRectanizer->addRect(currentVDRect.width(), currentVDRect.height(), &pos)) { - SkRect newRect = - SkRect::MakeXYWH(pos.fX, pos.fY, currentVDRect.width(), currentVDRect.height()); - canvas->drawImageRect(sourceImage, currentVDRect, newRect, nullptr); - entry.VDrect = newRect; - entry.rect = newRect; - if (entry.surface) { - // A rectangle moved from a standalone surface to the atlas. - entry.surface = nullptr; - mPixelUsedByVDs += VDRectArea; - } - } else { - // Repack failed for this item. If it is not already, store it in a standalone - // surface. - if (!entry.surface) { - // A rectangle moved from an atlas to a standalone surface. - mPixelUsedByVDs -= VDRectArea; - SkRect newRect = SkRect::MakeWH(currentVDRect.width(), currentVDRect.height()); - entry.surface = createSurface(newRect.width(), newRect.height(), context); - auto tempCanvas = entry.surface->getCanvas(); - tempCanvas->clear(SK_ColorTRANSPARENT); - tempCanvas->drawImageRect(sourceImageAtlas, currentVDRect, newRect, nullptr); - entry.VDrect = newRect; - entry.rect = newRect; - } - } - } - mPixelAllocated = mPixelUsedByVDs; - context->flush(); - mSurface = newSurface; - mConsecutiveFailures = 0; -} - -AtlasEntry VectorDrawableAtlas::requestNewEntry(int width, int height, GrContext* context) { - AtlasEntry result; - if (width <= 0 || height <= 0) { - return result; - } - - if (mSurface) { - const size_t area = width * height; - - // Use a rectanizer to allocate unused space from the atlas surface. - bool notTooBig = fitInAtlas(width, height); - SkIPoint16 pos; - if (notTooBig && mRectanizer->addRect(width, height, &pos)) { - mPixelUsedByVDs += area; - mPixelAllocated += area; - result.rect = SkRect::MakeXYWH(pos.fX, pos.fY, width, height); - result.surface = mSurface; - auto eraseIt = mRects.emplace(mRects.end(), result.rect, result.rect, nullptr); - CacheEntry* entry = &(*eraseIt); - entry->eraseIt = eraseIt; - result.key = reinterpret_cast(entry); - mConsecutiveFailures = 0; - return result; - } - - // Try to reuse atlas memory from rectangles freed by "releaseEntry". - auto freeRectIt = mFreeRects.lower_bound(area); - while (freeRectIt != mFreeRects.end()) { - SkRect& freeRect = freeRectIt->second; - if (freeRect.width() >= width && freeRect.height() >= height) { - result.rect = SkRect::MakeXYWH(freeRect.fLeft, freeRect.fTop, width, height); - result.surface = mSurface; - auto eraseIt = mRects.emplace(mRects.end(), result.rect, freeRect, nullptr); - CacheEntry* entry = &(*eraseIt); - entry->eraseIt = eraseIt; - result.key = reinterpret_cast(entry); - mPixelUsedByVDs += area; - mFreeRects.erase(freeRectIt); - mConsecutiveFailures = 0; - return result; - } - freeRectIt++; - } - - if (notTooBig && mConsecutiveFailures <= MAX_CONSECUTIVE_FAILURES) { - mConsecutiveFailures++; - } - } - - // Allocate a surface for a rectangle that is too big or if atlas is full. - if (nullptr != context) { - result.rect = SkRect::MakeWH(width, height); - result.surface = createSurface(width, height, context); - auto eraseIt = mRects.emplace(mRects.end(), result.rect, result.rect, result.surface); - CacheEntry* entry = &(*eraseIt); - entry->eraseIt = eraseIt; - result.key = reinterpret_cast(entry); - } - - return result; -} - -AtlasEntry VectorDrawableAtlas::getEntry(AtlasKey atlasKey) { - AtlasEntry result; - if (INVALID_ATLAS_KEY != atlasKey) { - CacheEntry* entry = reinterpret_cast(atlasKey); - result.rect = entry->VDrect; - result.surface = entry->surface; - if (!result.surface) { - result.surface = mSurface; - } - result.key = atlasKey; - } - return result; -} - -void VectorDrawableAtlas::releaseEntry(AtlasKey atlasKey) { - if (INVALID_ATLAS_KEY != atlasKey) { - if (!renderthread::RenderThread::isCurrent()) { - { - AutoMutex _lock(mReleaseKeyLock); - mKeysForRelease.push_back(atlasKey); - } - // invoke releaseEntry on the renderthread - renderthread::RenderProxy::releaseVDAtlasEntries(); - return; - } - CacheEntry* entry = reinterpret_cast(atlasKey); - if (!entry->surface) { - // Store freed atlas rectangles in "mFreeRects" and try to reuse them later, when atlas - // is full. - SkRect& removedRect = entry->rect; - size_t rectArea = removedRect.width() * removedRect.height(); - mFreeRects.emplace(rectArea, removedRect); - SkRect& removedVDRect = entry->VDrect; - size_t VDRectArea = removedVDRect.width() * removedVDRect.height(); - mPixelUsedByVDs -= VDRectArea; - mConsecutiveFailures = 0; - } - auto eraseIt = entry->eraseIt; - mRects.erase(eraseIt); - } -} - -void VectorDrawableAtlas::delayedReleaseEntries() { - AutoMutex _lock(mReleaseKeyLock); - for (auto key : mKeysForRelease) { - releaseEntry(key); - } - mKeysForRelease.clear(); -} - -sk_sp VectorDrawableAtlas::createSurface(int width, int height, GrContext* context) { - SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType); - // This must have a top-left origin so that calls to surface->canvas->writePixels - // performs a basic texture upload instead of a more complex drawing operation - return SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info, 0, kTopLeft_GrSurfaceOrigin, - nullptr); -} - -void VectorDrawableAtlas::setStorageMode(StorageMode mode) { - mStorageMode = mode; - if (StorageMode::disallowSharedSurface == mStorageMode && mSurface) { - mSurface.reset(); - mRectanizer.reset(); - mFreeRects.clear(); - } -} - -} /* namespace skiapipeline */ -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/VectorDrawableAtlas.h b/libs/hwui/pipeline/skia/VectorDrawableAtlas.h deleted file mode 100644 index 5e892aa7e92cc..0000000000000 --- a/libs/hwui/pipeline/skia/VectorDrawableAtlas.h +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -class GrRectanizer; - -namespace android { -namespace uirenderer { -namespace skiapipeline { - -typedef uintptr_t AtlasKey; - -#define INVALID_ATLAS_KEY 0 - -struct AtlasEntry { - sk_sp 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& 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 surface; - - /** - * iterator is used to delete self with a constant complexity (without traversing the list) - */ - std::list::iterator eraseIt; - }; - - /** - * atlas surface shared by all VDs - */ - sk_sp mSurface; - - std::unique_ptr 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 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 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 mKeysForRelease; - - /** - * A lock used to protect access to mKeysForRelease. - */ - Mutex mReleaseKeyLock; - - sk_sp 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 */ diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index fc268138e071a..ab9b1e20a80dd 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -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 context) { @@ -76,9 +72,6 @@ void CacheManager::reset(sk_sp 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 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) { diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h index d7977cc4566db..857710babf0c7 100644 --- a/libs/hwui/renderthread/CacheManager.h +++ b/libs/hwui/renderthread/CacheManager.h @@ -25,8 +25,6 @@ #include #include -#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 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 mVectorDrawableAtlas; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 30cc007d454b1..b41bdf6a174ff 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -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& 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 diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index 3b81014c05e24..ef0aa98d4220d 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -80,7 +80,6 @@ public: virtual bool pinImages(std::vector& mutableImages) = 0; virtual bool pinImages(LsaVector>& images) = 0; virtual void unpinImages() = 0; - virtual void onPrepareTree() = 0; virtual SkColorType getSurfaceColorType() const = 0; virtual sk_sp getSurfaceColorSpace() = 0; virtual GrSurfaceOrigin getSurfaceOrigin() = 0; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 40fbdffaf90a3..4f7ad7b69f57b 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -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(); diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index c3eb6ede9bf20..e6fe1d4864dab 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -150,10 +150,6 @@ public: ANDROID_API static void preload(); - static void repackVectorDrawableAtlas(); - - static void releaseVDAtlasEntries(); - private: RenderThread& mRenderThread; CanvasContext* mContext; diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index 958baa78deabd..307d13606cb8b 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -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> renderNodes; - renderNodes.push_back(redNode); - bool opaque = true; - android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1); - auto pipeline = std::make_unique(renderThread); - { - // add a pointer to a deleted vector drawable object in the pipeline - sp 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) { diff --git a/libs/hwui/tests/unit/VectorDrawableAtlasTests.cpp b/libs/hwui/tests/unit/VectorDrawableAtlasTests.cpp deleted file mode 100644 index 0c95fdd428515..0000000000000 --- a/libs/hwui/tests/unit/VectorDrawableAtlasTests.cpp +++ /dev/null @@ -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 - -#include -#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 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 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()); -} \ No newline at end of file