OSDN Git Service

drm_hwcomposer: Rework display modes handling
authorRoman Stratiienko <roman.o.stratiienko@globallogic.com>
Tue, 16 Nov 2021 16:23:18 +0000 (18:23 +0200)
committerRoman Stratiienko <roman.o.stratiienko@globallogic.com>
Thu, 2 Dec 2021 12:35:08 +0000 (14:35 +0200)
Android likes to adapt display output frequency to match window context
frequency. Unfortunately platform code has some limitations, therefore
hwcomposer HAL should be careful with reporting supported display modes.

Known platform limitations:
1: Framework doesn't distinguish between interlaced/progressive modes.
2. Framework will not switch display frequency in case margin in FPS rate
   is very small (<1FPS or so). Not a big issue, but that is causing
   some CTS tests to fail.

In addition to that VRR technology (or seamless mode switching) require
hwcomposer to group modes which tells the framework that seamless mode
configuration change is supported within a group of display modes.

By this commit do the following:
1. Group modes by the resolution:
   E.g.

    Group 1:
    1024x768i@60
    1024x768i@90
    1024x768@50
    1024x768@50.1

    Group 2:
    1920x1080@60
    1920x1080@24.3
    1920x1080i@60
    1920x1080i@120

2. Disable modes in a way that each group keeps only interlaced or proressive
   modes enabled. In case KMS reported preferred mode is interlaced - prefer
   interlaced for the whole group, otherwise prefer progressive.

3. Disable mode in case different mode in the same group has similar frequency
   with delta less than 1FPS.

4. Report only modes which remain enabled to the framework.

Test: atest CtsGraphicsTestCases

Signed-off-by: Roman Stratiienko <roman.o.stratiienko@globallogic.com>
DrmHwcTwo.cpp
DrmHwcTwo.h
drm/DrmConnector.cpp
drm/DrmConnector.h
drm/DrmMode.cpp
drm/DrmMode.h

