diff options
author | 2022-09-23 17:49:23 -0400 | |
---|---|---|
committer | 2022-09-28 10:47:56 -0400 | |
commit | 5f66fb8162935f342752ed069d8e967819b7f28b (patch) | |
tree | da3817f59e65259d187f5db2cd039ae0aa03dd8c | |
parent | d112853729e4047a73ee0fc6b96f3a1e90be33d2 (diff) |
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
21 files changed, 437 insertions, 208 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index cf5f10ba109e..7a9f3c1c4c76 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -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. diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index c97eb73c120b..d77e8825e462 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -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(); } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index b6f775d3e536..28fa77c0a4de 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1766,9 +1766,6 @@ public final class ViewRootImpl implements ViewParent, mAppVisibilityChanged = true; scheduleTraversals(); } - if (!mAppVisible) { - WindowManagerGlobal.trimForeground(); - } AnimationHandler.requestAnimatorsEnabled(mAppVisible, this); } } diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index d37756551db3..4a9dc5b14a71 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -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) { diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index 0e67f1f4842a..c6731d112b93 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -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; @@ -1142,6 +1140,16 @@ public class HardwareRenderer { } /** + * 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. * * This value is global to the process and affects all uses of HardwareRenderer, @@ -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); - - // 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 Display[] allDisplays = dm.getDisplays(); + if (allDisplays.length == 0) { + Log.d(LOG_TAG, "Failed to query displays"); + return; + } + + 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); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index b11e542472da..29f37737d433 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -521,6 +521,7 @@ cc_defaults { "Interpolator.cpp", "LightingInfo.cpp", "Matrix.cpp", + "MemoryPolicy.cpp", "PathParser.cpp", "Properties.cpp", "PropertyValuesAnimatorSet.cpp", diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp index 07594715a84c..f06fa2418003 100644 --- a/libs/hwui/DeviceInfo.cpp +++ b/libs/hwui/DeviceInfo.cpp @@ -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."); } } diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp index 7291cab364e2..b7e99994355c 100644 --- a/libs/hwui/HardwareBitmapUploader.cpp +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -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() { diff --git a/libs/hwui/MemoryPolicy.cpp b/libs/hwui/MemoryPolicy.cpp new file mode 100644 index 000000000000..ca1312e75f4c --- /dev/null +++ b/libs/hwui/MemoryPolicy.cpp @@ -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
\ No newline at end of file diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h new file mode 100644 index 000000000000..e86b338608d2 --- /dev/null +++ b/libs/hwui/MemoryPolicy.h @@ -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
\ No newline at end of file diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 5a67eb9935dd..277955e88dfe 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -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; diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 2f8c67903a8b..96a517629eaa 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -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 diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index 4f281fcde61d..704fba9241b6 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -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", diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index ded2b06fb3cf..1d24e718db1a 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() - : 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) { +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; + 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(); } } diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h index af82672c6f23..d21ac9badc43 100644 --- a/libs/hwui/renderthread/CacheManager.h +++ b/libs/hwui/renderthread/CacheManager.h @@ -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 onThreadIdle(); - void performDeferredCleanup(nsecs_t cleanupOlderThanMillis); + 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; + + 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; - const size_t mMaxGpuFontAtlasBytes; - const size_t mMaxCpuFontCacheBytes; - const size_t mBackgroundCpuFontCacheBytes; + nsecs_t mLastDeferredCleanup = 0; + bool mIsDestructionPending = false; + uint32_t mGenerationId = 0; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 75d3ff7753cb..6a0c5a8e499e 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -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(); } diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 951ee216ce35..748ab96d7e06 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -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 */ diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 40a0bac3762d..332471545f82 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -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); } }); } diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 3ff4081726f3..7a7f1abdd268 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -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 */ diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h index c1f6790b25b2..0a89e5e944f8 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -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. diff --git a/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp index edd3e4e4f4d4..df06ead3aa0c 100644 --- a/libs/hwui/tests/unit/CacheManagerTests.cpp +++ b/libs/hwui/tests/unit/CacheManagerTests.cpp @@ -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()); } |