Merge "Change how memory policy is configured"

This commit is contained in:
John Reck
2022-09-28 20:08:10 +00:00
committed by Android (Google) Code Review
21 changed files with 435 additions and 206 deletions

View File

@@ -6662,6 +6662,9 @@ public final class ActivityThread extends ClientTransactionHandler
// Pass the current context to HardwareRenderer
HardwareRenderer.setContextForInit(getSystemContext());
if (data.persistent) {
HardwareRenderer.setIsSystemOrPersistent();
}
// Instrumentation info affects the class loader, so load it before
// setting up the app context.

View File

@@ -196,8 +196,6 @@ public final class ThreadedRenderer extends HardwareRenderer {
*/
public static boolean sRendererEnabled = true;
public static boolean sTrimForeground = false;
/**
* Controls whether or not the renderer should aggressively trim
* memory. Note that this must not be set for any process that uses
@@ -205,9 +203,10 @@ public final class ThreadedRenderer extends HardwareRenderer {
* that do not go into the background.
*/
public static void enableForegroundTrimming() {
sTrimForeground = true;
// TODO: Remove
}
/**
* Initialize HWUI for being in a system process like system_server
* Should not be called in non-system processes
@@ -218,9 +217,8 @@ public final class ThreadedRenderer extends HardwareRenderer {
// process.
if (!ActivityManager.isHighEndGfx()) {
sRendererEnabled = false;
} else {
enableForegroundTrimming();
}
setIsSystemOrPersistent();
}
/**

View File

@@ -1766,9 +1766,6 @@ public final class ViewRootImpl implements ViewParent,
mAppVisibilityChanged = true;
scheduleTraversals();
}
if (!mAppVisible) {
WindowManagerGlobal.trimForeground();
}
AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
}
}

View File

@@ -19,9 +19,7 @@ package android.view;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
@@ -524,9 +522,6 @@ public final class WindowManagerGlobal {
}
allViewsRemoved = mRoots.isEmpty();
}
if (ThreadedRenderer.sTrimForeground) {
doTrimForeground();
}
// If we don't have any views anymore in our process, we no longer need the
// InsetsAnimationThread to save some resources.
@@ -543,65 +538,9 @@ public final class WindowManagerGlobal {
return index;
}
public static boolean shouldDestroyEglContext(int trimLevel) {
// On low-end gfx devices we trim when memory is moderate;
// on high-end devices we do this when low.
if (trimLevel >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
return true;
}
if (trimLevel >= ComponentCallbacks2.TRIM_MEMORY_MODERATE
&& !ActivityManager.isHighEndGfx()) {
return true;
}
return false;
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public void trimMemory(int level) {
if (shouldDestroyEglContext(level)) {
// Destroy all hardware surfaces and resources associated to
// known windows
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).destroyHardwareResources();
}
}
// Force a full memory flush
level = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
}
ThreadedRenderer.trimMemory(level);
if (ThreadedRenderer.sTrimForeground) {
doTrimForeground();
}
}
public static void trimForeground() {
if (ThreadedRenderer.sTrimForeground) {
WindowManagerGlobal wm = WindowManagerGlobal.getInstance();
wm.doTrimForeground();
}
}
private void doTrimForeground() {
boolean hasVisibleWindows = false;
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
final ViewRootImpl root = mRoots.get(i);
if (root.mView != null && root.getHostVisibility() == View.VISIBLE
&& root.mAttachInfo.mThreadedRenderer != null) {
hasVisibleWindows = true;
} else {
root.destroyHardwareResources();
}
}
}
if (!hasVisibleWindows) {
ThreadedRenderer.trimMemory(
ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
}
}
public void dumpGfxInfo(FileDescriptor fd, String[] args) {

View File

@@ -47,9 +47,7 @@ import java.io.File;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.stream.Stream;
import sun.misc.Cleaner;
@@ -1141,6 +1139,16 @@ public class HardwareRenderer {
ProcessInitializer.sInstance.setContext(context);
}
/**
* Sets whether or not the current process is a system or persistent process. Used to influence
* the chosen memory usage policy.
*
* @hide
**/
public static void setIsSystemOrPersistent() {
nSetIsSystemOrPersistent(true);
}
/**
* Returns true if HardwareRender will produce output.
*
@@ -1204,30 +1212,6 @@ public class HardwareRenderer {
private static class ProcessInitializer {
static ProcessInitializer sInstance = new ProcessInitializer();
// Magic values from android/data_space.h
private static final int INTERNAL_DATASPACE_SRGB = 142671872;
private static final int INTERNAL_DATASPACE_DISPLAY_P3 = 143261696;
private static final int INTERNAL_DATASPACE_SCRGB = 411107328;
private enum Dataspace {
DISPLAY_P3(ColorSpace.Named.DISPLAY_P3, INTERNAL_DATASPACE_DISPLAY_P3),
SCRGB(ColorSpace.Named.EXTENDED_SRGB, INTERNAL_DATASPACE_SCRGB),
SRGB(ColorSpace.Named.SRGB, INTERNAL_DATASPACE_SRGB);
private final ColorSpace.Named mColorSpace;
private final int mNativeDataspace;
Dataspace(ColorSpace.Named colorSpace, int nativeDataspace) {
this.mColorSpace = colorSpace;
this.mNativeDataspace = nativeDataspace;
}
static Optional<Dataspace> find(ColorSpace colorSpace) {
return Stream.of(Dataspace.values())
.filter(d -> ColorSpace.get(d.mColorSpace).equals(colorSpace))
.findFirst();
}
}
private boolean mInitialized = false;
private boolean mDisplayInitialized = false;
@@ -1296,6 +1280,7 @@ public class HardwareRenderer {
initDisplayInfo();
nSetIsHighEndGfx(ActivityManager.isHighEndGfx());
nSetIsLowRam(ActivityManager.isLowRamDeviceStatic());
// Defensively clear out the context in case we were passed a context that can leak
// if we live longer than it, e.g. an activity context.
mContext = null;
@@ -1314,26 +1299,55 @@ public class HardwareRenderer {
return;
}
Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
if (display == null) {
final Display defaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
if (defaultDisplay == null) {
Log.d(LOG_TAG, "Failed to find default display for display-based configuration");
return;
}
Dataspace wideColorDataspace =
Optional.ofNullable(display.getPreferredWideGamutColorSpace())
.flatMap(Dataspace::find)
// Default to SRGB if the display doesn't support wide color
.orElse(Dataspace.SRGB);
final Display[] allDisplays = dm.getDisplays();
if (allDisplays.length == 0) {
Log.d(LOG_TAG, "Failed to query displays");
return;
}
// Grab the physical screen dimensions from the active display mode
// Strictly speaking the screen resolution may not always be constant - it is for
// sizing the font cache for the underlying rendering thread. Since it's a
// heuristic we don't need to be always 100% correct.
Mode activeMode = display.getMode();
nInitDisplayInfo(activeMode.getPhysicalWidth(), activeMode.getPhysicalHeight(),
display.getRefreshRate(), wideColorDataspace.mNativeDataspace,
display.getAppVsyncOffsetNanos(), display.getPresentationDeadlineNanos());
final Mode activeMode = defaultDisplay.getMode();
final ColorSpace defaultWideColorSpace =
defaultDisplay.getPreferredWideGamutColorSpace();
int wideColorDataspace = defaultWideColorSpace != null
? defaultWideColorSpace.getDataSpace() : 0;
// largest width & height are used to size the default HWUI cache sizes. So find the
// largest display resolution we could encounter & use that as the guidance. The actual
// memory policy in play will interpret these values differently.
int largestWidth = activeMode.getPhysicalWidth();
int largestHeight = activeMode.getPhysicalHeight();
for (int i = 0; i < allDisplays.length; i++) {
final Display display = allDisplays[i];
// Take the first wide gamut dataspace as the source of truth
// Possibly should do per-HardwareRenderer wide gamut dataspace so we can use the
// target display's ideal instead
if (wideColorDataspace == 0) {
ColorSpace cs = display.getPreferredWideGamutColorSpace();
if (cs != null) {
wideColorDataspace = cs.getDataSpace();
}
}
Mode[] modes = display.getSupportedModes();
for (int j = 0; j < modes.length; j++) {
Mode mode = modes[j];
int width = mode.getPhysicalWidth();
int height = mode.getPhysicalHeight();
if ((width * height) > (largestWidth * largestHeight)) {
largestWidth = width;
largestHeight = height;
}
}
}
nInitDisplayInfo(largestWidth, largestHeight, defaultDisplay.getRefreshRate(),
wideColorDataspace, defaultDisplay.getAppVsyncOffsetNanos(),
defaultDisplay.getPresentationDeadlineNanos());
mDisplayInitialized = true;
}
@@ -1418,6 +1432,10 @@ public class HardwareRenderer {
private static native void nSetIsHighEndGfx(boolean isHighEndGfx);
private static native void nSetIsLowRam(boolean isLowRam);
private static native void nSetIsSystemOrPersistent(boolean isSystemOrPersistent);
private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
private static native void nDestroy(long nativeProxy, long rootRenderNode);

View File

@@ -521,6 +521,7 @@ cc_defaults {
"Interpolator.cpp",
"LightingInfo.cpp",
"Matrix.cpp",
"MemoryPolicy.cpp",
"PathParser.cpp",
"Properties.cpp",
"PropertyValuesAnimatorSet.cpp",

View File

@@ -93,12 +93,14 @@ void DeviceInfo::setWideColorDataspace(ADataSpace dataspace) {
case ADATASPACE_SCRGB:
get()->mWideColorSpace = SkColorSpace::MakeSRGB();
break;
default:
ALOGW("Unknown dataspace %d", dataspace);
// Treat unknown dataspaces as sRGB, so fall through
[[fallthrough]];
case ADATASPACE_SRGB:
// when sRGB is returned, it means wide color gamut is not supported.
get()->mWideColorSpace = SkColorSpace::MakeSRGB();
break;
default:
LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
}
}

View File

@@ -42,6 +42,8 @@
namespace android::uirenderer {
static constexpr auto kThreadTimeout = 60000_ms;
class AHBUploader;
// This helper uploader classes allows us to upload using either EGL or Vulkan using the same
// interface.
@@ -80,7 +82,7 @@ public:
}
void postIdleTimeoutCheck() {
mUploadThread->queue().postDelayed(5000_ms, [this](){ this->idleTimeoutCheck(); });
mUploadThread->queue().postDelayed(kThreadTimeout, [this]() { this->idleTimeoutCheck(); });
}
protected:
@@ -97,7 +99,7 @@ private:
bool shouldTimeOutLocked() {
nsecs_t durationSince = systemTime() - mLastUpload;
return durationSince > 2000_ms;
return durationSince > kThreadTimeout;
}
void idleTimeoutCheck() {

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2022 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 "MemoryPolicy.h"
#include <android-base/properties.h>
#include <optional>
#include <string_view>
#include "Properties.h"
namespace android::uirenderer {
constexpr static MemoryPolicy sDefaultMemoryPolicy;
constexpr static MemoryPolicy sPersistentOrSystemPolicy{
.contextTimeout = 10_s,
.useAlternativeUiHidden = true,
};
constexpr static MemoryPolicy sLowRamPolicy{
.useAlternativeUiHidden = true,
.purgeScratchOnly = false,
};
constexpr static MemoryPolicy sExtremeLowRam{
.initialMaxSurfaceAreaScale = 0.2f,
.surfaceSizeMultiplier = 5 * 4.0f,
.backgroundRetentionPercent = 0.2f,
.contextTimeout = 5_s,
.minimumResourceRetention = 1_s,
.useAlternativeUiHidden = true,
.purgeScratchOnly = false,
.releaseContextOnStoppedOnly = true,
};
const MemoryPolicy& loadMemoryPolicy() {
if (Properties::isSystemOrPersistent) {
return sPersistentOrSystemPolicy;
}
std::string memoryPolicy = base::GetProperty(PROPERTY_MEMORY_POLICY, "");
if (memoryPolicy == "default") {
return sDefaultMemoryPolicy;
}
if (memoryPolicy == "lowram") {
return sLowRamPolicy;
}
if (memoryPolicy == "extremelowram") {
return sExtremeLowRam;
}
if (Properties::isLowRam) {
return sLowRamPolicy;
}
return sDefaultMemoryPolicy;
}
} // namespace android::uirenderer

62
libs/hwui/MemoryPolicy.h Normal file
View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2022 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 "utils/TimeUtils.h"
namespace android::uirenderer {
// Values mirror those from ComponentCallbacks2.java
enum class TrimLevel {
COMPLETE = 80,
MODERATE = 60,
BACKGROUND = 40,
UI_HIDDEN = 20,
RUNNING_CRITICAL = 15,
RUNNING_LOW = 10,
RUNNING_MODERATE = 5,
};
struct MemoryPolicy {
// The initial scale factor applied to the display resolution. The default is 1, but
// lower values may be used to start with a smaller initial cache size. The cache will
// be adjusted if larger frames are actually rendered
float initialMaxSurfaceAreaScale = 1.0f;
// The foreground cache size multiplier. The surface area of the screen will be multiplied
// by this
float surfaceSizeMultiplier = 12.0f * 4.0f;
// How much of the foreground cache size should be preserved when going into the background
float backgroundRetentionPercent = 0.5f;
// How long after the last renderer goes away before the GPU context is released. A value
// of 0 means only drop the context on background TRIM signals
nsecs_t contextTimeout = 0_ms;
// The minimum amount of time to hold onto items in the resource cache
// The actual time used will be the max of this & when frames were actually rendered
nsecs_t minimumResourceRetention = 10_s;
// If false, use only TRIM_UI_HIDDEN to drive background cache limits;
// If true, use all signals (such as all contexts are stopped) to drive the limits
bool useAlternativeUiHidden = false;
// Whether or not to only purge scratch resources when triggering UI Hidden or background
// collection
bool purgeScratchOnly = true;
// EXPERIMENTAL: Whether or not to trigger releasing GPU context when all contexts are stopped
bool releaseContextOnStoppedOnly = false;
};
const MemoryPolicy& loadMemoryPolicy();
} // namespace android::uirenderer

View File

@@ -87,6 +87,10 @@ int Properties::targetCpuTimePercentage = 70;
bool Properties::enableWebViewOverlays = true;
bool Properties::isHighEndGfx = true;
bool Properties::isLowRam = false;
bool Properties::isSystemOrPersistent = false;
StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
DrawingEnabled Properties::drawingEnabled = DrawingEnabled::NotInitialized;

View File

@@ -193,6 +193,8 @@ enum DebugLevel {
*/
#define PROPERTY_DRAWING_ENABLED "debug.hwui.drawing_enabled"
#define PROPERTY_MEMORY_POLICY "debug.hwui.app_memory_policy"
///////////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////////
@@ -292,16 +294,27 @@ public:
static bool enableWebViewOverlays;
static bool isHighEndGfx;
static bool isLowRam;
static bool isSystemOrPersistent;
static StretchEffectBehavior getStretchEffectBehavior() {
return stretchEffectBehavior;
}
static void setIsHighEndGfx(bool isHighEndGfx) {
Properties::isHighEndGfx = isHighEndGfx;
stretchEffectBehavior = isHighEndGfx ?
StretchEffectBehavior::ShaderHWUI :
StretchEffectBehavior::UniformScale;
}
static void setIsLowRam(bool isLowRam) { Properties::isLowRam = isLowRam; }
static void setIsSystemOrPersistent(bool isSystemOrPersistent) {
Properties::isSystemOrPersistent = isSystemOrPersistent;
}
/**
* Used for testing. Typical configuration of stretch behavior is done
* through setIsHighEndGfx

View File

@@ -271,6 +271,16 @@ static void android_view_ThreadedRenderer_setIsHighEndGfx(JNIEnv* env, jobject c
Properties::setIsHighEndGfx(jIsHighEndGfx);
}
static void android_view_ThreadedRenderer_setIsLowRam(JNIEnv* env, jobject clazz,
jboolean isLowRam) {
Properties::setIsLowRam(isLowRam);
}
static void android_view_ThreadedRenderer_setIsSystemOrPersistent(JNIEnv* env, jobject clazz,
jboolean isSystemOrPersistent) {
Properties::setIsSystemOrPersistent(isSystemOrPersistent);
}
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlongArray frameInfo,
jint frameInfoSize) {
@@ -949,6 +959,9 @@ static const JNINativeMethod gMethods[] = {
{"nSetColorMode", "(JI)V", (void*)android_view_ThreadedRenderer_setColorMode},
{"nSetSdrWhitePoint", "(JF)V", (void*)android_view_ThreadedRenderer_setSdrWhitePoint},
{"nSetIsHighEndGfx", "(Z)V", (void*)android_view_ThreadedRenderer_setIsHighEndGfx},
{"nSetIsLowRam", "(Z)V", (void*)android_view_ThreadedRenderer_setIsLowRam},
{"nSetIsSystemOrPersistent", "(Z)V",
(void*)android_view_ThreadedRenderer_setIsSystemOrPersistent},
{"nSyncAndDrawFrame", "(J[JI)I", (void*)android_view_ThreadedRenderer_syncAndDrawFrame},
{"nDestroy", "(JJ)V", (void*)android_view_ThreadedRenderer_destroy},
{"nRegisterAnimatingRenderNode", "(JJ)V",

View File

@@ -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();
}
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;
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) {
SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
if (mGrContext) {
mGrContext->setResourceCacheLimit(mMaxResourceBytes);
}
}
void CacheManager::reset(sk_sp<GrDirectContext> context) {
@@ -69,6 +73,7 @@ void CacheManager::reset(sk_sp<GrDirectContext> context) {
if (context) {
mGrContext = std::move(context);
mGrContext->setResourceCacheLimit(mMaxResourceBytes);
mLastDeferredCleanup = systemTime(CLOCK_MONOTONIC);
}
}
@@ -96,7 +101,7 @@ void CacheManager::configureContext(GrContextOptions* contextOptions, const void
contextOptions->fGpuPathRenderers &= ~GpuPathRenderers::kCoverageCounting;
}
void CacheManager::trimMemory(TrimMemoryMode mode) {
void CacheManager::trimMemory(TrimLevel mode) {
if (!mGrContext) {
return;
}
@@ -104,21 +109,28 @@ void CacheManager::trimMemory(TrimMemoryMode mode) {
// 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::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
}
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::dumpMemoryUsage(String8& log, const RenderState* renderState)
}
void CacheManager::onFrameCompleted() {
cancelDestroyContext();
mFrameCompletions.next() = systemTime(CLOCK_MONOTONIC);
if (ATRACE_ENABLED()) {
static skiapipeline::ATraceMemoryDump tracer;
tracer.startFrame();
@@ -210,11 +242,82 @@ void CacheManager::onFrameCompleted() {
}
}
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();
}
}

View File

@@ -22,7 +22,11 @@
#endif
#include <SkSurface.h>
#include <utils/String8.h>
#include <vector>
#include "MemoryPolicy.h"
#include "utils/RingBuffer.h"
#include "utils/TimeUtils.h"
namespace android {
@@ -35,17 +39,15 @@ class RenderState;
namespace renderthread {
class IRenderPipeline;
class RenderThread;
class CanvasContext;
class CacheManager {
public:
enum class TrimMemoryMode { Complete, UiHidden };
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
void configureContext(GrContextOptions* context, const void* identity, ssize_t size);
#endif
void trimMemory(TrimMemoryMode mode);
void trimMemory(TrimLevel mode);
void trimStaleResources();
void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr);
void getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage);
@@ -53,30 +55,50 @@ public:
size_t getCacheSize() const { return mMaxResourceBytes; }
size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; }
void onFrameCompleted();
void notifyNextFrameSize(int width, int height);
void performDeferredCleanup(nsecs_t cleanupOlderThanMillis);
void onThreadIdle();
void registerCanvasContext(CanvasContext* context);
void unregisterCanvasContext(CanvasContext* context);
void onContextStopped(CanvasContext* context);
private:
friend class RenderThread;
explicit CacheManager();
explicit CacheManager(RenderThread& thread);
void setupCacheLimits();
bool areAllContextsStopped();
void checkUiHidden();
void scheduleDestroyContext();
void cancelDestroyContext();
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
void reset(sk_sp<GrDirectContext> grContext);
#endif
void destroy();
const size_t mMaxSurfaceArea;
RenderThread& mRenderThread;
const MemoryPolicy& mMemoryPolicy;
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
sk_sp<GrDirectContext> mGrContext;
#endif
const size_t mMaxResourceBytes;
const size_t mBackgroundResourceBytes;
size_t mMaxSurfaceArea = 0;
const size_t mMaxGpuFontAtlasBytes;
const size_t mMaxCpuFontCacheBytes;
const size_t mBackgroundCpuFontCacheBytes;
size_t mMaxResourceBytes = 0;
size_t mBackgroundResourceBytes = 0;
size_t mMaxGpuFontAtlasBytes = 0;
size_t mMaxCpuFontCacheBytes = 0;
size_t mBackgroundCpuFontCacheBytes = 0;
std::vector<CanvasContext*> mCanvasContexts;
RingBuffer<uint64_t, 100> mFrameCompletions;
nsecs_t mLastDeferredCleanup = 0;
bool mIsDestructionPending = false;
uint32_t mGenerationId = 0;
};
} /* namespace renderthread */

View File

@@ -42,9 +42,6 @@
#include "utils/GLUtils.h"
#include "utils/TimeUtils.h"
#define TRIM_MEMORY_COMPLETE 80
#define TRIM_MEMORY_UI_HIDDEN 20
#define LOG_FRAMETIME_MMA 0
#if LOG_FRAMETIME_MMA
@@ -122,6 +119,7 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode*
, mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos())
, mContentDrawBounds(0, 0, 0, 0)
, mRenderPipeline(std::move(renderPipeline)) {
mRenderThread.cacheManager().registerCanvasContext(this);
rootRenderNode->makeRoot();
mRenderNodes.emplace_back(rootRenderNode);
mProfiler.setDensity(DeviceInfo::getDensity());
@@ -133,6 +131,7 @@ CanvasContext::~CanvasContext() {
node->clearRoot();
}
mRenderNodes.clear();
mRenderThread.cacheManager().unregisterCanvasContext(this);
}
void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
@@ -154,6 +153,7 @@ void CanvasContext::destroy() {
freePrefetchedLayers();
destroyHardwareResources();
mAnimationContext->destroy();
mRenderThread.cacheManager().onContextStopped(this);
}
static void setBufferCount(ANativeWindow* window) {
@@ -251,6 +251,7 @@ void CanvasContext::setStopped(bool stopped) {
mGenerationID++;
mRenderThread.removeFrameCallback(this);
mRenderPipeline->onStop();
mRenderThread.cacheManager().onContextStopped(this);
} else if (mIsDirty && hasSurface()) {
mRenderThread.postFrameCallback(this);
}
@@ -461,7 +462,6 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy
}
void CanvasContext::stopDrawing() {
cleanupResources();
mRenderThread.removeFrameCallback(this);
mAnimationContext->pauseAnimators();
mGenerationID++;
@@ -648,25 +648,10 @@ nsecs_t CanvasContext::draw() {
}
}
cleanupResources();
mRenderThread.cacheManager().onFrameCompleted();
return mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
}
void CanvasContext::cleanupResources() {
auto& tracker = mJankTracker.frames();
auto size = tracker.size();
auto capacity = tracker.capacity();
if (size == capacity) {
nsecs_t nowNanos = systemTime(SYSTEM_TIME_MONOTONIC);
nsecs_t frameCompleteNanos =
tracker[0].get(FrameInfoIndex::FrameCompleted);
nsecs_t frameDiffNanos = nowNanos - frameCompleteNanos;
nsecs_t cleanupMillis = ns2ms(std::max(frameDiffNanos, 10_s));
mRenderThread.cacheManager().performDeferredCleanup(cleanupMillis);
}
}
void CanvasContext::reportMetricsWithPresentTime() {
{ // acquire lock
std::scoped_lock lock(mFrameMetricsReporterMutex);
@@ -790,6 +775,7 @@ SkISize CanvasContext::getNextFrameSize() const {
SkISize size;
size.fWidth = ANativeWindow_getWidth(anw);
size.fHeight = ANativeWindow_getHeight(anw);
mRenderThread.cacheManager().notifyNextFrameSize(size.fWidth, size.fHeight);
return size;
}
@@ -868,18 +854,6 @@ void CanvasContext::destroyHardwareResources() {
}
}
void CanvasContext::trimMemory(RenderThread& thread, int level) {
ATRACE_CALL();
if (!thread.getGrContext()) return;
ATRACE_CALL();
if (level >= TRIM_MEMORY_COMPLETE) {
thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
thread.destroyRenderingContext();
} else if (level >= TRIM_MEMORY_UI_HIDDEN) {
thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden);
}
}
DeferredLayerUpdater* CanvasContext::createTextureLayer() {
return mRenderPipeline->createTextureLayer();
}

