Change how memory policy is configured
Should be a mostly no-op but adds a handful of new options
* Adds a 'MemoryPolicy' to mostly consolidate memory settings
* Moves trim handling into HWUI proper
* Adds settings for UI hidden & context destruction that's not
dependent on TRIM signals
* Treats persistent process the same as system_server
* Tweaks HardwareBitmapUploader timeout to reduce churn
Bug: 245565051
Test: builds & boots
Change-Id: I1f1b3db884ef7fa45ff2556436464a99440b998e
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index ded2b06..1d24e71 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -16,6 +16,16 @@
#include "CacheManager.h"
+#include <GrContextOptions.h>
+#include <SkExecutor.h>
+#include <SkGraphics.h>
+#include <SkMathPriv.h>
+#include <math.h>
+#include <utils/Trace.h>
+
+#include <set>
+
+#include "CanvasContext.h"
#include "DeviceInfo.h"
#include "Layer.h"
#include "Properties.h"
@@ -25,40 +35,34 @@
#include "pipeline/skia/SkiaMemoryTracer.h"
#include "renderstate/RenderState.h"
#include "thread/CommonPool.h"
-#include <utils/Trace.h>
-
-#include <GrContextOptions.h>
-#include <SkExecutor.h>
-#include <SkGraphics.h>
-#include <SkMathPriv.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)
+CacheManager::CacheManager(RenderThread& thread)
+ : mRenderThread(thread), mMemoryPolicy(loadMemoryPolicy()) {
+ mMaxSurfaceArea = static_cast<size_t>((DeviceInfo::getWidth() * DeviceInfo::getHeight()) *
+ mMemoryPolicy.initialMaxSurfaceAreaScale);
+ setupCacheLimits();
+}
-CacheManager::CacheManager()
- : mMaxSurfaceArea(DeviceInfo::getWidth() * DeviceInfo::getHeight())
- , mMaxResourceBytes(mMaxSurfaceArea * SURFACE_SIZE_MULTIPLIER)
- , mBackgroundResourceBytes(mMaxResourceBytes * BACKGROUND_RETENTION_PERCENTAGE)
- // This sets the maximum size for a single texture atlas in the GPU font cache. If
- // necessary, the cache can allocate additional textures that are counted against the
- // total cache limits provided to Skia.
- , mMaxGpuFontAtlasBytes(GrNextSizePow2(mMaxSurfaceArea))
- // This sets the maximum size of the CPU font cache to be at least the same size as the
- // total number of GPU font caches (i.e. 4 separate GPU atlases).
- , mMaxCpuFontCacheBytes(
- std::max(mMaxGpuFontAtlasBytes * 4, SkGraphics::GetFontCacheLimit()))
- , mBackgroundCpuFontCacheBytes(mMaxCpuFontCacheBytes * BACKGROUND_RETENTION_PERCENTAGE) {
+void CacheManager::setupCacheLimits() {
+ mMaxResourceBytes = mMaxSurfaceArea * mMemoryPolicy.surfaceSizeMultiplier;
+ mBackgroundResourceBytes = mMaxResourceBytes * mMemoryPolicy.backgroundRetentionPercent;
+ // This sets the maximum size for a single texture atlas in the GPU font cache. If
+ // necessary, the cache can allocate additional textures that are counted against the
+ // total cache limits provided to Skia.
+ mMaxGpuFontAtlasBytes = GrNextSizePow2(mMaxSurfaceArea);
+ // This sets the maximum size of the CPU font cache to be at least the same size as the
+ // total number of GPU font caches (i.e. 4 separate GPU atlases).
+ mMaxCpuFontCacheBytes = std::max(mMaxGpuFontAtlasBytes * 4, SkGraphics::GetFontCacheLimit());
+ mBackgroundCpuFontCacheBytes = mMaxCpuFontCacheBytes * mMemoryPolicy.backgroundRetentionPercent;
+
SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
+ if (mGrContext) {
+ mGrContext->setResourceCacheLimit(mMaxResourceBytes);
+ }
}
void CacheManager::reset(sk_sp<GrDirectContext> context) {
@@ -69,6 +73,7 @@
if (context) {
mGrContext = std::move(context);
mGrContext->setResourceCacheLimit(mMaxResourceBytes);
+ mLastDeferredCleanup = systemTime(CLOCK_MONOTONIC);
}
}
@@ -96,7 +101,7 @@
contextOptions->fGpuPathRenderers &= ~GpuPathRenderers::kCoverageCounting;
}
-void CacheManager::trimMemory(TrimMemoryMode mode) {
+void CacheManager::trimMemory(TrimLevel mode) {
if (!mGrContext) {
return;
}
@@ -104,21 +109,28 @@
// flush and submit all work to the gpu and wait for it to finish
mGrContext->flushAndSubmit(/*syncCpu=*/true);
+ if (!Properties::isHighEndGfx && mode >= TrimLevel::MODERATE) {
+ mode = TrimLevel::COMPLETE;
+ }
+
switch (mode) {
- case TrimMemoryMode::Complete:
+ case TrimLevel::COMPLETE:
mGrContext->freeGpuResources();
SkGraphics::PurgeAllCaches();
+ mRenderThread.destroyRenderingContext();
break;
- case TrimMemoryMode::UiHidden:
+ case TrimLevel::UI_HIDDEN:
// Here we purge all the unlocked scratch resources and then toggle the resources cache
// limits between the background and max amounts. This causes the unlocked resources
// that have persistent data to be purged in LRU order.
- mGrContext->purgeUnlockedResources(true);
mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
- mGrContext->setResourceCacheLimit(mMaxResourceBytes);
SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
+ mGrContext->purgeUnlockedResources(mMemoryPolicy.purgeScratchOnly);
+ mGrContext->setResourceCacheLimit(mMaxResourceBytes);
SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
break;
+ default:
+ break;
}
}
@@ -147,11 +159,29 @@
}
void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) {
+ log.appendFormat(R"(Memory policy:
+ Max surface area: %zu
+ Max resource usage: %.2fMB (x%.0f)
+ Background retention: %.0f%% (altUiHidden = %s)
+)",
+ mMaxSurfaceArea, mMaxResourceBytes / 1000000.f,
+ mMemoryPolicy.surfaceSizeMultiplier,
+ mMemoryPolicy.backgroundRetentionPercent * 100.0f,
+ mMemoryPolicy.useAlternativeUiHidden ? "true" : "false");
+ if (Properties::isSystemOrPersistent) {
+ log.appendFormat(" IsSystemOrPersistent\n");
+ }
+ log.appendFormat(" GPU Context timeout: %" PRIu64 "\n", ns2s(mMemoryPolicy.contextTimeout));
+ size_t stoppedContexts = 0;
+ for (auto context : mCanvasContexts) {
+ if (context->isStopped()) stoppedContexts++;
+ }
+ log.appendFormat("Contexts: %zu (stopped = %zu)\n", mCanvasContexts.size(), stoppedContexts);
+
if (!mGrContext) {
- log.appendFormat("No valid cache instance.\n");
+ log.appendFormat("No GPU context.\n");
return;
}
-
std::vector<skiapipeline::ResourcePair> cpuResourceMap = {
{"skia/sk_resource_cache/bitmap_", "Bitmaps"},
{"skia/sk_resource_cache/rrect-blur_", "Masks"},
@@ -199,6 +229,8 @@
}
void CacheManager::onFrameCompleted() {
+ cancelDestroyContext();
+ mFrameCompletions.next() = systemTime(CLOCK_MONOTONIC);
if (ATRACE_ENABLED()) {
static skiapipeline::ATraceMemoryDump tracer;
tracer.startFrame();
@@ -210,11 +242,82 @@
}
}
-void CacheManager::performDeferredCleanup(nsecs_t cleanupOlderThanMillis) {
- if (mGrContext) {
- mGrContext->performDeferredCleanup(
- std::chrono::milliseconds(cleanupOlderThanMillis),
- /* scratchResourcesOnly */true);
+void CacheManager::onThreadIdle() {
+ if (!mGrContext || mFrameCompletions.size() == 0) return;
+
+ const nsecs_t now = systemTime(CLOCK_MONOTONIC);
+ // Rate limiting
+ if ((now - mLastDeferredCleanup) < 25_ms) {
+ mLastDeferredCleanup = now;
+ const nsecs_t frameCompleteNanos = mFrameCompletions[0];
+ const nsecs_t frameDiffNanos = now - frameCompleteNanos;
+ const nsecs_t cleanupMillis =
+ ns2ms(std::max(frameDiffNanos, mMemoryPolicy.minimumResourceRetention));
+ mGrContext->performDeferredCleanup(std::chrono::milliseconds(cleanupMillis),
+ mMemoryPolicy.purgeScratchOnly);
+ }
+}
+
+void CacheManager::scheduleDestroyContext() {
+ if (mMemoryPolicy.contextTimeout > 0) {
+ mRenderThread.queue().postDelayed(mMemoryPolicy.contextTimeout,
+ [this, genId = mGenerationId] {
+ if (mGenerationId != genId) return;
+ // GenID should have already stopped this, but just in
+ // case
+ if (!areAllContextsStopped()) return;
+ mRenderThread.destroyRenderingContext();
+ });
+ }
+}
+
+void CacheManager::cancelDestroyContext() {
+ if (mIsDestructionPending) {
+ mIsDestructionPending = false;
+ mGenerationId++;
+ }
+}
+
+bool CacheManager::areAllContextsStopped() {
+ for (auto context : mCanvasContexts) {
+ if (!context->isStopped()) return false;
+ }
+ return true;
+}
+
+void CacheManager::checkUiHidden() {
+ if (!mGrContext) return;
+
+ if (mMemoryPolicy.useAlternativeUiHidden && areAllContextsStopped()) {
+ trimMemory(TrimLevel::UI_HIDDEN);
+ }
+}
+
+void CacheManager::registerCanvasContext(CanvasContext* context) {
+ mCanvasContexts.push_back(context);
+ cancelDestroyContext();
+}
+
+void CacheManager::unregisterCanvasContext(CanvasContext* context) {
+ std::erase(mCanvasContexts, context);
+ checkUiHidden();
+ if (mCanvasContexts.empty()) {
+ scheduleDestroyContext();
+ }
+}
+
+void CacheManager::onContextStopped(CanvasContext* context) {
+ checkUiHidden();
+ if (mMemoryPolicy.releaseContextOnStoppedOnly && areAllContextsStopped()) {
+ scheduleDestroyContext();
+ }
+}
+
+void CacheManager::notifyNextFrameSize(int width, int height) {
+ int frameArea = width * height;
+ if (frameArea > mMaxSurfaceArea) {
+ mMaxSurfaceArea = frameArea;
+ setupCacheLimits();
}
}