Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c823bd02 authored by Winson Chung's avatar Winson Chung
Browse files

Add mechanism to help manage multiple system perf hints from SysUI

- Users can request high perf sessions for transitions and interactions
  with specific hints for SurfaceFlinger or ADPF.
- Add injectable hinter on the Shell side for use within shell features

Bug: 300019131
Bug: 297385713
Test: SystemPerformanceHinterTests

Change-Id: I869689fc88106be9c55d8dd3243173f00749fc07
parent cea6bf39
Loading
Loading
Loading
Loading
+327 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.
 */

package android.window;

import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN;
import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_SELF;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.PerformanceHintManager;
import android.os.Trace;
import android.util.Log;
import android.view.SurfaceControl;

import com.android.internal.annotations.VisibleForTesting;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Random;
import java.util.function.Supplier;

/**
 * A helper class to manage performance related hints for a process.  This helper is used for both
 * long-lived and transient hints.
 *
 * @hide
 */
public class SystemPerformanceHinter {
    private static final String TAG = "SystemPerformanceHinter";

    // Change app and SF wakeup times to allow sf more time to composite a frame
    public static final int HINT_SF_EARLY_WAKEUP = 1 << 0;
    // Force max refresh rate
    public static final int HINT_SF_FRAME_RATE = 1 << 1;
    // Boost CPU & GPU clocks
    public static final int HINT_ADPF = 1 << 2;
    // Convenience constant for SF only flags
    public static final int HINT_SF = HINT_SF_EARLY_WAKEUP | HINT_SF_FRAME_RATE;
    // Convenience constant for all the flags
    public static final int HINT_ALL = HINT_SF_EARLY_WAKEUP | HINT_SF_FRAME_RATE | HINT_ADPF;

    // Hints that are applied per-display and require a display root surface
    private static final int HINT_PER_DISPLAY = HINT_SF_FRAME_RATE;
    // Hints that are global (not per-display)
    private static final int HINT_GLOBAL = HINT_SF_EARLY_WAKEUP | HINT_ADPF;

    @IntDef(prefix = {"HINT_"}, value = {
            HINT_SF_EARLY_WAKEUP,
            HINT_SF_FRAME_RATE,
            HINT_ADPF,
    })
    private @interface HintFlags {}

    /**
     * A provider for the root to apply SurfaceControl hints which will be inherited by all children
     * of that root.
     * @hide
     */
    public interface DisplayRootProvider {
        /**
         * @return the SurfaceControl to apply hints for the given displayId.
         */
        @Nullable SurfaceControl getRootForDisplay(int displayId);
    }

    /**
     * A session where high performance is needed.
     * @hide
     */
    public class HighPerfSession implements AutoCloseable {
        private final @HintFlags int hintFlags;
        private final String reason;
        private final int displayId;
        private final int traceCookie;

        protected HighPerfSession(@HintFlags int hintFlags, int displayId, @NonNull String reason) {
            this.hintFlags = hintFlags;
            this.reason = reason;
            this.displayId = displayId;
            this.traceCookie = new Random().nextInt();
            if (hintFlags != 0) {
                startSession(this);
            }
        }

        /**
         * Closes this session.
         */
        public void close() {
            if (hintFlags != 0) {
                endSession(this);
            }
        }

        public void finalize() {
            close();
        }
    }

    /**
     * A no-op implementation of a session.
     */
    private class NoOpHighPerfSession extends HighPerfSession {
        public NoOpHighPerfSession() {
            super(0 /* hintFlags */, -1 /* displayId */, "");
        }

        public void close() {
            // Do nothing
        }
    }

    // The active sessions
    private final ArrayList<HighPerfSession> mActiveSessions = new ArrayList<>();
    private final SurfaceControl.Transaction mTransaction;
    private final PerformanceHintManager mPerfHintManager;
    private @Nullable PerformanceHintManager.Session mAdpfSession;
    private @Nullable DisplayRootProvider mDisplayRootProvider;


    /**
     * Constructor for the hinter.
     * @hide
     */
    public SystemPerformanceHinter(@NonNull Context context,
            @Nullable DisplayRootProvider displayRootProvider) {
        this(context, displayRootProvider, null /* transactionSupplier */);
    }

    /**
     * Constructor for the hinter.
     * @hide
     */
    @VisibleForTesting
    public SystemPerformanceHinter(@NonNull Context context,
            @Nullable DisplayRootProvider displayRootProvider,
            @Nullable Supplier<SurfaceControl.Transaction> transactionSupplier) {
        mDisplayRootProvider = displayRootProvider;
        mPerfHintManager = context.getSystemService(PerformanceHintManager.class);
        mTransaction = transactionSupplier != null
                ? transactionSupplier.get()
                : new SurfaceControl.Transaction();
    }

    /**
     * Sets the current ADPF session, required if you are using HINT_ADPF.  It is the responsibility
     * of the caller to manage up the ADPF session.
     * @hide
     */
    public void setAdpfSession(PerformanceHintManager.Session adpfSession) {
        mAdpfSession = adpfSession;
    }

