script-astra/Android/Sdk/sources/android-35/android/window/SystemPerformanceHinter.java

412 lines
16 KiB
Java
Raw Permalink Normal View History

2025-01-20 15:15:20 +00:00
/*
* 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.Surface.FRAME_RATE_CATEGORY_DEFAULT;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN;
import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_PROPAGATE;
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.Display;
import android.view.SurfaceControl;
import java.io.PrintWriter;
import java.util.ArrayList;
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";
private static final int HINT_NO_OP = 0;
// 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 String mTraceName;
protected HighPerfSession(@HintFlags int hintFlags, int displayId, @NonNull String reason) {
this.hintFlags = hintFlags;
this.reason = reason;
this.displayId = displayId;
}
/** Makes this session active. It is no-op if this session is already active. */
public void start() {
if (!mActiveSessions.contains(this)) {
startSession(this);
}
}
/**
* Closes this session.
*/
@Override
public void close() {
endSession(this);
}
@Override
public void finalize() {
close();
}
boolean asyncTraceBegin() {
if (!Trace.isTagEnabled(mTraceTag)) {
mTraceName = null;
return false;
}
if (mTraceName == null) {
mTraceName = "PerfSession-d" + displayId + "-" + reason;
}
Trace.asyncTraceForTrackBegin(mTraceTag, TAG, mTraceName,
System.identityHashCode(this));
return true;
}
boolean asyncTraceEnd() {
if (mTraceName == null) {
return false;
}
Trace.asyncTraceForTrackEnd(mTraceTag, TAG, System.identityHashCode(this));
return true;
}
}
/**
* A no-op implementation of a session.
*/
private class NoOpHighPerfSession extends HighPerfSession {
public NoOpHighPerfSession() {
super(HINT_NO_OP, Display.INVALID_DISPLAY, "");
}
@Override
public void start() {
}
@Override
public void close() {
// Do nothing
}
}
/** The tag category of trace. */
public long mTraceTag = Trace.TRACE_TAG_APP;
// 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
*/
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;
}
/** Creates a session that requires high performance. */
public HighPerfSession createSession(@HintFlags int hintFlags, int displayId,
@NonNull String reason) {
if (hintFlags == HINT_NO_OP) {
throw new IllegalArgumentException("Not allow empty hint flags");
}
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 new session that requires high performance.
*/
public HighPerfSession startSession(@HintFlags int hintFlags, int displayId,
@NonNull String reason) {
final HighPerfSession session = createSession(hintFlags, displayId, reason);
if (session.hintFlags != HINT_NO_OP) {
startSession(session);
}
return session;
}
/** Starts the session that requires high performance. */
private void startSession(HighPerfSession session) {
final boolean isTraceEnabled = session.asyncTraceBegin();
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)) {
SurfaceControl displaySurfaceControl = mDisplayRootProvider.getRootForDisplay(
session.displayId);
mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
// smoothSwitchOnly is false to request a higher framerate, even if it means switching
// the display mode will cause would jank on non-VRR devices because keeping a lower
// refresh rate would mean a poorer user experience.
mTransaction.setFrameRateCategory(
displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH, /* smoothSwitchOnly= */ false);
transactionChanged = true;
if (isTraceEnabled) {
asyncTraceBegin(HINT_SF_FRAME_RATE, session.displayId);
}
}
// Global flags
if (nowEnabled(oldGlobalFlags, newGlobalFlags, HINT_SF_EARLY_WAKEUP)) {
mTransaction.setEarlyWakeupStart();
transactionChanged = true;
if (isTraceEnabled) {
asyncTraceBegin(HINT_SF_EARLY_WAKEUP, Display.INVALID_DISPLAY);
}
}
if (nowEnabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
mAdpfSession.sendHint(PerformanceHintManager.Session.CPU_LOAD_UP);
if (isTraceEnabled) {
asyncTraceBegin(HINT_ADPF, Display.INVALID_DISPLAY);
}
}
if (transactionChanged) {
mTransaction.applyAsyncUnsafe();
}
}
/**
* Ends a session that requires high performance.
*/
private void endSession(HighPerfSession session) {
final boolean isTraceEnabled = session.asyncTraceEnd();
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)) {
SurfaceControl displaySurfaceControl = mDisplayRootProvider.getRootForDisplay(
session.displayId);
mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,
FRAME_RATE_SELECTION_STRATEGY_PROPAGATE);
// smoothSwitchOnly is false to request a higher framerate, even if it means switching
// the display mode will cause would jank on non-VRR devices because keeping a lower
// refresh rate would mean a poorer user experience.
mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT,
/* smoothSwitchOnly= */ false);
transactionChanged = true;
if (isTraceEnabled) {
asyncTraceEnd(HINT_SF_FRAME_RATE);
}
}
// Global flags
if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_SF_EARLY_WAKEUP)) {
mTransaction.setEarlyWakeupEnd();
transactionChanged = true;
if (isTraceEnabled) {
asyncTraceEnd(HINT_SF_EARLY_WAKEUP);
}
}
if (nowDisabled(oldGlobalFlags, newGlobalFlags, HINT_ADPF)) {
mAdpfSession.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
if (isTraceEnabled) {
asyncTraceEnd(HINT_ADPF);
}
}
if (transactionChanged) {
mTransaction.applyAsyncUnsafe();
}
}
/**
* 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;
}
private void asyncTraceBegin(@HintFlags int flag, int displayId) {
final String prefix = switch (flag) {
case HINT_SF_EARLY_WAKEUP -> "PerfHint-early_wakeup";
case HINT_SF_FRAME_RATE -> "PerfHint-framerate";
case HINT_ADPF -> "PerfHint-adpf";
default -> "PerfHint-" + flag;
};
final String name = displayId != Display.INVALID_DISPLAY
? (prefix + "-d" + displayId) : prefix;
Trace.asyncTraceForTrackBegin(mTraceTag, TAG, name,
flag ^ System.identityHashCode(this));
}
private void asyncTraceEnd(@HintFlags int flag) {
Trace.asyncTraceForTrackEnd(mTraceTag, TAG, flag ^ System.identityHashCode(this));
}
/**
* 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);
}
}
}