Implement CacheManager for the Skia pipelines.
The core of the implementation is complete and provides heuristic
cache sizing based on the size of the surface being used. This CL
will also be used to add the following features in the future...
1) Support Vulkan pipeline reporting on the size of the surface.
2) Complete the VectorDrawableAtlas stub code
3) Automatic purging of stale resources for low memory devices.
Test: hwui_unit_tests (new test added) and CtsUiRendering
Bug: 62260637
Change-Id: Ib85159cca28b646fe249f2190b07f1b7e0f50d8f
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
new file mode 100644
index 0000000..f0d6b38
--- /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 */