    /**
     * Starts a session that requires high performance.
     * @hide
     */
    public HighPerfSession startSession(@HintFlags int hintFlags, int displayId,
            @NonNull String reason) {
        if (mDisplayRootProvider == null && (hintFlags & HINT_SF_FRAME_RATE) != 0) {
            throw new IllegalArgumentException(
                    "Using SF frame rate hints requires a valid display root provider");
        }
        if (mAdpfSession == null && (hintFlags & HINT_ADPF) != 0) {
            throw new IllegalArgumentException("Using ADPF hints requires an ADPF session");
        }
        if ((hintFlags & HINT_PER_DISPLAY) != 0) {
            if (mDisplayRootProvider.getRootForDisplay(displayId) == null) {
                // Just log an error and return early if there is no root as there could be races
                // between when a display root is removed and when a hint session is requested
                Log.v(TAG, "No display root for displayId=" + displayId);
                Trace.instant(TRACE_TAG_WINDOW_MANAGER, "PerfHint-NoDisplayRoot: " + displayId);
                return new NoOpHighPerfSession();
            }
        }
        return new HighPerfSession(hintFlags, displayId, reason);
    }

    /**
     * Starts a session that requires high performance.
     */
    private void startSession(HighPerfSession session) {
        int oldGlobalFlags = calculateActiveHintFlags(HINT_GLOBAL);
        int oldPerDisplayFlags = calculateActiveHintFlagsForDisplay(HINT_PER_DISPLAY,
                session.displayId);
        mActiveSessions.add(session);
        int newGlobalFlags = calculateActiveHintFlags(HINT_GLOBAL);
        int newPerDisplayFlags = calculateActiveHintFlagsForDisplay(HINT_PER_DISPLAY,
                session.displayId);

        boolean transactionChanged = false;
        // Per-display flags
        if (nowEnabled(oldPerDisplayFlags, newPerDisplayFlags, HINT_SF_FRAME_RATE)) {
            mTransaction.setFrameRateSelectionStrategy(
                    mDisplayRootProvider.getRootForDisplay(session.displayId),
                    FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
            transactionChanged = true;
            Trace.beginAsyncSection("PerfHint-framerate-" + session.reason, session.traceCookie);
        }

        // Global flags
        if (nowEnabled(oldGlobalFlags, newGlobalFlags, HINT_SF_EARLY_WAKEUP)) {
            mTransaction.setEarlyWakeupStart();
            transactionChanged = true;
            Trace.beginAsyncSection("PerfHint-early_wakeup-" + session.reason, session.traceCookie);
        }
        if (nowEnabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
            mAdpfSession.sendHint(PerformanceHintManager.Session.CPU_LOAD_UP);
            Trace.beginAsyncSection("PerfHint-adpf-" + session.reason, session.traceCookie);
        }
        if (transactionChanged) {
            mTransaction.apply();
        }
    }

    /**
     * Ends a session that requires high performance.
     */
    private void endSession(HighPerfSession session) {
        int oldGlobalFlags = calculateActiveHintFlags(HINT_GLOBAL);
        int oldPerDisplayFlags = calculateActiveHintFlagsForDisplay(HINT_PER_DISPLAY,
                session.displayId);
        mActiveSessions.remove(session);
        int newGlobalFlags = calculateActiveHintFlags(HINT_GLOBAL);
        int newPerDisplayFlags = calculateActiveHintFlagsForDisplay(HINT_PER_DISPLAY,
                session.displayId);

        boolean transactionChanged = false;
        // Per-display flags
        if (nowDisabled(oldPerDisplayFlags, newPerDisplayFlags, HINT_SF_FRAME_RATE)) {
            mTransaction.setFrameRateSelectionStrategy(
                    mDisplayRootProvider.getRootForDisplay(session.displayId),
                    FRAME_RATE_SELECTION_STRATEGY_SELF);
            transactionChanged = true;
            Trace.endAsyncSection("PerfHint-framerate-" + session.reason, session.traceCookie);
        }

        // Global flags
        if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_SF_EARLY_WAKEUP)) {
            mTransaction.setEarlyWakeupEnd();
            transactionChanged = true;
            Trace.endAsyncSection("PerfHint-early_wakeup" + session.reason, session.traceCookie);
        }
        if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
            mAdpfSession.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
            Trace.endAsyncSection("PerfHint-adpf-" + session.reason, session.traceCookie);
        }
        if (transactionChanged) {
            mTransaction.apply();
        }
    }

    /**
     * Checks if checkFlags was previously not set and is now set.
     */
    private boolean nowEnabled(@HintFlags int oldFlags, @HintFlags int newFlags,
                               @HintFlags int checkFlags) {
        return (oldFlags & checkFlags) == 0 && (newFlags & checkFlags) != 0;
    }

    /**
     * Checks if checkFlags was previously set and is now not set.
     */
    private boolean nowDisabled(@HintFlags int oldFlags, @HintFlags int newFlags,
                                @HintFlags int checkFlags) {
        return (oldFlags & checkFlags) != 0 && (newFlags & checkFlags) == 0;
    }

    /**
     * @return the combined hint flags for all active sessions, filtered by {@param filterFlags}.
     */
    private @HintFlags int calculateActiveHintFlags(@HintFlags int filterFlags) {
        int flags = 0;
        for (int i = 0; i < mActiveSessions.size(); i++) {
            flags |= mActiveSessions.get(i).hintFlags & filterFlags;
        }
        return flags;
    }

    /**
     * @return the combined hint flags for all active sessions for a given display, filtered by
     *         {@param filterFlags}.
     */
    private @HintFlags int calculateActiveHintFlagsForDisplay(@HintFlags int filterFlags,
            int displayId) {
        int flags = 0;
        for (int i = 0; i < mActiveSessions.size(); i++) {
            final HighPerfSession session = mActiveSessions.get(i);
            if (session.displayId == displayId) {
                flags |= mActiveSessions.get(i).hintFlags & filterFlags;
            }
        }
        return flags;
    }

    /**
     * Dumps the existing sessions.
     */
    public void dump(PrintWriter pw, String prefix) {
        final String innerPrefix = prefix + "  ";
        pw.println(prefix + TAG + ":");
        pw.println(innerPrefix + "Active sessions (" + mActiveSessions.size() + "):");
        for (int i = 0; i < mActiveSessions.size(); i++) {
            final HighPerfSession s = mActiveSessions.get(i);
            pw.println(innerPrefix + "  reason=" + s.reason
                    + " flags=" + s.hintFlags
                    + " display=" + s.displayId);
        }
    }
}
+389 −0

