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

Commit b878a206 authored by Ady Abraham's avatar Ady Abraham
Browse files

DisplayModeDirector: separate the display refresh rate and the render frame rate

Add an additional dimension to DisplayModeDirector for render frame rate
in addition to the display physical refresh rate. DisplayModeDirector aggregates the requests for both render frame rates and physical refresh rates, and sets the policy to SurfaceFlinger.
SurfaceFlinger will select the display refresh rate based on the physical refresh rate range, and schedule applications based on the render frame rate range.
This is a change over the previous logic where the render frame rate and
physical refresh rate were always the same for SurfaceFlinger.

Test: atest LocalDisplayAdapterTest DisplayModeDirectorTest
Bug: 241460058
Change-Id: I9563257c7268a731a3919ffa368f7b8ae777eb99
parent a18bf663
Loading
Loading
Loading
Loading
+1 −68
Original line number Diff line number Diff line
@@ -24,11 +24,11 @@ import android.hardware.SensorManager;
import android.os.Handler;
import android.os.PowerManager;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.SurfaceControl;
import android.view.SurfaceControl.RefreshRateRange;
import android.view.SurfaceControl.Transaction;
import android.window.DisplayWindowPolicyController;
import android.window.ScreenCapture;
@@ -36,7 +36,6 @@ import android.window.ScreenCapture;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
@@ -650,72 +649,6 @@ public abstract class DisplayManagerInternal {
        void onDisplayGroupChanged(int groupId);
    }

    /**
     * Information about the min and max refresh rate DM would like to set the display to.
     */
    public static final class RefreshRateRange {
        public static final String TAG = "RefreshRateRange";

        // The tolerance within which we consider something approximately equals.
        public static final float FLOAT_TOLERANCE = 0.01f;

        /**
         * The lowest desired refresh rate.
         */
        public float min;

        /**
         * The highest desired refresh rate.
         */
        public float max;

        public RefreshRateRange() {}

        public RefreshRateRange(float min, float max) {
            if (min < 0 || max < 0 || min > max + FLOAT_TOLERANCE) {
                Slog.e(TAG, "Wrong values for min and max when initializing RefreshRateRange : "
                        + min + " " + max);
                this.min = this.max = 0;
                return;
            }
            if (min > max) {
                // Min and max are within epsilon of each other, but in the wrong order.
                float t = min;
                min = max;
                max = t;
            }
            this.min = min;
            this.max = max;
        }

        /**
         * Checks whether the two objects have the same values.
         */
        @Override
        public boolean equals(Object other) {
            if (other == this) {
                return true;
            }

            if (!(other instanceof RefreshRateRange)) {
                return false;
            }

            RefreshRateRange refreshRateRange = (RefreshRateRange) other;
            return (min == refreshRateRange.min && max == refreshRateRange.max);
        }

        @Override
        public int hashCode() {
            return Objects.hash(min, max);
        }

        @Override
        public String toString() {
            return "(" + min + " " + max + ")";
        }
    }

    /**
     * Describes a limitation on a display's refresh rate. Includes the allowed refresh rate
     * range as well as information about when it applies, such as high-brightness-mode.
+179 −35
Original line number Diff line number Diff line
@@ -67,6 +67,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import android.view.Surface.OutOfResourcesException;
@@ -1673,6 +1674,146 @@ public final class SurfaceControl implements Parcelable {
        return nativeGetDisplayedContentSample(displayToken, maxFrames, timestamp);
    }

    /**
     * Information about the min and max refresh rate DM would like to set the display to.
     * @hide
     */
    public static final class RefreshRateRange {
        public static final String TAG = "RefreshRateRange";

        // The tolerance within which we consider something approximately equals.
        public static final float FLOAT_TOLERANCE = 0.01f;

        /**
         * The lowest desired refresh rate.
         */
        public float min;

        /**
         * The highest desired refresh rate.
         */
        public float max;

        public RefreshRateRange() {}

        public RefreshRateRange(float min, float max) {
            if (min < 0 || max < 0 || min > max + FLOAT_TOLERANCE) {
                Slog.e(TAG, "Wrong values for min and max when initializing RefreshRateRange : "
                        + min + " " + max);
                this.min = this.max = 0;
                return;
            }
            if (min > max) {
                // Min and max are within epsilon of each other, but in the wrong order.
                float t = min;
                min = max;
                max = t;
            }
            this.min = min;
            this.max = max;
        }

        /**
         * Checks whether the two objects have the same values.
         */
        @Override
        public boolean equals(Object other) {
            if (other == this) {
                return true;
            }

            if (!(other instanceof RefreshRateRange)) {
                return false;
            }

            RefreshRateRange refreshRateRange = (RefreshRateRange) other;
            return (min == refreshRateRange.min && max == refreshRateRange.max);
        }

        @Override
        public int hashCode() {
            return Objects.hash(min, max);
        }

        @Override
        public String toString() {
            return "(" + min + " " + max + ")";
        }

        /**
         * Copies the supplied object's values to this object.
         */
        public void copyFrom(RefreshRateRange other) {
            this.min = other.min;
            this.max = other.max;
        }
    }

    /**
     * Information about the ranges of refresh rates for the display physical refresh rates and the
     * render frame rate DM would like to set the policy to.
     * @hide
     */
    public static final class RefreshRateRanges {
        public static final String TAG = "RefreshRateRanges";

        /**
         *  The range of refresh rates that the display should run at.
         */
        public final RefreshRateRange physical;

        /**
         *  The range of refresh rates that apps should render at.
         */
        public final RefreshRateRange render;

        public RefreshRateRanges() {
            physical = new RefreshRateRange();
            render = new RefreshRateRange();
        }

        public RefreshRateRanges(RefreshRateRange physical, RefreshRateRange render) {
            this.physical = new RefreshRateRange(physical.min, physical.max);
            this.render = new RefreshRateRange(render.min, render.max);
        }

        /**
         * Checks whether the two objects have the same values.
         */
        @Override
        public boolean equals(Object other) {
            if (other == this) {
                return true;
            }

            if (!(other instanceof RefreshRateRanges)) {
                return false;
            }

            RefreshRateRanges rates = (RefreshRateRanges) other;
            return physical.equals(rates.physical) && render.equals(
                    rates.render);
        }

        @Override
        public int hashCode() {
            return Objects.hash(physical, render);
        }

        @Override
        public String toString() {
            return "physical: " + physical + " render:  " + render;
        }

        /**
         * Copies the supplied object's values to this object.
         */
        public void copyFrom(RefreshRateRanges other) {
            this.physical.copyFrom(other.physical);
            this.render.copyFrom(other.render);
        }
    }


    /**
     * Contains information about desired display configuration.
@@ -1682,44 +1823,49 @@ public final class SurfaceControl implements Parcelable {
    public static final class DesiredDisplayModeSpecs {
        public int defaultMode;
        /**
         * The primary refresh rate range represents display manager's general guidance on the
         * display configs surface flinger will consider when switching refresh rates. Unless
         * surface flinger has a specific reason to do otherwise, it will stay within this range.
         * If true this will allow switching between modes in different display configuration
         * groups. This way the user may see visual interruptions when the display mode changes.
         */
        public float primaryRefreshRateMin;
        public float primaryRefreshRateMax;
        public boolean allowGroupSwitching;

        /**
         * The app request refresh rate range allows surface flinger to consider more display
         * configs when switching refresh rates. Although surface flinger will generally stay within
         * the primary range, specific considerations, such as layer frame rate settings specified
         * via the setFrameRate() api, may cause surface flinger to go outside the primary
         * range. Surface flinger never goes outside the app request range. The app request range
         * will be greater than or equal to the primary refresh rate range, never smaller.
         * The primary physical and render refresh rate ranges represent display manager's general
         * guidance on the display configs surface flinger will consider when switching refresh
         * rates and scheduling the frame rate. Unless surface flinger has a specific reason to do
         * otherwise, it will stay within this range.
         */
        public float appRequestRefreshRateMin;
        public float appRequestRefreshRateMax;
        public final RefreshRateRanges primaryRanges;

        /**
         * If true this will allow switching between modes in different display configuration
         * groups. This way the user may see visual interruptions when the display mode changes.
         * The app request physical and render refresh rate ranges allow surface flinger to consider
         * more display configs when switching refresh rates. Although surface flinger will
         * generally stay within the primary range, specific considerations, such as layer frame
         * rate settings specified via the setFrameRate() api, may cause surface flinger to go
         * outside the primary range. Surface flinger never goes outside the app request range.
         * The app request range will be greater than or equal to the primary refresh rate range,
         * never smaller.
         */
        public boolean allowGroupSwitching;
        public final RefreshRateRanges appRequestRanges;

        public DesiredDisplayModeSpecs() {}
        public DesiredDisplayModeSpecs() {
            this.primaryRanges = new RefreshRateRanges();
            this.appRequestRanges = new RefreshRateRanges();
        }

        public DesiredDisplayModeSpecs(DesiredDisplayModeSpecs other) {
            this.primaryRanges = new RefreshRateRanges();
            this.appRequestRanges = new RefreshRateRanges();
            copyFrom(other);
        }

        public DesiredDisplayModeSpecs(int defaultMode, boolean allowGroupSwitching,
                float primaryRefreshRateMin, float primaryRefreshRateMax,
                float appRequestRefreshRateMin, float appRequestRefreshRateMax) {
                RefreshRateRanges primaryRanges, RefreshRateRanges appRequestRanges) {
            this.defaultMode = defaultMode;
            this.allowGroupSwitching = allowGroupSwitching;
            this.primaryRefreshRateMin = primaryRefreshRateMin;
            this.primaryRefreshRateMax = primaryRefreshRateMax;
            this.appRequestRefreshRateMin = appRequestRefreshRateMin;
            this.appRequestRefreshRateMax = appRequestRefreshRateMax;
            this.primaryRanges =
                    new RefreshRateRanges(primaryRanges.physical, primaryRanges.render);
            this.appRequestRanges =
                    new RefreshRateRanges(appRequestRanges.physical, appRequestRanges.render);
        }

        @Override