index 8b8068c..6faec1c 100644 (file)
@@ -349,9 +349,9 @@ HWC2::Error DrmHwcTwo::HwcDisplay::ChosePreferredConfig() {
   uint32_t num_configs = 0;
   HWC2::Error err = GetDisplayConfigs(&num_configs, nullptr);
   if (err != HWC2::Error::None || !num_configs)
-    return err;
+    return HWC2::Error::BadDisplay;
 
-  return SetActiveConfig(connector_->get_preferred_mode_id());
+  return SetActiveConfig(preferred_config_id_);
 }
 
 HWC2::Error DrmHwcTwo::HwcDisplay::AcceptDisplayChanges() {
@@ -378,13 +378,13 @@ HWC2::Error DrmHwcTwo::HwcDisplay::DestroyLayer(hwc2_layer_t layer) {
   return HWC2::Error::None;
 }
 
-HWC2::Error DrmHwcTwo::HwcDisplay::GetActiveConfig(hwc2_config_t *config) {
+HWC2::Error DrmHwcTwo::HwcDisplay::GetActiveConfig(
+    hwc2_config_t *config) const {
   supported(__func__);
-  DrmMode const &mode = connector_->active_mode();
-  if (mode.id() == 0)
+  if (hwc_configs_.count(active_config_id_) == 0)
     return HWC2::Error::BadConfig;
 
-  *config = mode.id();
+  *config = active_config_id_;
   return HWC2::Error::None;
 }
 
@@ -443,46 +443,47 @@ HWC2::Error DrmHwcTwo::HwcDisplay::GetDisplayAttribute(hwc2_config_t config,
                                                        int32_t attribute_in,
                                                        int32_t *value) {
   supported(__func__);
-  auto mode = std::find_if(connector_->modes().begin(),
-                           connector_->modes().end(),
-                           [config](DrmMode const &m) {
-                             return m.id() == config;
-                           });
-  if (mode == connector_->modes().end()) {
-    ALOGE("Could not find active mode for %d", config);
+  int conf = static_cast<int>(config);
+
+  if (hwc_configs_.count(conf) == 0) {
+    ALOGE("Could not find active mode for %d", conf);
     return HWC2::Error::BadConfig;
   }
 
+  auto &hwc_config = hwc_configs_[conf];
+
   static const int32_t kUmPerInch = 25400;
   uint32_t mm_width = connector_->mm_width();
   uint32_t mm_height = connector_->mm_height();
   auto attribute = static_cast<HWC2::Attribute>(attribute_in);
   switch (attribute) {
     case HWC2::Attribute::Width:
-      *value = static_cast<int>(mode->h_display());
+      *value = static_cast<int>(hwc_config.mode.h_display());
       break;
     case HWC2::Attribute::Height:
-      *value = static_cast<int>(mode->v_display());
+      *value = static_cast<int>(hwc_config.mode.v_display());
       break;
     case HWC2::Attribute::VsyncPeriod:
       // in nanoseconds
-      *value = static_cast<int>(1E9 / mode->v_refresh());
+      *value = static_cast<int>(1E9 / hwc_config.mode.v_refresh());
       break;
     case HWC2::Attribute::DpiX:
       // Dots per 1000 inches
-      *value = mm_width
-                   ? static_cast<int>(mode->h_display() * kUmPerInch / mm_width)
-                   : -1;
+      *value = mm_width ? static_cast<int>(hwc_config.mode.h_display() *
+                                           kUmPerInch / mm_width)
+                        : -1;
       break;
     case HWC2::Attribute::DpiY:
       // Dots per 1000 inches
-      *value = mm_height ? static_cast<int>(mode->v_display() * kUmPerInch /
-                                            mm_height)
+      *value = mm_height ? static_cast<int>(hwc_config.mode.v_display() *
+                                            kUmPerInch / mm_height)
                          : -1;
       break;
 #if PLATFORM_SDK_VERSION > 29
     case HWC2::Attribute::ConfigGroup:
-      *value = 0; /* TODO: Add support for config groups */
+      /* Dispite ConfigGroup is a part of HWC2.4 API, framework
+       * able to request it even if service @2.1 is used */
+      *value = hwc_config.group_id;
       break;
 #endif
     default:
@@ -506,79 +507,149 @@ HWC2::Error DrmHwcTwo::HwcDisplay::GetDisplayConfigs(uint32_t *num_configs,
       ALOGE("Failed to update display modes %d", ret);
       return HWC2::Error::BadDisplay;
     }
-  }
 
-  // Since the upper layers only look at vactive/hactive/refresh, height and
-  // width, it doesn't differentiate interlaced from progressive and other
-  // similar modes. Depending on the order of modes we return to SF, it could
-  // end up choosing a suboptimal configuration and dropping the preferred
-  // mode. To workaround this, don't offer interlaced modes to SF if there is
-  // at least one non-interlaced alternative and only offer a single WxH@R
-  // mode with at least the prefered mode from in DrmConnector::UpdateModes()
-
-  // TODO(nobody): Remove the following block of code until AOSP handles all
-  // modes
-  std::vector<DrmMode> sel_modes;
-
-  // Add the preferred mode first to be sure it's not dropped
-  auto mode = std::find_if(connector_->modes().begin(),
-                           connector_->modes().end(), [&](DrmMode const &m) {
-                             return m.id() ==
-                                    connector_->get_preferred_mode_id();
-                           });
-  if (mode != connector_->modes().end())
-    sel_modes.push_back(*mode);
-
-  // Add the active mode if different from preferred mode
-  if (connector_->active_mode().id() != connector_->get_preferred_mode_id())
-    sel_modes.push_back(connector_->active_mode());
-
-  // Cycle over the modes and filter out "similar" modes, keeping only the
-  // first ones in the order given by DRM (from CEA ids and timings order)
-  for (const DrmMode &mode : connector_->modes()) {
-    // TODO(nobody): Remove this when 3D Attributes are in AOSP
-    if (mode.flags() & DRM_MODE_FLAG_3D_MASK)
-      continue;
+    hwc_configs_.clear();
+    preferred_config_id_ = 0;
+    int preferred_config_group_id_ = 0;
+
+    if (connector_->modes().empty()) {
+      ALOGE("No modes reported by KMS");
+      return HWC2::Error::BadDisplay;
+    }
 
-    // TODO(nobody): Remove this when the Interlaced attribute is in AOSP
-    if (mode.flags() & DRM_MODE_FLAG_INTERLACE) {
-      auto m = std::find_if(connector_->modes().begin(),
-                            connector_->modes().end(),
-                            [&mode](DrmMode const &m) {
-                              return !(m.flags() & DRM_MODE_FLAG_INTERLACE) &&
-                                     m.h_display() == mode.h_display() &&
-                                     m.v_display() == mode.v_display();
-                            });
-      if (m == connector_->modes().end())
-        sel_modes.push_back(mode);
+    int last_config_id = 1;
+    int last_group_id = 1;
+
+    /* Group modes */
+    for (const auto &mode : connector_->modes()) {
+      /* Find group for the new mode or create new group */
+      int group_found = 0;
+      for (auto &hwc_config : hwc_configs_) {
+        if (mode.h_display() == hwc_config.second.mode.h_display() &&
+            mode.v_display() == hwc_config.second.mode.v_display()) {
+          group_found = hwc_config.second.group_id;
+        }
+      }
+      if (group_found == 0) {
+        group_found = last_group_id++;
+      }
 
-      continue;
+      bool disabled = false;
+      if (mode.flags() & DRM_MODE_FLAG_3D_MASK) {
+        ALOGI("Disabling display mode %s (Modes with 3D flag aren't supported)",
+              mode.name().c_str());
+        disabled = true;
+      }
+
+      /* Add config */
+      hwc_configs_[last_config_id] = {
+          .id = last_config_id,
+          .group_id = group_found,
+          .mode = mode,
+          .disabled = disabled,
+      };
+
+      /* Chwck if the mode is preferred */
+      if ((mode.type() & DRM_MODE_TYPE_PREFERRED) != 0 &&
+          preferred_config_id_ == 0) {
+        preferred_config_id_ = last_config_id;
+        preferred_config_group_id_ = group_found;
+      }
+
+      last_config_id++;
     }
 
-    // Search for a similar WxH@R mode in the filtered list and drop it if
-    // another mode with the same WxH@R has already been selected
-    // TODO(nobody): Remove this when AOSP handles duplicates modes
-    auto m = std::find_if(sel_modes.begin(), sel_modes.end(),
-                          [&mode](DrmMode const &m) {
-                            return m.h_display() == mode.h_display() &&
-                                   m.v_display() == mode.v_display() &&
-                                   m.v_refresh() == mode.v_refresh();
-                          });
-    if (m == sel_modes.end())
-      sel_modes.push_back(mode);
-  }
+    /* We must have preferred mode. Set first mode as preferred
+     * in case KMS haven't reported anything. */
+    if (preferred_config_id_ == 0) {
+      preferred_config_id_ = 1;
+      preferred_config_group_id_ = 1;
+    }
 
-  auto num_modes = static_cast<uint32_t>(sel_modes.size());
-  if (!configs) {
-    *num_configs = num_modes;
-    return HWC2::Error::None;
+    for (int group = 1; group < last_group_id; group++) {
+      bool has_interlaced = false;
+      bool has_progressive = false;
+      for (auto &hwc_config : hwc_configs_) {
+        if (hwc_config.second.group_id != group || hwc_config.second.disabled) {
+          continue;
+        }
+
+        if (hwc_config.second.IsInterlaced()) {
+          has_interlaced = true;
+        } else {
+          has_progressive = true;
+        }
+      }
+
+      bool has_both = has_interlaced && has_progressive;
+      if (!has_both) {
+        continue;
+      }
+
+      bool group_contains_preferred_interlaced = false;
+      if (group == preferred_config_group_id_ &&
+          hwc_configs_[preferred_config_id_].IsInterlaced()) {
+        group_contains_preferred_interlaced = true;
+      }
+
+      for (auto &hwc_config : hwc_configs_) {
+        if (hwc_config.second.group_id != group || hwc_config.second.disabled) {
+          continue;
+        }
+
+        bool disable = group_contains_preferred_interlaced
+                           ? !hwc_config.second.IsInterlaced()
+                           : hwc_config.second.IsInterlaced();
+
+        if (disable) {
+          ALOGI(
+              "Group %i: Disabling display mode %s (This group should consist "
+              "of %s modes)",
+              group, hwc_config.second.mode.name().c_str(),
+              group_contains_preferred_interlaced ? "interlaced"
+                                                  : "progressive");
+
+          hwc_config.second.disabled = true;
+        }
+      }
+    }
+
+    /* Group should not contain 2 modes with FPS delta less than ~1HZ
+     * otherwise android.graphics.cts.SetFrameRateTest CTS will fail
+     */
+    for (int m1 = 1; m1 < last_config_id; m1++) {
+      for (int m2 = 1; m2 < last_config_id; m2++) {
+        if (m1 != m2 &&
+            hwc_configs_[m1].group_id == hwc_configs_[m2].group_id &&
+            !hwc_configs_[m1].disabled && !hwc_configs_[m2].disabled &&
+            fabsf(hwc_configs_[m1].mode.v_refresh() -
+                  hwc_configs_[m2].mode.v_refresh()) < 1.0) {
+          ALOGI(
+              "Group %i: Disabling display mode %s (Refresh rate value is "
+              "too close to existing mode %s)",
+              hwc_configs_[m2].group_id, hwc_configs_[m2].mode.name().c_str(),
+              hwc_configs_[m1].mode.name().c_str());
+
+          hwc_configs_[m2].disabled = true;
+        }
+      }
+    }
   }
 
   uint32_t idx = 0;
-  for (const DrmMode &mode : sel_modes) {
-    if (idx >= *num_configs)
-      break;
-    configs[idx++] = mode.id();
+  for (auto &hwc_config : hwc_configs_) {
+    if (hwc_config.second.disabled) {
+      continue;
+    }
+
+    if (configs != nullptr) {
+      if (idx >= *num_configs) {
+        break;
+      }
+      configs[idx] = hwc_config.second.id;
+    }
+
+    idx++;
   }
   *num_configs = idx;
   return HWC2::Error::None;
@@ -768,18 +839,18 @@ HWC2::Error DrmHwcTwo::HwcDisplay::PresentDisplay(int32_t *present_fence) {
 
 HWC2::Error DrmHwcTwo::HwcDisplay::SetActiveConfig(hwc2_config_t config) {
   supported(__func__);
-  auto mode = std::find_if(connector_->modes().begin(),
-                           connector_->modes().end(),
-                           [config](DrmMode const &m) {
-                             return m.id() == config;
-                           });
-  if (mode == connector_->modes().end()) {
-    ALOGE("Could not find active mode for %d", config);
+
+  int conf = static_cast<int>(config);
+
+  if (hwc_configs_.count(conf) == 0) {
+    ALOGE("Could not find active mode for %d", conf);
     return HWC2::Error::BadConfig;
   }
 
+  auto &mode = hwc_configs_[conf].mode;
+
   AtomicCommitArgs a_args = {
-      .display_mode = *mode,
+      .display_mode = mode,
       .clear_active_composition = true,
   };
 
@@ -789,13 +860,13 @@ HWC2::Error DrmHwcTwo::HwcDisplay::SetActiveConfig(hwc2_config_t config) {
     return HWC2::Error::BadConfig;
   }
 
-  connector_->set_active_mode(*mode);
+  active_config_id_ = conf;
 
   // Setup the client layer's dimensions
   hwc_rect_t display_frame = {.left = 0,
                               .top = 0,
-                              .right = static_cast<int>(mode->h_display()),
-                              .bottom = static_cast<int>(mode->v_display())};
+                              .right = static_cast<int>(mode.h_display()),
+                              .bottom = static_cast<int>(mode.v_display())};
   client_layer_.SetLayerDisplayFrame(display_frame);
 
   return HWC2::Error::None;
@@ -938,12 +1009,8 @@ HWC2::Error DrmHwcTwo::HwcDisplay::GetDisplayConnectionType(uint32_t *outType) {
 HWC2::Error DrmHwcTwo::HwcDisplay::GetDisplayVsyncPeriod(
     hwc2_vsync_period_t *outVsyncPeriod /* ns */) {
   supported(__func__);
-  DrmMode const &mode = connector_->active_mode();
-  if (mode.id() == 0)
-    return HWC2::Error::BadConfig;
-
-  *outVsyncPeriod = static_cast<hwc2_vsync_period_t>(1E9 / mode.v_refresh());
-  return HWC2::Error::None;
+  return GetDisplayAttribute(active_config_id_, HWC2_ATTRIBUTE_VSYNC_PERIOD,
+                             (int32_t *)(outVsyncPeriod));
 }
 
 HWC2::Error DrmHwcTwo::HwcDisplay::SetActiveConfigWithConstraints(
index ed547c0..7fd4fbd 100644 (file)
@@ -160,7 +160,7 @@ class DrmHwcTwo : public hwc2_device_t {
     HWC2::Error AcceptDisplayChanges();
     HWC2::Error CreateLayer(hwc2_layer_t *layer);
     HWC2::Error DestroyLayer(hwc2_layer_t layer);
-    HWC2::Error GetActiveConfig(hwc2_config_t *config);
+    HWC2::Error GetActiveConfig(hwc2_config_t *config) const;
     HWC2::Error GetChangedCompositionTypes(uint32_t *num_elements,
                                            hwc2_layer_t *layers,
                                            int32_t *types);
@@ -250,6 +250,22 @@ class DrmHwcTwo : public hwc2_device_t {
       uint32_t frames_flattened_ = 0;
     };
 
+    struct HwcDisplayConfig {
+      int id{};
+      int group_id{};
+      DrmMode mode;
+      bool disabled{};
+
+      bool IsInterlaced() {
+        return (mode.flags() & DRM_MODE_FLAG_INTERLACE) != 0;
+      }
+    };
+
+    std::map<int /*config_id*/, struct HwcDisplayConfig> hwc_configs_;
+
+    int active_config_id_ = 0;
+    int preferred_config_id_ = 0;
+
     const Backend *backend() const {
       return backend_.get();
     }
index b3179c7..5b3c697 100644 (file)
@@ -159,43 +159,27 @@ std::string DrmConnector::name() const {
 }
 
 int DrmConnector::UpdateModes() {
-  int fd = drm_->fd();
-
-  drmModeConnectorPtr c = drmModeGetConnector(fd, id_);
+  drmModeConnectorPtr c = drmModeGetConnector(drm_->fd(), id_);
   if (!c) {
     ALOGE("Failed to get connector %d", id_);
     return -ENODEV;
   }
 
-  state_ = c->connection;
-
-  bool preferred_mode_found = false;
-  std::vector<DrmMode> new_modes;
+  modes_.clear();
   for (int i = 0; i < c->count_modes; ++i) {
     bool exists = false;
     for (const DrmMode &mode : modes_) {
       if (mode == c->modes[i]) {
-        new_modes.push_back(mode);
         exists = true;
         break;
       }
     }
+
     if (!exists) {
-      DrmMode m(&c->modes[i]);
-      m.set_id(drm_->next_mode_id());
-      new_modes.push_back(m);
+      modes_.emplace_back(DrmMode(&c->modes[i]));
     }
-    // Use only the first DRM_MODE_TYPE_PREFERRED mode found
-    if (!preferred_mode_found &&
-        (new_modes.back().type() & DRM_MODE_TYPE_PREFERRED)) {
-      preferred_mode_id_ = new_modes.back().id();
-      preferred_mode_found = true;
-    }
-  }
-  modes_.swap(new_modes);
-  if (!preferred_mode_found && !modes_.empty()) {
-    preferred_mode_id_ = modes_[0].id();
   }
+
   return 0;
 }
 
index e2789ea..f2305aa 100644 (file)
@@ -82,10 +82,6 @@ class DrmConnector {
   uint32_t mm_width() const;
   uint32_t mm_height() const;
 
-  uint32_t get_preferred_mode_id() const {
-    return preferred_mode_id_;
-  }
-
  private:
   DrmDevice *drm_;
 
@@ -111,8 +107,6 @@ class DrmConnector {
   DrmProperty writeback_out_fence_;
 
   std::vector<DrmEncoder *> possible_encoders_;
-
-  uint32_t preferred_mode_id_{};
 };
 }  // namespace android
 
index 971c327..1c8bd0f 100644 (file)
@@ -49,14 +49,6 @@ bool DrmMode::operator==(const drmModeModeInfo &m) const {
          v_scan_ == m.vscan && flags_ == m.flags && type_ == m.type;
 }
 
-uint32_t DrmMode::id() const {
-  return id_;
-}
-
-void DrmMode::set_id(uint32_t id) {
-  id_ = id;
-}
-
 uint32_t DrmMode::clock() const {
   return clock_;
 }
@@ -115,7 +107,7 @@ uint32_t DrmMode::type() const {
 }
 
 std::string DrmMode::name() const {
-  return name_;
+  return name_ + "@" + std::to_string(v_refresh());
 }
 
 auto DrmMode::CreateModeBlob(const DrmDevice &drm)
index 0974b5a..41b9e15 100644 (file)
 #ifndef ANDROID_DRM_MODE_H_
 #define ANDROID_DRM_MODE_H_
 
-#include <stdint.h>
+#include <stdio.h>
 #include <xf86drmMode.h>
 
+#include <cstdint>
 #include <string>
 
 #include "DrmUnique.h"
@@ -35,9 +36,6 @@ class DrmMode {
 
   bool operator==(const drmModeModeInfo &m) const;
 
-  uint32_t id() const;
-  void set_id(uint32_t id);
-
   uint32_t clock() const;
 
   uint16_t h_display() const;
@@ -61,8 +59,6 @@ class DrmMode {
   auto CreateModeBlob(const DrmDevice &drm) -> DrmModeUserPropertyBlobUnique;
 
  private:
-  uint32_t id_ = 0;
-
   uint32_t clock_ = 0;
 
   uint16_t h_display_ = 0;