File added.

Preview size limit exceeded, changes collapsed.

+14 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.view.SurfaceControl;
import android.window.DisplayAreaAppearedInfo;
import android.window.DisplayAreaInfo;
import android.window.DisplayAreaOrganizer;
import android.window.SystemPerformanceHinter;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -58,6 +59,14 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
    /** {@link DisplayAreaContext} list, which is mapped by display IDs. */
    private final SparseArray<DisplayAreaContext> mDisplayAreaContexts = new SparseArray<>();

    private final SystemPerformanceHinter.DisplayRootProvider mPerfRootProvider =
            new SystemPerformanceHinter.DisplayRootProvider() {
                @Override
                public SurfaceControl getRootForDisplay(int displayId) {
                    return mLeashes.get(displayId);
                }
            };

    private final Context mContext;

    public RootTaskDisplayAreaOrganizer(Executor executor, Context context) {
@@ -229,6 +238,11 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
        return mDisplayAreaContexts.get(displayId);
    }

    @NonNull
    public SystemPerformanceHinter.DisplayRootProvider getPerformanceRootProvider() {
        return mPerfRootProvider;
    }

    public void dump(@NonNull PrintWriter pw, String prefix) {
        final String innerPrefix = prefix + "  ";
        final String childPrefix = innerPrefix + "  ";
+13 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.os.Handler;
import android.os.SystemProperties;
import android.view.IWindowManager;
import android.view.accessibility.AccessibilityManager;
import android.window.SystemPerformanceHinter;

import com.android.internal.logging.UiEventLogger;
import com.android.launcher3.icons.IconProvider;
@@ -85,6 +86,7 @@ import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.performance.PerfHintController;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
@@ -296,6 +298,17 @@ public abstract class WMShellBaseModule {
        return new LaunchAdjacentController(syncQueue);
    }

    @WMSingleton
    @Provides
    static SystemPerformanceHinter provideSystemPerformanceHinter(Context context,
            ShellInit shellInit,
            ShellCommandHandler shellCommandHandler,
            RootTaskDisplayAreaOrganizer rootTdaOrganizer) {
        final PerfHintController perfHintController =
                new PerfHintController(context, shellInit, shellCommandHandler, rootTdaOrganizer);
        return perfHintController.getHinter();
    }

    //
    // Back animation
    //
+56 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.
 */
package com.android.wm.shell.performance

import android.content.Context
import android.os.PerformanceHintManager
import android.os.Process
import android.window.SystemPerformanceHinter
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellInit
import java.io.PrintWriter
import java.util.concurrent.TimeUnit

/**
 * Manages the performance hints to the system.
 */
class PerfHintController(private val mContext: Context,
                         shellInit: ShellInit,
                         private val mShellCommandHandler: ShellCommandHandler,
                         rootTdaOrganizer: RootTaskDisplayAreaOrganizer) {

    // The system perf hinter
    val hinter: SystemPerformanceHinter

    init {
        hinter = SystemPerformanceHinter(mContext,
                rootTdaOrganizer.performanceRootProvider)
        shellInit.addInitCallback(this::onInit, this)
    }

    private fun onInit() {
        mShellCommandHandler.addDumpCallback(this::dump, this)
        val perfHintMgr = mContext.getSystemService(PerformanceHintManager::class.java)
        val adpfSession = perfHintMgr!!.createHintSession(intArrayOf(Process.myTid()),
                TimeUnit.SECONDS.toNanos(1))
        hinter.setAdpfSession(adpfSession)
    }

    fun dump(pw: PrintWriter, prefix: String?) {
        hinter.dump(pw, prefix)
    }
}