View File

@@ -127,6 +127,7 @@ public:
void setSurfaceControl(ASurfaceControl* surfaceControl);
bool pauseSurface();
void setStopped(bool stopped);
bool isStopped() { return mStopped || !hasSurface(); }
bool hasSurface() const { return mNativeSurface.get(); }
void allocateBuffers();
@@ -148,7 +149,6 @@ public:
void markLayerInUse(RenderNode* node);
void destroyHardwareResources();
static void trimMemory(RenderThread& thread, int level);
DeferredLayerUpdater* createTextureLayer();
@@ -330,8 +330,6 @@ private:
std::function<bool(int64_t, int64_t, int64_t)> mASurfaceTransactionCallback;
std::function<void()> mPrepareSurfaceControlForWebviewCallback;
void cleanupResources();
};
} /* namespace renderthread */

View File

@@ -196,7 +196,8 @@ void RenderProxy::trimMemory(int level) {
// Avoid creating a RenderThread to do a trimMemory.
if (RenderThread::hasInstance()) {
RenderThread& thread = RenderThread::getInstance();
thread.queue().post([&thread, level]() { CanvasContext::trimMemory(thread, level); });
const auto trimLevel = static_cast<TrimLevel>(level);
thread.queue().post([&thread, trimLevel]() { thread.trimMemory(trimLevel); });
}
}
@@ -205,7 +206,7 @@ void RenderProxy::purgeCaches() {
RenderThread& thread = RenderThread::getInstance();
thread.queue().post([&thread]() {
if (thread.getGrContext()) {
thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
thread.cacheManager().trimMemory(TrimLevel::COMPLETE);
}
});
}