@@ -1732,10 +1878,9 @@ public final class SurfaceControl implements Parcelable {
         */
        public boolean equals(DesiredDisplayModeSpecs other) {
            return other != null && defaultMode == other.defaultMode
                    && primaryRefreshRateMin == other.primaryRefreshRateMin
                    && primaryRefreshRateMax == other.primaryRefreshRateMax
                    && appRequestRefreshRateMin == other.appRequestRefreshRateMin
                    && appRequestRefreshRateMax == other.appRequestRefreshRateMax;
                    && allowGroupSwitching == other.allowGroupSwitching
                    && primaryRanges.equals(other.primaryRanges)
                    && appRequestRanges.equals(other.appRequestRanges);
        }

        @Override
@@ -1748,18 +1893,17 @@ public final class SurfaceControl implements Parcelable {
         */
        public void copyFrom(DesiredDisplayModeSpecs other) {
            defaultMode = other.defaultMode;
            primaryRefreshRateMin = other.primaryRefreshRateMin;
            primaryRefreshRateMax = other.primaryRefreshRateMax;
            appRequestRefreshRateMin = other.appRequestRefreshRateMin;
            appRequestRefreshRateMax = other.appRequestRefreshRateMax;
            allowGroupSwitching = other.allowGroupSwitching;
            primaryRanges.copyFrom(other.primaryRanges);
            appRequestRanges.copyFrom(other.appRequestRanges);
        }

        @Override
        public String toString() {
            return String.format("defaultConfig=%d primaryRefreshRateRange=[%.0f %.0f]"
                            + " appRequestRefreshRateRange=[%.0f %.0f]",
                    defaultMode, primaryRefreshRateMin, primaryRefreshRateMax,
                    appRequestRefreshRateMin, appRequestRefreshRateMax);
            return "defaultMode=" + defaultMode
                    + " allowGroupSwitching=" + allowGroupSwitching
                    + " primaryRanges=" + primaryRanges
                    + " appRequestRanges=" + appRequestRanges;
        }
    }

+116 −42
Original line number Diff line number Diff line
@@ -187,15 +187,27 @@ static struct {
    jfieldID white;
} gDisplayPrimariesClassInfo;

static struct {
    jclass clazz;
    jmethodID ctor;
    jfieldID min;
    jfieldID max;
} gRefreshRateRangeClassInfo;

static struct {
    jclass clazz;
    jmethodID ctor;
    jfieldID physical;
    jfieldID render;
} gRefreshRateRangesClassInfo;

static struct {
    jclass clazz;
    jmethodID ctor;
    jfieldID defaultMode;
    jfieldID allowGroupSwitching;
    jfieldID primaryRefreshRateMin;
    jfieldID primaryRefreshRateMax;
    jfieldID appRequestRefreshRateMin;
    jfieldID appRequestRefreshRateMax;
    jfieldID primaryRanges;
    jfieldID appRequestRanges;
} gDesiredDisplayModeSpecsClassInfo;

static struct {
@@ -1190,6 +1202,39 @@ static jobject nativeGetDynamicDisplayInfo(JNIEnv* env, jclass clazz, jobject to
    return object;
}

struct RefreshRateRange {
    const float min;
    const float max;

    RefreshRateRange(float min, float max) : min(min), max(max) {}

    RefreshRateRange(JNIEnv* env, jobject obj)
          : min(env->GetFloatField(obj, gRefreshRateRangeClassInfo.min)),
            max(env->GetFloatField(obj, gRefreshRateRangeClassInfo.max)) {}

    jobject toJava(JNIEnv* env) const {
        return env->NewObject(gRefreshRateRangeClassInfo.clazz, gRefreshRateRangeClassInfo.ctor,
                              min, max);
    }
};

struct RefreshRateRanges {
    const RefreshRateRange physical;
    const RefreshRateRange render;

    RefreshRateRanges(RefreshRateRange physical, RefreshRateRange render)
          : physical(physical), render(render) {}

    RefreshRateRanges(JNIEnv* env, jobject obj)
          : physical(env, env->GetObjectField(obj, gRefreshRateRangesClassInfo.physical)),
            render(env, env->GetObjectField(obj, gRefreshRateRangesClassInfo.render)) {}

    jobject toJava(JNIEnv* env) const {
        return env->NewObject(gRefreshRateRangesClassInfo.clazz, gRefreshRateRangesClassInfo.ctor,
                              physical.toJava(env), render.toJava(env));
    }
};

static jboolean nativeSetDesiredDisplayModeSpecs(JNIEnv* env, jclass clazz, jobject tokenObj,
                                                 jobject DesiredDisplayModeSpecs) {
    sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
@@ -1200,25 +1245,23 @@ static jboolean nativeSetDesiredDisplayModeSpecs(JNIEnv* env, jclass clazz, jobj
    jboolean allowGroupSwitching =
            env->GetBooleanField(DesiredDisplayModeSpecs,
                                 gDesiredDisplayModeSpecsClassInfo.allowGroupSwitching);
    jfloat primaryRefreshRateMin =
            env->GetFloatField(DesiredDisplayModeSpecs,
                               gDesiredDisplayModeSpecsClassInfo.primaryRefreshRateMin);
    jfloat primaryRefreshRateMax =
            env->GetFloatField(DesiredDisplayModeSpecs,
                               gDesiredDisplayModeSpecsClassInfo.primaryRefreshRateMax);
    jfloat appRequestRefreshRateMin =
            env->GetFloatField(DesiredDisplayModeSpecs,
                               gDesiredDisplayModeSpecsClassInfo.appRequestRefreshRateMin);
    jfloat appRequestRefreshRateMax =
            env->GetFloatField(DesiredDisplayModeSpecs,
                               gDesiredDisplayModeSpecsClassInfo.appRequestRefreshRateMax);

    size_t result = SurfaceComposerClient::setDesiredDisplayModeSpecs(token, defaultMode,

    const jobject primaryRangesObject =
            env->GetObjectField(DesiredDisplayModeSpecs,
                                gDesiredDisplayModeSpecsClassInfo.primaryRanges);
    const jobject appRequestRangesObject =
            env->GetObjectField(DesiredDisplayModeSpecs,
                                gDesiredDisplayModeSpecsClassInfo.appRequestRanges);
    const RefreshRateRanges primaryRanges(env, primaryRangesObject);
    const RefreshRateRanges appRequestRanges(env, appRequestRangesObject);

    size_t result =
            SurfaceComposerClient::setDesiredDisplayModeSpecs(token, defaultMode,
                                                              allowGroupSwitching,
                                                                      primaryRefreshRateMin,
                                                                      primaryRefreshRateMax,
                                                                      appRequestRefreshRateMin,
                                                                      appRequestRefreshRateMax);
                                                              primaryRanges.physical.min,
                                                              primaryRanges.physical.max,
                                                              appRequestRanges.physical.min,
                                                              appRequestRanges.physical.max);
    return result == NO_ERROR ? JNI_TRUE : JNI_FALSE;
}

@@ -1228,22 +1271,31 @@ static jobject nativeGetDesiredDisplayModeSpecs(JNIEnv* env, jclass clazz, jobje

    ui::DisplayModeId defaultMode;
    bool allowGroupSwitching;
    float primaryRefreshRateMin;
    float primaryRefreshRateMax;
    float appRequestRefreshRateMin;
    float appRequestRefreshRateMax;
    float primaryPhysicalRefreshRateMin;
    float primaryPhysicalRefreshRateMax;
    float appRequestPhysicalRefreshRateMin;
    float appRequestPhysicalRefreshRateMax;
    if (SurfaceComposerClient::getDesiredDisplayModeSpecs(token, &defaultMode, &allowGroupSwitching,
                                                          &primaryRefreshRateMin,
                                                          &primaryRefreshRateMax,
                                                          &appRequestRefreshRateMin,
                                                          &appRequestRefreshRateMax) != NO_ERROR) {
                                                          &primaryPhysicalRefreshRateMin,
                                                          &primaryPhysicalRefreshRateMax,
                                                          &appRequestPhysicalRefreshRateMin,
                                                          &appRequestPhysicalRefreshRateMax) !=
        NO_ERROR) {
        return nullptr;
    }

    const RefreshRateRange primaryPhysicalRange(primaryPhysicalRefreshRateMin,
                                                primaryPhysicalRefreshRateMax);
    const RefreshRateRange appRequestPhysicalRange(appRequestPhysicalRefreshRateMin,
                                                   appRequestPhysicalRefreshRateMax);

    // TODO(b/241460058): populate the render ranges
    const RefreshRateRanges primaryRanges(primaryPhysicalRange, primaryPhysicalRange);
    const RefreshRateRanges appRequestRanges(appRequestPhysicalRange, appRequestPhysicalRange);

    return env->NewObject(gDesiredDisplayModeSpecsClassInfo.clazz,
                          gDesiredDisplayModeSpecsClassInfo.ctor, defaultMode, allowGroupSwitching,
                          primaryRefreshRateMin, primaryRefreshRateMax, appRequestRefreshRateMin,
                          appRequestRefreshRateMax);
                          primaryRanges.toJava(env), appRequestRanges.toJava(env));
}

static jobject nativeGetDisplayNativePrimaries(JNIEnv* env, jclass, jobject tokenObj) {
@@ -2235,23 +2287,45 @@ int register_android_view_SurfaceControl(JNIEnv* env)
    gDisplayPrimariesClassInfo.white = GetFieldIDOrDie(env, displayPrimariesClazz, "white",
            "Landroid/view/SurfaceControl$CieXyz;");

    jclass RefreshRateRangeClazz =
            FindClassOrDie(env, "android/view/SurfaceControl$RefreshRateRange");
    gRefreshRateRangeClassInfo.clazz = MakeGlobalRefOrDie(env, RefreshRateRangeClazz);
    gRefreshRateRangeClassInfo.ctor =
            GetMethodIDOrDie(env, gRefreshRateRangeClassInfo.clazz, "<init>", "(FF)V");
    gRefreshRateRangeClassInfo.min = GetFieldIDOrDie(env, RefreshRateRangeClazz, "min", "F");
    gRefreshRateRangeClassInfo.max = GetFieldIDOrDie(env, RefreshRateRangeClazz, "max", "F");

    jclass RefreshRateRangesClazz =
            FindClassOrDie(env, "android/view/SurfaceControl$RefreshRateRanges");
    gRefreshRateRangesClassInfo.clazz = MakeGlobalRefOrDie(env, RefreshRateRangesClazz);
    gRefreshRateRangesClassInfo.ctor =
            GetMethodIDOrDie(env, gRefreshRateRangesClassInfo.clazz, "<init>",
                             "(Landroid/view/SurfaceControl$RefreshRateRange;Landroid/view/"
                             "SurfaceControl$RefreshRateRange;)V");
    gRefreshRateRangesClassInfo.physical =
            GetFieldIDOrDie(env, RefreshRateRangesClazz, "physical",
                            "Landroid/view/SurfaceControl$RefreshRateRange;");
    gRefreshRateRangesClassInfo.render =
            GetFieldIDOrDie(env, RefreshRateRangesClazz, "render",
                            "Landroid/view/SurfaceControl$RefreshRateRange;");

    jclass DesiredDisplayModeSpecsClazz =
            FindClassOrDie(env, "android/view/SurfaceControl$DesiredDisplayModeSpecs");
    gDesiredDisplayModeSpecsClassInfo.clazz = MakeGlobalRefOrDie(env, DesiredDisplayModeSpecsClazz);
    gDesiredDisplayModeSpecsClassInfo.ctor =
            GetMethodIDOrDie(env, gDesiredDisplayModeSpecsClassInfo.clazz, "<init>", "(IZFFFF)V");
            GetMethodIDOrDie(env, gDesiredDisplayModeSpecsClassInfo.clazz, "<init>",
                             "(IZLandroid/view/SurfaceControl$RefreshRateRanges;Landroid/view/"
                             "SurfaceControl$RefreshRateRanges;)V");
    gDesiredDisplayModeSpecsClassInfo.defaultMode =
            GetFieldIDOrDie(env, DesiredDisplayModeSpecsClazz, "defaultMode", "I");
    gDesiredDisplayModeSpecsClassInfo.allowGroupSwitching =
            GetFieldIDOrDie(env, DesiredDisplayModeSpecsClazz, "allowGroupSwitching", "Z");
    gDesiredDisplayModeSpecsClassInfo.primaryRefreshRateMin =
            GetFieldIDOrDie(env, DesiredDisplayModeSpecsClazz, "primaryRefreshRateMin", "F");
    gDesiredDisplayModeSpecsClassInfo.primaryRefreshRateMax =
            GetFieldIDOrDie(env, DesiredDisplayModeSpecsClazz, "primaryRefreshRateMax", "F");
    gDesiredDisplayModeSpecsClassInfo.appRequestRefreshRateMin =
            GetFieldIDOrDie(env, DesiredDisplayModeSpecsClazz, "appRequestRefreshRateMin", "F");
    gDesiredDisplayModeSpecsClassInfo.appRequestRefreshRateMax =
            GetFieldIDOrDie(env, DesiredDisplayModeSpecsClazz, "appRequestRefreshRateMax", "F");
    gDesiredDisplayModeSpecsClassInfo.primaryRanges =
            GetFieldIDOrDie(env, DesiredDisplayModeSpecsClazz, "primaryRanges",
                            "Landroid/view/SurfaceControl$RefreshRateRanges;");
    gDesiredDisplayModeSpecsClassInfo.appRequestRanges =
            GetFieldIDOrDie(env, DesiredDisplayModeSpecsClazz, "appRequestRanges",
                            "Landroid/view/SurfaceControl$RefreshRateRanges;");

    jclass jankDataClazz =
                FindClassOrDie(env, "android/view/SurfaceControl$JankData");
+1 −0
Original line number Diff line number Diff line
@@ -117,6 +117,7 @@ import android.view.DisplayEventReceiver;
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.RefreshRateRange;
import android.window.DisplayWindowPolicyController;
import android.window.ScreenCapture;

+286 −114

File changed.

Preview size limit exceeded, changes collapsed.

Loading