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