View File

@@ -251,7 +251,7 @@ void RenderThread::initThreadLocals() {
mEglManager = new EglManager();
mRenderState = new RenderState(*this);
mVkManager = VulkanManager::getInstance();
mCacheManager = new CacheManager();
mCacheManager = new CacheManager(*this);
}
void RenderThread::setupFrameInterval() {
@@ -453,6 +453,8 @@ bool RenderThread::threadLoop() {
// next vsync (oops), so none of the callbacks are run.
requestVsync();
}
mCacheManager->onThreadIdle();
}
return false;
@@ -502,6 +504,11 @@ void RenderThread::preload() {
HardwareBitmapUploader::initialize();
}
void RenderThread::trimMemory(TrimLevel level) {
ATRACE_CALL();
cacheManager().trimMemory(level);
}
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */

View File

@@ -17,11 +17,11 @@
#ifndef RENDERTHREAD_H_
#define RENDERTHREAD_H_
#include <surface_control_private.h>
#include <GrDirectContext.h>
#include <SkBitmap.h>
#include <cutils/compiler.h>
#include <private/android/choreographer.h>
#include <surface_control_private.h>
#include <thread/ThreadBase.h>
#include <utils/Looper.h>
#include <utils/Thread.h>
@@ -31,6 +31,7 @@
#include <set>
#include "CacheManager.h"
#include "MemoryPolicy.h"
#include "ProfileDataContainer.h"
#include "RenderTask.h"
#include "TimeLord.h"
@@ -172,6 +173,8 @@ public:
return mASurfaceControlFunctions;
}
void trimMemory(TrimLevel level);
/**
* isCurrent provides a way to query, if the caller is running on
* the render thread.

View File

@@ -58,7 +58,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, trimMemory) {
ASSERT_TRUE(SkImage_pinAsTexture(image.get(), grContext));
// attempt to trim all memory while we still hold strong refs
renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE);
ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
// free the surfaces
@@ -75,11 +75,11 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, trimMemory) {
ASSERT_TRUE(renderThread.cacheManager().getBackgroundCacheSize() < purgeableBytes);
// UI hidden and make sure only some got purged (unique should remain)
renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden);
renderThread.cacheManager().trimMemory(TrimLevel::UI_HIDDEN);
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);
renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE);
ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
}