• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.display.mode;
18 
19 import android.annotation.Nullable;
20 import android.util.Slog;
21 import android.util.SparseArray;
22 import android.view.Display;
23 import android.view.SurfaceControl;
24 
25 import java.util.ArrayList;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Set;
29 
30 final class VoteSummary {
31     private static final float FLOAT_TOLERANCE = SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE;
32     private static final String TAG = "VoteSummary";
33 
34     public float minPhysicalRefreshRate;
35     public float maxPhysicalRefreshRate;
36     public float minRenderFrameRate;
37     public float maxRenderFrameRate;
38     public int width;
39     public int height;
40     public int minWidth;
41     public int minHeight;
42     public boolean disableRefreshRateSwitching;
43     /**
44      *  available modes should have mode with specific refresh rate
45      */
46     public float appRequestBaseModeRefreshRate;
47     /**
48      * requestRefreshRate should be within [minRenderFrameRate, maxRenderFrameRate] range
49      */
50     public Set<Float> requestedRefreshRates = new HashSet<>();
51 
52     @Nullable
53     public List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates;
54 
55     @Nullable
56     public List<Integer> supportedModeIds;
57 
58     /**
59      * set of rejected modes due to mode config failure for connected display
60      */
61     public Set<Integer> rejectedModeIds = new HashSet<>();
62 
63     final boolean mIsDisplayResolutionRangeVotingEnabled;
64 
65     private final boolean mSupportedModesVoteEnabled;
66     private final boolean mSupportsFrameRateOverride;
67     private final boolean mLoggingEnabled;
68 
VoteSummary(boolean isDisplayResolutionRangeVotingEnabled, boolean supportedModesVoteEnabled, boolean loggingEnabled, boolean supportsFrameRateOverride)69     VoteSummary(boolean isDisplayResolutionRangeVotingEnabled, boolean supportedModesVoteEnabled,
70             boolean loggingEnabled, boolean supportsFrameRateOverride) {
71         mIsDisplayResolutionRangeVotingEnabled = isDisplayResolutionRangeVotingEnabled;
72         mSupportedModesVoteEnabled = supportedModesVoteEnabled;
73         mLoggingEnabled = loggingEnabled;
74         mSupportsFrameRateOverride = supportsFrameRateOverride;
75         reset();
76     }
77 
applyVotes(SparseArray<Vote> votes, int lowestConsideredPriority, int highestConsideredPriority)78     void applyVotes(SparseArray<Vote> votes,
79             int lowestConsideredPriority, int highestConsideredPriority) {
80         reset();
81         for (int priority = highestConsideredPriority;
82                 priority >= lowestConsideredPriority;
83                 priority--) {
84             Vote vote = votes.get(priority);
85             if (vote == null) {
86                 continue;
87             }
88             vote.updateSummary(this);
89         }
90         if (mLoggingEnabled) {
91             Slog.i(TAG, "applyVotes for range ["
92                     + Vote.priorityToString(lowestConsideredPriority) + ", "
93                     + Vote.priorityToString(highestConsideredPriority) + "]: "
94                     + this);
95         }
96     }
97 
adjustSize(Display.Mode defaultMode, Display.Mode[] modes)98     void adjustSize(Display.Mode defaultMode, Display.Mode[] modes) {
99         // If we don't have anything specifying the width / height of the display, just use
100         // the default width and height. We don't want these switching out from underneath
101         // us since it's a pretty disruptive behavior.
102         if (height == Vote.INVALID_SIZE || width == Vote.INVALID_SIZE) {
103             width = defaultMode.getPhysicalWidth();
104             height = defaultMode.getPhysicalHeight();
105         } else if (mIsDisplayResolutionRangeVotingEnabled) {
106             updateSummaryWithBestAllowedResolution(modes);
107         }
108         if (mLoggingEnabled) {
109             Slog.i(TAG, "adjustSize: " + this);
110         }
111     }
112 
limitRefreshRanges(VoteSummary otherSummary)113     void limitRefreshRanges(VoteSummary otherSummary) {
114         minPhysicalRefreshRate =
115                 Math.min(minPhysicalRefreshRate, otherSummary.minPhysicalRefreshRate);
116         maxPhysicalRefreshRate =
117                 Math.max(maxPhysicalRefreshRate, otherSummary.maxPhysicalRefreshRate);
118         minRenderFrameRate = Math.min(minRenderFrameRate, otherSummary.minRenderFrameRate);
119         maxRenderFrameRate = Math.max(maxRenderFrameRate, otherSummary.maxRenderFrameRate);
120 
121         if (mLoggingEnabled) {
122             Slog.i(TAG, "limitRefreshRanges: " + this);
123         }
124     }
125 
filterModes(Display.Mode[] modes)126     List<Display.Mode> filterModes(Display.Mode[] modes) {
127         if (!isValid()) {
128             return new ArrayList<>();
129         }
130         ArrayList<Display.Mode> availableModes = new ArrayList<>();
131         boolean missingBaseModeRefreshRate = appRequestBaseModeRefreshRate > 0f;
132 
133         for (Display.Mode mode : modes) {
134             if (!validateRefreshRatesSupported(mode)) {
135                 continue;
136             }
137             if (!validateModeSupported(mode)) {
138                 continue;
139             }
140             if (!validateModeRejected(mode)) {
141                 continue;
142             }
143             if (!validateModeSize(mode)) {
144                 continue;
145             }
146             if (!validateModeWithinPhysicalRefreshRange(mode)) {
147                 continue;
148             }
149             if (!validateModeWithinRenderRefreshRange(mode)) {
150                 continue;
151             }
152             if (!validateModeRenderRateAchievable(mode)) {
153                 continue;
154             }
155             availableModes.add(mode);
156             if (equalsWithinFloatTolerance(mode.getRefreshRate(), appRequestBaseModeRefreshRate)) {
157                 missingBaseModeRefreshRate = false;
158             }
159         }
160         if (missingBaseModeRefreshRate) {
161             return new ArrayList<>();
162         }
163 
164         return availableModes;
165     }
166 
selectBaseMode(List<Display.Mode> availableModes, Display.Mode defaultMode)167     Display.Mode selectBaseMode(List<Display.Mode> availableModes, Display.Mode defaultMode) {
168         // The base mode should be as close as possible to the app requested mode. Since all the
169         // available modes already have the same size, we just need to look for a matching refresh
170         // rate. If the summary doesn't include an app requested refresh rate, we'll use the default
171         // mode refresh rate. This is important because SurfaceFlinger can do only seamless switches
172         // by default. Some devices (e.g. TV) don't support seamless switching so the mode we select
173         // here won't be changed.
174         float preferredRefreshRate =
175                 appRequestBaseModeRefreshRate > 0
176                         ? appRequestBaseModeRefreshRate : defaultMode.getRefreshRate();
177         for (Display.Mode availableMode : availableModes) {
178             if (equalsWithinFloatTolerance(preferredRefreshRate, availableMode.getRefreshRate())) {
179                 return availableMode;
180             }
181         }
182 
183         // If we couldn't find a mode id based on the refresh rate, it means that the available
184         // modes were filtered by the app requested size, which is different that the default mode
185         // size, and the requested app refresh rate was dropped from the summary due to a higher
186         // priority vote. Since we don't have any other hint about the refresh rate,
187         // we just pick the first.
188         return !availableModes.isEmpty() ? availableModes.get(0) : null;
189     }
190 
disableModeSwitching(float fps)191     void disableModeSwitching(float fps) {
192         minPhysicalRefreshRate = maxPhysicalRefreshRate = fps;
193         maxRenderFrameRate = Math.min(maxRenderFrameRate, fps);
194 
195         if (mLoggingEnabled) {
196             Slog.i(TAG, "Disabled mode switching on summary: " + this);
197         }
198     }
199 
disableRenderRateSwitching(float fps)200     void disableRenderRateSwitching(float fps) {
201         minRenderFrameRate = maxRenderFrameRate;
202 
203         if (!isRenderRateAchievable(fps)) {
204             minRenderFrameRate = maxRenderFrameRate = fps;
205         }
206 
207         if (mLoggingEnabled) {
208             Slog.i(TAG, "Disabled render rate switching on summary: " + this);
209         }
210     }
validateModeSize(Display.Mode mode)211     private boolean validateModeSize(Display.Mode mode) {
212         if (mode.getPhysicalWidth() != width
213                 || mode.getPhysicalHeight() != height) {
214             if (mLoggingEnabled) {
215                 Slog.w(TAG, "Discarding mode " + mode.getModeId() + ", wrong size"
216                         + ": desiredWidth=" + width
217                         + ": desiredHeight=" + height
218                         + ": actualWidth=" + mode.getPhysicalWidth()
219                         + ": actualHeight=" + mode.getPhysicalHeight());
220             }
221             return false;
222         }
223         return true;
224     }
225 
validateModeWithinPhysicalRefreshRange(Display.Mode mode)226     private boolean validateModeWithinPhysicalRefreshRange(Display.Mode mode) {
227         float refreshRate = mode.getRefreshRate();
228         // Some refresh rates are calculated based on frame timings, so they aren't *exactly*
229         // equal to expected refresh rate. Given that, we apply a bit of tolerance to this
230         // comparison.
231         if (refreshRate < (minPhysicalRefreshRate - FLOAT_TOLERANCE)
232                 || refreshRate > (maxPhysicalRefreshRate + FLOAT_TOLERANCE)) {
233             if (mLoggingEnabled) {
234                 Slog.w(TAG, "Discarding mode " + mode.getModeId()
235                         + ", outside refresh rate bounds"
236                         + ": minPhysicalRefreshRate=" + minPhysicalRefreshRate
237                         + ", maxPhysicalRefreshRate=" + maxPhysicalRefreshRate
238                         + ", modeRefreshRate=" + refreshRate);
239             }
240             return false;
241         }
242         return true;
243     }
244 
validateModeWithinRenderRefreshRange(Display.Mode mode)245     private boolean validateModeWithinRenderRefreshRange(Display.Mode mode) {
246         float refreshRate = mode.getRefreshRate();
247         // The physical refresh rate must be in the render frame rate range, unless
248         // frame rate override is supported.
249         if (!mSupportsFrameRateOverride) {
250             if (refreshRate < (minRenderFrameRate - FLOAT_TOLERANCE)
251                     || refreshRate > (maxRenderFrameRate + FLOAT_TOLERANCE)) {
252                 if (mLoggingEnabled) {
253                     Slog.w(TAG, "Discarding mode " + mode.getModeId()
254                             + ", outside render rate bounds"
255                             + ": minRenderFrameRate=" + minRenderFrameRate
256                             + ", maxRenderFrameRate=" + maxRenderFrameRate
257                             + ", modeRefreshRate=" + refreshRate);
258                 }
259                 return false;
260             }
261         }
262         return true;
263     }
264 
validateModeRenderRateAchievable(Display.Mode mode)265     private boolean validateModeRenderRateAchievable(Display.Mode mode) {
266         float refreshRate = mode.getRefreshRate();
267         if (!isRenderRateAchievable(refreshRate)) {
268             if (mLoggingEnabled) {
269                 Slog.w(TAG, "Discarding mode " + mode.getModeId()
270                         + ", outside frame rate bounds"
271                         + ": minRenderFrameRate=" + minRenderFrameRate
272                         + ", maxRenderFrameRate=" + maxRenderFrameRate
273                         + ", modePhysicalRefreshRate=" + refreshRate);
274             }
275             return false;
276         }
277         return true;
278     }
279 
validateModeSupported(Display.Mode mode)280     private boolean validateModeSupported(Display.Mode mode) {
281         if (supportedModeIds == null || !mSupportedModesVoteEnabled) {
282             return true;
283         }
284         if (supportedModeIds.contains(mode.getModeId())) {
285             return true;
286         }
287         if (mLoggingEnabled) {
288             Slog.w(TAG, "Discarding mode " + mode.getModeId()
289                     + ", supportedMode not found"
290                     + ": mode.modeId=" + mode.getModeId()
291                     + ", supportedModeIds=" + supportedModeIds);
292         }
293         return false;
294     }
295 
validateModeRejected(Display.Mode mode)296     private boolean validateModeRejected(Display.Mode mode) {
297         if (rejectedModeIds == null) {
298             return true;
299         }
300         if (!rejectedModeIds.contains(mode.getModeId())) {
301             return true;
302         }
303         if (mLoggingEnabled) {
304             Slog.w(TAG, "Discarding mode" + mode.getModeId()
305                     + ", is a rejectedMode"
306                     + ": mode.modeId=" + mode.getModeId()
307                     + ", rejectedModeIds=" + rejectedModeIds);
308         }
309         return false;
310     }
311 
validateRefreshRatesSupported(Display.Mode mode)312     private boolean validateRefreshRatesSupported(Display.Mode mode) {
313         if (supportedRefreshRates == null || !mSupportedModesVoteEnabled) {
314             return true;
315         }
316         for (SupportedRefreshRatesVote.RefreshRates refreshRates : this.supportedRefreshRates) {
317             if (equalsWithinFloatTolerance(mode.getRefreshRate(), refreshRates.mPeakRefreshRate)
318                     && equalsWithinFloatTolerance(mode.getVsyncRate(), refreshRates.mVsyncRate)) {
319                 return true;
320             }
321         }
322         if (mLoggingEnabled) {
323             Slog.w(TAG, "Discarding mode " + mode.getModeId()
324                     + ", supportedRefreshRates not found"
325                     + ": mode.refreshRate=" + mode.getRefreshRate()
326                     + ", mode.vsyncRate=" + mode.getVsyncRate()
327                     + ", supportedRefreshRates=" + supportedRefreshRates);
328         }
329         return false;
330     }
331 
isRenderRateAchievable(float physicalRefreshRate)332     private boolean isRenderRateAchievable(float physicalRefreshRate) {
333         // Check whether the render frame rate range is achievable by the mode's physical
334         // refresh rate, meaning that if a divisor of the physical refresh rate is in range
335         // of the render frame rate.
336         // For example for the render frame rate [50, 70]:
337         //   - 120Hz is in range as we can render at 60hz by skipping every other frame,
338         //     which is within the render rate range
339         //   - 90hz is not in range as none of the even divisors (i.e. 90, 45, 30)
340         //     fall within the acceptable render range.
341         final int divisor =
342                 (int) Math.ceil((physicalRefreshRate / maxRenderFrameRate)
343                         - FLOAT_TOLERANCE);
344         float adjustedPhysicalRefreshRate = physicalRefreshRate / divisor;
345         return adjustedPhysicalRefreshRate >= (minRenderFrameRate - FLOAT_TOLERANCE);
346     }
347 
isValid()348     private boolean isValid() {
349         if (minRenderFrameRate > maxRenderFrameRate + FLOAT_TOLERANCE) {
350             if (mLoggingEnabled) {
351                 Slog.w(TAG, "Vote summary resulted in empty set (invalid frame rate range)"
352                         + ": minRenderFrameRate=" + minRenderFrameRate
353                         + ", maxRenderFrameRate=" + maxRenderFrameRate);
354             }
355             return false;
356         }
357 
358         if (supportedRefreshRates != null && mSupportedModesVoteEnabled
359                 && supportedRefreshRates.isEmpty()) {
360             if (mLoggingEnabled) {
361                 Slog.w(TAG, "Vote summary resulted in empty set (empty supportedModes)");
362             }
363             return false;
364         }
365 
366         for (Float requestedRefreshRate : requestedRefreshRates) {
367             if (requestedRefreshRate < minRenderFrameRate
368                     || requestedRefreshRate > maxRenderFrameRate) {
369                 if (mLoggingEnabled) {
370                     Slog.w(TAG, "Requested refreshRate is outside frame rate range"
371                             + ": requestedRefreshRates=" + requestedRefreshRates
372                             + ", requestedRefreshRate=" + requestedRefreshRate
373                             + ", minRenderFrameRate=" + minRenderFrameRate
374                             + ", maxRenderFrameRate=" + maxRenderFrameRate);
375                 }
376                 return false;
377             }
378         }
379 
380         return true;
381     }
382 
updateSummaryWithBestAllowedResolution(final Display.Mode[] supportedModes)383     private void updateSummaryWithBestAllowedResolution(final Display.Mode[] supportedModes) {
384         int maxAllowedWidth = width;
385         int maxAllowedHeight = height;
386         width = Vote.INVALID_SIZE;
387         height = Vote.INVALID_SIZE;
388         int maxNumberOfPixels = 0;
389         for (Display.Mode mode : supportedModes) {
390             if (mode.getPhysicalWidth() > maxAllowedWidth
391                     || mode.getPhysicalHeight() > maxAllowedHeight
392                     || mode.getPhysicalWidth() < minWidth
393                     || mode.getPhysicalHeight() < minHeight
394                     || mode.getRefreshRate() < (minPhysicalRefreshRate - FLOAT_TOLERANCE)
395                     || mode.getRefreshRate() > (maxPhysicalRefreshRate + FLOAT_TOLERANCE)
396             ) {
397                 continue;
398             }
399 
400             int numberOfPixels = mode.getPhysicalHeight() * mode.getPhysicalWidth();
401             if (numberOfPixels > maxNumberOfPixels || (mode.getPhysicalWidth() == maxAllowedWidth
402                     && mode.getPhysicalHeight() == maxAllowedHeight)) {
403                 maxNumberOfPixels = numberOfPixels;
404                 width = mode.getPhysicalWidth();
405                 height = mode.getPhysicalHeight();
406             }
407         }
408     }
409 
reset()410     private void reset() {
411         minPhysicalRefreshRate = 0f;
412         maxPhysicalRefreshRate = Float.POSITIVE_INFINITY;
413         minRenderFrameRate = 0f;
414         maxRenderFrameRate = Float.POSITIVE_INFINITY;
415         width = Vote.INVALID_SIZE;
416         height = Vote.INVALID_SIZE;
417         minWidth = 0;
418         minHeight = 0;
419         disableRefreshRateSwitching = false;
420         appRequestBaseModeRefreshRate = 0f;
421         requestedRefreshRates.clear();
422         supportedRefreshRates = null;
423         supportedModeIds = null;
424         rejectedModeIds.clear();
425         if (mLoggingEnabled) {
426             Slog.i(TAG, "Summary reset: " + this);
427         }
428     }
429 
equalsWithinFloatTolerance(float a, float b)430     private static boolean equalsWithinFloatTolerance(float a, float b) {
431         return a >= b - FLOAT_TOLERANCE && a <= b + FLOAT_TOLERANCE;
432     }
433 
434     @Override
toString()435     public String toString() {
436         return  "VoteSummary{ minPhysicalRefreshRate=" + minPhysicalRefreshRate
437                 + ", maxPhysicalRefreshRate=" + maxPhysicalRefreshRate
438                 + ", minRenderFrameRate=" + minRenderFrameRate
439                 + ", maxRenderFrameRate=" + maxRenderFrameRate
440                 + ", width=" + width
441                 + ", height=" + height
442                 + ", minWidth=" + minWidth
443                 + ", minHeight=" + minHeight
444                 + ", disableRefreshRateSwitching=" + disableRefreshRateSwitching
445                 + ", appRequestBaseModeRefreshRate=" + appRequestBaseModeRefreshRate
446                 + ", requestRefreshRates=" + requestedRefreshRates
447                 + ", supportedRefreshRates=" + supportedRefreshRates
448                 + ", supportedModeIds=" + supportedModeIds
449                 + ", rejectedModeIds=" + rejectedModeIds
450                 + ", mIsDisplayResolutionRangeVotingEnabled="
451                 + mIsDisplayResolutionRangeVotingEnabled
452                 + ", mSupportedModesVoteEnabled=" + mSupportedModesVoteEnabled
453                 + ", mSupportsFrameRateOverride=" + mSupportsFrameRateOverride + " }";
454     }
455 }
456