From 44db040f6c07b63642bc7ae72040d09851d70df7 Mon Sep 17 00:00:00 2001 From: Jernej Virag Date: Tue, 9 May 2023 20:24:48 +0200 Subject: [PATCH 1/2] Allow triming of font caches through WindowManager This allows triming of Skia font caches when an app created a lot of transient font allocations and it knows that it won't need it anymore. This is primarily meant for persistent processes like SystemUI to avoid font caches taking up memory after they're not needed anymore. Bug: 275486055 Test: Tested as part of a follow-up commit, ran LockscreenWithSwipeMicrobenchmark which showed a noticable reduction of RSS+anon memory use after unlock. Change-Id: I6d80003d8baab35cb2ca858d4e4d4696b32f3adf --- .../android/view/WindowManagerGlobal.java | 6 +++ .../android/graphics/HardwareRenderer.java | 42 +++++++++++++++++++ libs/hwui/MemoryPolicy.h | 6 +++ .../jni/android_graphics_HardwareRenderer.cpp | 5 +++ libs/hwui/renderthread/CacheManager.cpp | 19 +++++++++ libs/hwui/renderthread/CacheManager.h | 1 + libs/hwui/renderthread/RenderProxy.cpp | 9 ++++ libs/hwui/renderthread/RenderProxy.h | 1 + libs/hwui/renderthread/RenderThread.cpp | 5 +++ libs/hwui/renderthread/RenderThread.h | 1 + 10 files changed, 95 insertions(+) diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 02cd0372ea889..99a4f6b41ef32 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -23,6 +23,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; +import android.graphics.HardwareRenderer; import android.os.Build; import android.os.IBinder; import android.os.RemoteException; @@ -550,6 +551,11 @@ public final class WindowManagerGlobal { ThreadedRenderer.trimMemory(level); } + /** @hide */ + public void trimCaches(@HardwareRenderer.CacheTrimLevel int level) { + ThreadedRenderer.trimCaches(level); + } + public void dumpGfxInfo(FileDescriptor fd, String[] args) { FileOutputStream fout = new FileOutputStream(fd); PrintWriter pw = new FastPrintWriter(fout); diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index 9ed3d9c3c94b6..9cde1878d9d89 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -144,6 +144,32 @@ public class HardwareRenderer { public @interface DumpFlags { } + + /** + * Trims all Skia caches. + * @hide + */ + public static final int CACHE_TRIM_ALL = 0; + /** + * Trims Skia font caches. + * @hide + */ + public static final int CACHE_TRIM_FONT = 1; + /** + * Trims Skia resource caches. + * @hide + */ + public static final int CACHE_TRIM_RESOURCES = 2; + + /** @hide */ + @IntDef(prefix = {"CACHE_TRIM_"}, value = { + CACHE_TRIM_ALL, + CACHE_TRIM_FONT, + CACHE_TRIM_RESOURCES + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CacheTrimLevel {} + /** * Name of the file that holds the shaders cache. */ @@ -1131,6 +1157,20 @@ public class HardwareRenderer { nTrimMemory(level); } + /** + * Invoke this when all font caches should be flushed. This can cause jank on next render + * commands so use it only after expensive font allocation operations which would + * allocate large amount of temporary memory. + * + * @param level Hint about which caches to trim. See {@link #CACHE_TRIM_ALL}, + * {@link #CACHE_TRIM_FONT}, {@link #CACHE_TRIM_RESOURCES} + * + * @hide + */ + public static void trimCaches(@CacheTrimLevel int level) { + nTrimCaches(level); + } + /** @hide */ public static void overrideProperty(@NonNull String name, @NonNull String value) { if (name == null || value == null) { @@ -1497,6 +1537,8 @@ public class HardwareRenderer { private static native void nTrimMemory(int level); + private static native void nTrimCaches(int level); + private static native void nOverrideProperty(String name, String value); private static native void nFence(long nativeProxy); diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h index 139cdde5c0d21..347daf34f52a5 100644 --- a/libs/hwui/MemoryPolicy.h +++ b/libs/hwui/MemoryPolicy.h @@ -31,6 +31,12 @@ enum class TrimLevel { RUNNING_MODERATE = 5, }; +enum class CacheTrimLevel { + ALL_CACHES = 0, + FONT_CACHE = 1, + RESOURCE_CACHE = 2, +}; + 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 diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index 6a7411f5d8597..d04de37f69618 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -362,6 +362,10 @@ static void android_view_ThreadedRenderer_trimMemory(JNIEnv* env, jobject clazz, RenderProxy::trimMemory(level); } +static void android_view_ThreadedRenderer_trimCaches(JNIEnv* env, jobject clazz, jint level) { + RenderProxy::trimCaches(level); +} + static void android_view_ThreadedRenderer_overrideProperty(JNIEnv* env, jobject clazz, jstring name, jstring value) { const char* nameCharArray = env->GetStringUTFChars(name, NULL); @@ -1018,6 +1022,7 @@ static const JNINativeMethod gMethods[] = { (void*)android_view_ThreadedRenderer_notifyCallbackPending}, {"nNotifyExpensiveFrame", "(J)V", (void*)android_view_ThreadedRenderer_notifyExpensiveFrame}, + {"nTrimCaches", "(I)V", (void*)android_view_ThreadedRenderer_trimCaches}, }; static JavaVM* mJvm = nullptr; diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index c00a2707e0a25..babce88b8e1ef 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -139,6 +139,25 @@ void CacheManager::trimMemory(TrimLevel mode) { } } +void CacheManager::trimCaches(CacheTrimLevel mode) { + switch (mode) { + case CacheTrimLevel::FONT_CACHE: + SkGraphics::PurgeFontCache(); + break; + case CacheTrimLevel::RESOURCE_CACHE: + SkGraphics::PurgeResourceCache(); + break; + case CacheTrimLevel::ALL_CACHES: + SkGraphics::PurgeAllCaches(); + if (mGrContext) { + mGrContext->purgeUnlockedResources(false); + } + break; + default: + break; + } +} + void CacheManager::trimStaleResources() { if (!mGrContext) { return; diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h index d21ac9badc431..5e43ac209696c 100644 --- a/libs/hwui/renderthread/CacheManager.h +++ b/libs/hwui/renderthread/CacheManager.h @@ -48,6 +48,7 @@ public: void configureContext(GrContextOptions* context, const void* identity, ssize_t size); #endif void trimMemory(TrimLevel mode); + void trimCaches(CacheTrimLevel mode); void trimStaleResources(); void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr); void getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage); diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 31b4b203c6707..224c878bf43dd 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -231,6 +231,15 @@ void RenderProxy::trimMemory(int level) { } } +void RenderProxy::trimCaches(int level) { + // Avoid creating a RenderThread to do a trimMemory. + if (RenderThread::hasInstance()) { + RenderThread& thread = RenderThread::getInstance(); + const auto trimLevel = static_cast(level); + thread.queue().post([&thread, trimLevel]() { thread.trimCaches(trimLevel); }); + } +} + void RenderProxy::purgeCaches() { if (RenderThread::hasInstance()) { RenderThread& thread = RenderThread::getInstance(); diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 82072a6e2499d..47c1b0cd28e54 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -105,6 +105,7 @@ public: void destroyHardwareResources(); static void trimMemory(int level); + static void trimCaches(int level); static void purgeCaches(); static void overrideProperty(const char* name, const char* value); diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 9ba67a2110c1d..eb28c080c0560 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -521,6 +521,11 @@ void RenderThread::trimMemory(TrimLevel level) { cacheManager().trimMemory(level); } +void RenderThread::trimCaches(CacheTrimLevel level) { + ATRACE_CALL(); + cacheManager().trimCaches(level); +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h index c77cd4134d1ed..79e57de9d66f1 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -174,6 +174,7 @@ public: } void trimMemory(TrimLevel level); + void trimCaches(CacheTrimLevel level); /** * isCurrent provides a way to query, if the caller is running on From f1a8c8d4002da1929fa98b24143b6501fc6cd49d Mon Sep 17 00:00:00 2001 From: Jernej Virag Date: Thu, 11 May 2023 15:59:46 +0200 Subject: [PATCH 2/2] Trim font caches when keyguard goes away Right now we send a trim UI_HIDDEN command to trim the resources used by lockscreen after unlock, but that does not clean up any font caches generated by e.g. lockscreen clock animation. This can result in significant memory bloat for user apps once the device is unlock. This trims font caches after unlock as well to increase availability of resources for apps. Bug:275486055 Bug:278225145 Bug:283203305 Test: newly added unit tests for ResourceTrimmer ran LockscreenWithSwipeMicrobenchmark and confirmed lower memory use Change-Id: I84637049a106a94bf9f06c5261c2941b55db4ba0 --- .../src/com/android/systemui/flags/Flags.kt | 4 +++ .../systemui/keyguard/ResourceTrimmer.kt | 34 ++++++++++++++++-- .../phone/StatusBarKeyguardViewManager.java | 4 --- .../systemui/utils/GlobalWindowManager.kt | 6 ++++ .../systemui/keyguard/ResourceTrimmerTest.kt | 36 +++++++++++++++++-- 5 files changed, 76 insertions(+), 8 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index f71a46caef344..be0fcd3cd7234 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -676,6 +676,10 @@ object Flags { val TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK = unreleasedFlag(2401, "trim_resources_with_background_trim_on_lock") + // TODO:(b/283203305): Tracking bug + @JvmField + val TRIM_FONT_CACHES_AT_UNLOCK = releasedFlag(2402, "trim_font_caches_on_unlock") + // 2700 - unfold transitions // TODO(b/265764985): Tracking Bug @Keep diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt index 8386a05b6647f..d8affa4d6c21a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard import android.annotation.WorkerThread import android.content.ComponentCallbacks2 +import android.graphics.HardwareRenderer import android.os.Trace import android.util.Log import com.android.systemui.CoreStartable @@ -27,12 +28,13 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.utils.GlobalWindowManager import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map @@ -50,6 +52,7 @@ class ResourceTrimmer @Inject constructor( private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val globalWindowManager: GlobalWindowManager, @Application private val applicationScope: CoroutineScope, @Background private val bgDispatcher: CoroutineDispatcher, @@ -58,7 +61,10 @@ constructor( override fun start() { Log.d(LOG_TAG, "Resource trimmer registered.") - if (!featureFlags.isEnabled(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)) { + if ( + !(featureFlags.isEnabled(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) || + featureFlags.isEnabled(Flags.TRIM_FONT_CACHES_AT_UNLOCK)) + ) { return } @@ -78,6 +84,30 @@ constructor( .distinctUntilChanged() .collect { onWakefulnessUpdated(it.first, it.second, it.third) } } + + applicationScope.launch(bgDispatcher) { + // We drop 1 to avoid triggering on initial collect(). + keyguardTransitionInteractor.anyStateToGoneTransition.collect { transition -> + if (transition.transitionState == TransitionState.FINISHED) { + onKeyguardGone() + } + } + } + } + + @WorkerThread + private fun onKeyguardGone() { + if (!featureFlags.isEnabled(Flags.TRIM_FONT_CACHES_AT_UNLOCK)) { + return + } + + if (DEBUG) { + Log.d(LOG_TAG, "Trimming font caches since keyguard went away.") + } + // We want to clear temporary caches we've created while rendering and animating + // lockscreen elements, especially clocks. + globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) + globalWindowManager.trimCaches(HardwareRenderer.CACHE_TRIM_FONT) } @WorkerThread diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index df68e7eb037b8..0414a14205d7b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -23,7 +23,6 @@ import static com.android.systemui.plugins.ActivityStarter.OnDismissAction; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK; import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING; -import android.content.ComponentCallbacks2; import android.content.Context; import android.content.res.ColorStateList; import android.hardware.biometrics.BiometricSourceType; @@ -36,7 +35,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewRootImpl; -import android.view.WindowManagerGlobal; import android.window.BackEvent; import android.window.OnBackAnimationCallback; import android.window.OnBackInvokedDispatcher; @@ -985,8 +983,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mShadeViewController.resetViewGroupFade(); mCentralSurfaces.finishKeyguardFadingAway(); mBiometricUnlockController.finishKeyguardFadingAway(); - WindowManagerGlobal.getInstance().trimMemory( - ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); } private void wakeAndUnlockDejank() { diff --git a/packages/SystemUI/src/com/android/systemui/utils/GlobalWindowManager.kt b/packages/SystemUI/src/com/android/systemui/utils/GlobalWindowManager.kt index 038fddc1f7a9c..4111850b53ed8 100644 --- a/packages/SystemUI/src/com/android/systemui/utils/GlobalWindowManager.kt +++ b/packages/SystemUI/src/com/android/systemui/utils/GlobalWindowManager.kt @@ -1,5 +1,6 @@ package com.android.systemui.utils +import android.graphics.HardwareRenderer.CacheTrimLevel import android.view.WindowManagerGlobal import javax.inject.Inject @@ -13,4 +14,9 @@ class GlobalWindowManager @Inject constructor() { fun trimMemory(level: Int) { WindowManagerGlobal.getInstance().trimMemory(level) } + + /** Sends a trim caches command to [WindowManagerGlobal]. */ + fun trimCaches(@CacheTrimLevel level: Int) { + WindowManagerGlobal.getInstance().trimCaches(level) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt index 367d206814cbc..548d26f2aaed3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt @@ -1,6 +1,7 @@ package com.android.systemui.keyguard import android.content.ComponentCallbacks2 +import android.graphics.HardwareRenderer import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -9,7 +10,11 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.WakeSleepReason import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState @@ -25,6 +30,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations @@ -37,6 +43,7 @@ class ResourceTrimmerTest : SysuiTestCase() { private val testScope = TestScope(testDispatcher) private val keyguardRepository = FakeKeyguardRepository() private val featureFlags = FakeFeatureFlags() + private val keyguardTransitionRepository = FakeKeyguardTransitionRepository() @Mock private lateinit var globalWindowManager: GlobalWindowManager private lateinit var resourceTrimmer: ResourceTrimmer @@ -45,13 +52,15 @@ class ResourceTrimmerTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) featureFlags.set(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK, true) + featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, true) featureFlags.set(Flags.FACE_AUTH_REFACTOR, false) keyguardRepository.setWakefulnessModel( WakefulnessModel(WakefulnessState.AWAKE, WakeSleepReason.OTHER, WakeSleepReason.OTHER) ) keyguardRepository.setDozeAmount(0f) + keyguardRepository.setKeyguardGoingAway(false) - val interactor = + val keyguardInteractor = KeyguardInteractor( keyguardRepository, FakeCommandQueue(), @@ -60,7 +69,8 @@ class ResourceTrimmerTest : SysuiTestCase() { ) resourceTrimmer = ResourceTrimmer( - interactor, + keyguardInteractor, + KeyguardTransitionInteractor(keyguardTransitionRepository), globalWindowManager, testScope.backgroundScope, testDispatcher, @@ -191,4 +201,26 @@ class ResourceTrimmerTest : SysuiTestCase() { verifyZeroInteractions(globalWindowManager) } } + + @Test + fun keyguardTransitionsToGone_trimsFontCache() = + testScope.runTest { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE) + ) + verify(globalWindowManager, times(1)) + .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) + verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_FONT) + verifyNoMoreInteractions(globalWindowManager) + } + + @Test + fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache() = + testScope.runTest { + featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE) + ) + verifyNoMoreInteractions(globalWindowManager) + } }