diff options
| author | 2017-06-06 20:37:01 +0000 | |
|---|---|---|
| committer | 2017-06-06 20:37:08 +0000 | |
| commit | 12741cec2d46e54c38eb7fe7b387f9f98f8c87fc (patch) | |
| tree | d3e28cc48e53733aedd243a71762a9f8a78cb48f | |
| parent | ae6455957b13fbd5e892231cec2e9d243b5fd449 (diff) | |
| parent | f9e45d1d818ae0956ba77ed598b7040cfecca553 (diff) | |
Merge "Implement CacheManager for the Skia pipelines."
| -rw-r--r-- | libs/hwui/Android.bp | 2 | ||||
| -rw-r--r-- | libs/hwui/pipeline/skia/SkiaPipeline.cpp | 3 | ||||
| -rw-r--r-- | libs/hwui/renderstate/RenderState.h | 2 | ||||
| -rw-r--r-- | libs/hwui/renderthread/CacheManager.cpp | 187 | ||||
| -rw-r--r-- | libs/hwui/renderthread/CacheManager.h | 92 | ||||
| -rw-r--r-- | libs/hwui/renderthread/CanvasContext.cpp | 40 | ||||
| -rw-r--r-- | libs/hwui/renderthread/EglManager.cpp | 2 | ||||
| -rw-r--r-- | libs/hwui/renderthread/RenderProxy.cpp | 13 | ||||
| -rw-r--r-- | libs/hwui/renderthread/RenderThread.cpp | 47 | ||||
| -rw-r--r-- | libs/hwui/renderthread/RenderThread.h | 6 | ||||
| -rw-r--r-- | libs/hwui/tests/unit/CacheManagerTests.cpp | 75 |
11 files changed, 444 insertions, 25 deletions
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 4cfb9d88c716..303d05f084aa 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -141,6 +141,7 @@ cc_defaults { "renderstate/Scissor.cpp", "renderstate/Stencil.cpp", "renderstate/TextureState.cpp", + "renderthread/CacheManager.cpp", "renderthread/CanvasContext.cpp", "renderthread/OpenGLPipeline.cpp", "renderthread/DrawFrameTask.cpp", @@ -300,6 +301,7 @@ cc_test { "tests/unit/BakedOpRendererTests.cpp", "tests/unit/BakedOpStateTests.cpp", "tests/unit/BitmapTests.cpp", + "tests/unit/CacheManagerTests.cpp", "tests/unit/CanvasContextTests.cpp", "tests/unit/CanvasStateTests.cpp", "tests/unit/ClipAreaTests.cpp", diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 88293db193f8..bbbbd5ce51b2 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -50,8 +50,7 @@ TaskManager* SkiaPipeline::getTaskManager() { } void SkiaPipeline::onDestroyHardwareResources() { - // No need to flush the caches here. There is a timer - // which will flush temporary resources over time. + mRenderThread.cacheManager().trimStaleResources(); } bool SkiaPipeline::pinImages(std::vector<SkImage*>& mutableImages) { diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h index 787946f79f6b..4b7a86580621 100644 --- a/libs/hwui/renderstate/RenderState.h +++ b/libs/hwui/renderstate/RenderState.h @@ -45,6 +45,7 @@ class Layer; class DeferredLayerUpdater; namespace renderthread { +class CacheManager; class CanvasContext; class RenderThread; } @@ -55,6 +56,7 @@ class RenderState { PREVENT_COPY_AND_ASSIGN(RenderState); friend class renderthread::RenderThread; friend class Caches; + friend class renderthread::CacheManager; public: void onGLContextCreated(); void onGLContextDestroyed(); diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp new file mode 100644 index 000000000000..f0d6b3860938 --- /dev/null +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -0,0 +1,187 @@ +/* + * 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 "CacheManager.h" + +#include "Layer.h" +#include "RenderThread.h" +#include "renderstate/RenderState.h" + +#include <gui/Surface.h> +#include <GrContextOptions.h> +#include <math.h> +#include <set> + +namespace android { +namespace uirenderer { +namespace renderthread { + +// This multiplier was selected based on historical review of cache sizes relative +// to the screen resolution. This is meant to be a conservative default based on +// that analysis. The 4.0f is used because the default pixel format is assumed to +// be ARGB_8888. +#define SURFACE_SIZE_MULTIPLIER (12.0f * 4.0f) +#define BACKGROUND_RETENTION_PERCENTAGE (0.5f) + +// for super large fonts we will draw them as paths so no need to keep linearly +// increasing the font cache size. +#define FONT_CACHE_MIN_MB (0.5f) +#define FONT_CACHE_MAX_MB (4.0f) + +CacheManager::CacheManager(const DisplayInfo& display) + : mMaxSurfaceArea(display.w * display.h) { + mVectorDrawableAtlas.reset(new VectorDrawableAtlas); +} + +void CacheManager::reset(GrContext* context) { + if (context != mGrContext.get()) { + destroy(); + } + + if (context) { + mGrContext = sk_ref_sp(context); + mGrContext->getResourceCacheLimits(&mMaxResources, nullptr); + updateContextCacheSizes(); + } +} + +void CacheManager::destroy() { + // cleanup any caches here as the GrContext is about to go away... + mGrContext.reset(nullptr); + mVectorDrawableAtlas.reset(new VectorDrawableAtlas); +} + +void CacheManager::updateContextCacheSizes() { + mMaxResourceBytes = mMaxSurfaceArea * SURFACE_SIZE_MULTIPLIER; + mBackgroundResourceBytes = mMaxResourceBytes * BACKGROUND_RETENTION_PERCENTAGE; + + mGrContext->setResourceCacheLimits(mMaxResources, mMaxResourceBytes); +} + +void CacheManager::configureContext(GrContextOptions* contextOptions) { + contextOptions->fAllowPathMaskCaching = true; + + float screenMP = mMaxSurfaceArea / 1024.0f / 1024.0f; + float fontCacheMB = 0; + float decimalVal = std::modf(screenMP, &fontCacheMB); + + // This is a basic heuristic to size the cache to a multiple of 512 KB + if (decimalVal > 0.8f) { + fontCacheMB += 1.0f; + } else if (decimalVal > 0.5f) { + fontCacheMB += 0.5f; + } + + // set limits on min/max size of the cache + fontCacheMB = std::max(FONT_CACHE_MIN_MB, std::min(FONT_CACHE_MAX_MB, fontCacheMB)); + + // We must currently set the size of the text cache based on the size of the + // display even though we like to be dynamicallysizing it to the size of the window. + // Skia's implementation doesn't provide a mechanism to resize the font cache due to + // the potential cost of recreating the glyphs. + contextOptions->fGlyphCacheTextureMaximumBytes = fontCacheMB * 1024 * 1024; +} + +void CacheManager::trimMemory(TrimMemoryMode mode) { + if (!mGrContext) { + return; + } + + mGrContext->flush(); + + switch (mode) { + case TrimMemoryMode::Complete: + mVectorDrawableAtlas.reset(new VectorDrawableAtlas); + mGrContext->freeGpuResources(); + break; + case TrimMemoryMode::UiHidden: + mGrContext->purgeUnlockedResources(mMaxResourceBytes - mBackgroundResourceBytes, true); + break; + } +} + +void CacheManager::trimStaleResources() { + if (!mGrContext) { + return; + } + mGrContext->flush(); + mGrContext->purgeResourcesNotUsedInMs(std::chrono::seconds(30)); +} + +VectorDrawableAtlas* CacheManager::acquireVectorDrawableAtlas() { + LOG_ALWAYS_FATAL_IF(mVectorDrawableAtlas.get() == nullptr); + LOG_ALWAYS_FATAL_IF(mGrContext == nullptr); + + /** + * TODO LIST: + * 1) compute the atlas based on the surfaceArea surface + * 2) identify a way to reuse cache entries + * 3) add ability to repack the cache? + * 4) define memory conditions where we clear the cache (e.g. surface->reset()) + */ + + return mVectorDrawableAtlas.release(); +} +void CacheManager::releaseVectorDrawableAtlas(VectorDrawableAtlas* atlas) { + LOG_ALWAYS_FATAL_IF(mVectorDrawableAtlas.get() != nullptr); + mVectorDrawableAtlas.reset(atlas); + mVectorDrawableAtlas->isNewAtlas = false; +} + +void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) { + if (!mGrContext) { + log.appendFormat("No valid cache instance.\n"); + return; + } + + size_t bytesCached; + mGrContext->getResourceCacheUsage(nullptr, &bytesCached); + + log.appendFormat("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) { + log.appendFormat(" Layer Info:\n"); + } + + size_t layerMemoryTotal = 0; + for (std::set<Layer*>::iterator it = renderState->mActiveLayers.begin(); + it != renderState->mActiveLayers.end(); it++) { + const Layer* layer = *it; + const char* layerType = layer->getApi() == Layer::Api::OpenGL ? "GlLayer" : "VkLayer"; + log.appendFormat(" %s size %dx%d\n", layerType, + layer->getWidth(), layer->getHeight()); + layerMemoryTotal += layer->getWidth() * layer->getHeight() * 4; + } + log.appendFormat(" Layers Total %6.2f kB (numLayers = %zu)\n", + layerMemoryTotal / 1024.0f, renderState->mActiveLayers.size()); + } + + + log.appendFormat("Total memory usage:\n"); + log.appendFormat(" %zu bytes, %.2f MB (%.2f MB is purgeable)\n", + bytesCached, bytesCached / 1024.0f / 1024.0f, + mGrContext->getResourceCachePurgeableBytes() / 1024.0f / 1024.0f); + + +} + +} /* namespace renderthread */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h new file mode 100644 index 000000000000..43d58f2d58a8 --- /dev/null +++ b/libs/hwui/renderthread/CacheManager.h @@ -0,0 +1,92 @@ +/* + * 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. + */ + +#ifndef CACHEMANAGER_H +#define CACHEMANAGER_H + +#include <GrContext.h> +#include <SkSurface.h> +#include <ui/DisplayInfo.h> +#include <utils/String8.h> +#include <vector> + +namespace android { + +class Surface; + +namespace uirenderer { + +class RenderState; + +namespace renderthread { + +class IRenderPipeline; +class RenderThread; + +struct VectorDrawableAtlas { + sk_sp<SkSurface> surface; + bool isNewAtlas = true; +}; + +class CacheManager { +public: + enum class TrimMemoryMode { + Complete, + UiHidden + }; + + void configureContext(GrContextOptions* context); + void trimMemory(TrimMemoryMode mode); + void trimStaleResources(); + void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr); + + VectorDrawableAtlas* acquireVectorDrawableAtlas(); + void releaseVectorDrawableAtlas(VectorDrawableAtlas*); + + size_t getCacheSize() const { return mMaxResourceBytes; } + size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; } + +private: + friend class RenderThread; + + CacheManager(const DisplayInfo& display); + + + void reset(GrContext* grContext); + void destroy(); + void updateContextCacheSizes(); + + const size_t mMaxSurfaceArea; + sk_sp<GrContext> mGrContext; + + int mMaxResources = 0; + size_t mMaxResourceBytes = 0; + size_t mBackgroundResourceBytes = 0; + + struct PipelineProps { + const void* pipelineKey = nullptr; + size_t surfaceArea = 0; + }; + + std::unique_ptr<VectorDrawableAtlas> mVectorDrawableAtlas; +}; + +} /* namespace renderthread */ +} /* namespace uirenderer */ +} /* namespace android */ + +#endif /* CACHEMANAGER_H */ + diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index e2a4a2a8dec2..a79bf359913b 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -585,15 +585,37 @@ void CanvasContext::destroyHardwareResources() { } void CanvasContext::trimMemory(RenderThread& thread, int level) { - // No context means nothing to free - if (!thread.eglManager().hasEglContext()) return; - - ATRACE_CALL(); - if (level >= TRIM_MEMORY_COMPLETE) { - thread.renderState().flush(Caches::FlushMode::Full); - thread.eglManager().destroy(); - } else if (level >= TRIM_MEMORY_UI_HIDDEN) { - thread.renderState().flush(Caches::FlushMode::Moderate); + auto renderType = Properties::getRenderPipelineType(); + switch (renderType) { + case RenderPipelineType::OpenGL: { + // No context means nothing to free + if (!thread.eglManager().hasEglContext()) return; + ATRACE_CALL(); + if (level >= TRIM_MEMORY_COMPLETE) { + thread.renderState().flush(Caches::FlushMode::Full); + thread.eglManager().destroy(); + } else if (level >= TRIM_MEMORY_UI_HIDDEN) { + thread.renderState().flush(Caches::FlushMode::Moderate); + } + break; + } + case RenderPipelineType::SkiaGL: + case RenderPipelineType::SkiaVulkan: { + // No context means nothing to free + if (!thread.getGrContext()) return; + ATRACE_CALL(); + if (level >= TRIM_MEMORY_COMPLETE) { + thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete); + thread.eglManager().destroy(); + thread.vulkanManager().destroy(); + } else if (level >= TRIM_MEMORY_UI_HIDDEN) { + thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden); + } + break; + } + default: + LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType); + break; } } diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 53d42a2b9c43..ecf686c5b40c 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -138,7 +138,7 @@ void EglManager::initialize() { GrContextOptions options; options.fGpuPathRenderers &= ~GrContextOptions::GpuPathRenderers::kDistanceField; - options.fAllowPathMaskCaching = true; + mRenderThread.cacheManager().configureContext(&options); mRenderThread.setGrContext(GrContext::Create(GrBackend::kOpenGL_GrBackend, (GrBackendContext)glInterface.get(), options)); } diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 5a4695f1315e..ec56c313f62a 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -469,18 +469,7 @@ uint32_t RenderProxy::frameTimePercentile(int p) { } CREATE_BRIDGE2(dumpGraphicsMemory, int fd, RenderThread* thread) { - args->thread->jankTracker().dump(args->fd); - - FILE *file = fdopen(args->fd, "a"); - if (Caches::hasInstance()) { - String8 cachesLog; - Caches::getInstance().dumpMemoryUsage(cachesLog); - fprintf(file, "\nCaches:\n%s\n", cachesLog.string()); - } else { - fprintf(file, "\nNo caches instance.\n"); - } - fprintf(file, "\nPipeline=FrameBuilder\n"); - fflush(file); + args->thread->dumpGraphicsMemory(args->fd); return nullptr; } diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 055458397023..13af2c4d15e8 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -202,6 +202,45 @@ void RenderThread::initThreadLocals() { mRenderState = new RenderState(*this); mJankTracker = new JankTracker(mDisplayInfo); mVkManager = new VulkanManager(*this); + mCacheManager = new CacheManager(mDisplayInfo); +} + +void RenderThread::dumpGraphicsMemory(int fd) { + jankTracker().dump(fd); + + String8 cachesOutput; + String8 pipeline; + auto renderType = Properties::getRenderPipelineType(); + switch (renderType) { + case RenderPipelineType::OpenGL: { + if (Caches::hasInstance()) { + cachesOutput.appendFormat("Caches:\n"); + Caches::getInstance().dumpMemoryUsage(cachesOutput); + } else { + cachesOutput.appendFormat("No caches instance."); + } + pipeline.appendFormat("FrameBuilder"); + break; + } + case RenderPipelineType::SkiaGL: { + mCacheManager->dumpMemoryUsage(cachesOutput, mRenderState); + pipeline.appendFormat("Skia (OpenGL)"); + break; + } + case RenderPipelineType::SkiaVulkan: { + mCacheManager->dumpMemoryUsage(cachesOutput, mRenderState); + pipeline.appendFormat("Skia (Vulkan)"); + break; + } + default: + LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType); + break; + } + + FILE *file = fdopen(fd, "a"); + fprintf(file, "\n%s\n", cachesOutput.string()); + fprintf(file, "\nPipeline=%s\n", pipeline.string()); + fflush(file); } Readback& RenderThread::readback() { @@ -228,6 +267,14 @@ Readback& RenderThread::readback() { return *mReadback; } +void RenderThread::setGrContext(GrContext* context) { + mCacheManager->reset(context); + if (mGrContext.get()) { + mGrContext->releaseResourcesAndAbandonContext(); + } + mGrContext.reset(context); +} + int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) { if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) { ALOGE("Display event receiver pipe was closed or an error occurred. " diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h index 4b5601c5abc4..d9842572d7cd 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -20,6 +20,7 @@ #include "RenderTask.h" #include "../JankTracker.h" +#include "CacheManager.h" #include "TimeLord.h" #include <GrContext.h> @@ -102,11 +103,13 @@ public: const DisplayInfo& mainDisplayInfo() { return mDisplayInfo; } GrContext* getGrContext() const { return mGrContext.get(); } - void setGrContext(GrContext* cxt) { mGrContext.reset(cxt); } + void setGrContext(GrContext* cxt); + CacheManager& cacheManager() { return *mCacheManager; } VulkanManager& vulkanManager() { return *mVkManager; } sk_sp<Bitmap> allocateHardwareBitmap(SkBitmap& skBitmap); + void dumpGraphicsMemory(int fd); protected: virtual bool threadLoop() override; @@ -161,6 +164,7 @@ private: Readback* mReadback = nullptr; sk_sp<GrContext> mGrContext; + CacheManager* mCacheManager; VulkanManager* mVkManager; }; diff --git a/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp new file mode 100644 index 000000000000..6115162c8f81 --- /dev/null +++ b/libs/hwui/tests/unit/CacheManagerTests.cpp @@ -0,0 +1,75 @@ +/* + * 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 "renderthread/CacheManager.h" +#include "renderthread/EglManager.h" +#include "tests/common/TestUtils.h" + +using namespace android; +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; + +static size_t getCacheUsage(GrContext* grContext) { + size_t cacheUsage; + grContext->getResourceCacheUsage(nullptr, &cacheUsage); + return cacheUsage; +} + +RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, trimMemory) { + DisplayInfo displayInfo = renderThread.mainDisplayInfo(); + GrContext* grContext = renderThread.getGrContext(); + ASSERT_TRUE(grContext != nullptr); + + // create pairs of offscreen render targets and images until we exceed the backgroundCacheSizeLimit + std::vector<sk_sp<SkSurface>> surfaces; + + while (getCacheUsage(grContext) <= renderThread.cacheManager().getBackgroundCacheSize()) { + SkImageInfo info = SkImageInfo::MakeA8(displayInfo.w, displayInfo.h); + sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(grContext, SkBudgeted::kYes, info); + surface->getCanvas()->drawColor(SK_AlphaTRANSPARENT); + + grContext->flush(); + + surfaces.push_back(surface); + } + + ASSERT_TRUE(1 < surfaces.size()); + + // attempt to trim all memory while we still hold strong refs + renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete); + ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes()); + + // free the surfaces + for (size_t i = 0; i < surfaces.size(); i++) { + ASSERT_TRUE(surfaces[i]->unique()); + surfaces[i].reset(); + } + + // verify that we have enough purgeable bytes + const size_t purgeableBytes = grContext->getResourceCachePurgeableBytes(); + ASSERT_TRUE(renderThread.cacheManager().getBackgroundCacheSize() < purgeableBytes); + + // UI hidden and make sure only some got purged + renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden); + ASSERT_TRUE(0 < grContext->getResourceCachePurgeableBytes()); + ASSERT_TRUE(renderThread.cacheManager().getBackgroundCacheSize() > getCacheUsage(grContext)); + + // complete and make sure all get purged + renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete); + ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes()); +} |