drm_hwcomposer: Add blocking SetConfig

Config changes within the same config group are intended to change
the refresh rate and are expected to be seamless, and as such are queued
up to be applied along with other frame updates such as pageflips. These
commits ought not to result in a full modeset.

For config changes that go between config groups, there might be visible
artifacts (modesets).

Introduce HwcDisplay::SetConfig to implement support for changing
between config groups. Config changes that are expected to be seamless
and not introduce jank or other visual artifacts should continue to go
through HwcDisplay::QueueConfig.

Since there may not be an appropriate client buffer available for the
initial SetConfig, allocate a CPU-writable buffer for scanout that can
be used for the first commit. The buffer can be destroyed after the
next frame is presented.

Change-Id: I48a87a070130dd2328f415719032486d9659c5b6
Signed-off-by: Drew Davenport <ddavenport@google.com>
diff --git a/hwc2_device/HwcDisplay.cpp b/hwc2_device/HwcDisplay.cpp
index 159314c..43564d4 100644
--- a/hwc2_device/HwcDisplay.cpp
+++ b/hwc2_device/HwcDisplay.cpp
@@ -21,6 +21,11 @@
 
 #include <cinttypes>
 
+#include <hardware/gralloc.h>
+#include <ui/GraphicBufferAllocator.h>
+#include <ui/GraphicBufferMapper.h>
+#include <ui/PixelFormat.h>
+
 #include "backend/Backend.h"
 #include "backend/BackendManager.h"
 #include "bufferinfo/BufferInfoGetter.h"
@@ -35,6 +40,71 @@
 
 namespace android {
 
+namespace {
+// Allocate a black buffer that can be used for an initial modeset when there.
+// is no appropriate client buffer available to be used.
+// Caller must free the returned buffer with GraphicBufferAllocator::free.
+auto GetModesetBuffer(uint32_t width, uint32_t height) -> buffer_handle_t {
+  constexpr PixelFormat format = PIXEL_FORMAT_RGBA_8888;
+  constexpr uint64_t usage = GRALLOC_USAGE_SW_READ_OFTEN |
+                             GRALLOC_USAGE_SW_WRITE_OFTEN |
+                             GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_FB;
+
+  constexpr uint32_t layer_count = 1;
+  const std::string name = "drm-hwcomposer";
+
+  buffer_handle_t handle = nullptr;
+  uint32_t stride = 0;
+  status_t status = GraphicBufferAllocator::get().allocate(width, height,
+                                                           format, layer_count,
+                                                           usage, &handle,
+                                                           &stride, name);
+  if (status != OK) {
+    ALOGE("Failed to allocate modeset buffer.");
+    return nullptr;
+  }
+
+  void *data = nullptr;
+  Rect bounds = {0, 0, static_cast<int32_t>(width),
+                 static_cast<int32_t>(height)};
+  status = GraphicBufferMapper::get().lock(handle, usage, bounds, &data);
+  if (status != OK) {
+    ALOGE("Failed to map modeset buffer.");
+    GraphicBufferAllocator::get().free(handle);
+    return nullptr;
+  }
+
+  // Cast one of the multiplicands to ensure that the multiplication happens
+  // in a wider type (size_t).
+  const size_t buffer_size = static_cast<size_t>(height) * stride *
+                             bytesPerPixel(format);
+  memset(data, 0, buffer_size);
+  status = GraphicBufferMapper::get().unlock(handle);
+  ALOGW_IF(status != OK, "Failed to unmap buffer.");
+  return handle;
+}
+
+auto GetModesetLayerProperties(buffer_handle_t buffer, uint32_t width,
+                               uint32_t height) -> HwcLayer::LayerProperties {
+  HwcLayer::LayerProperties properties;
+  properties.buffer = {.buffer_handle = buffer, .acquire_fence = {}};
+  properties.display_frame = {
+      .left = 0,
+      .top = 0,
+      .right = int(width),
+      .bottom = int(height),
+  };
+  properties.source_crop = (hwc_frect_t){
+      .left = 0.0F,
+      .top = 0.0F,
+      .right = static_cast<float>(width),
+      .bottom = static_cast<float>(height),
+  };
+  properties.blend_mode = BufferBlendMode::kNone;
+  return properties;
+}
+}  // namespace
+
 std::string HwcDisplay::DumpDelta(HwcDisplay::Stats delta) {
   if (delta.total_pixops_ == 0)
     return "No stats yet";
@@ -113,6 +183,57 @@
   return GetConfig(staged_mode_config_id_.value_or(configs_.active_config_id));
 }
 
+HwcDisplay::ConfigError HwcDisplay::SetConfig(hwc2_config_t config) {
+  const HwcDisplayConfig *new_config = GetConfig(config);
+  if (new_config == nullptr) {
+    ALOGE("Could not find active mode for %u", config);
+    return ConfigError::kBadConfig;
+  }
+
+  const HwcDisplayConfig *current_config = GetCurrentConfig();
+
+  const uint32_t width = new_config->mode.GetRawMode().hdisplay;
+  const uint32_t height = new_config->mode.GetRawMode().hdisplay;
+
+  std::optional<LayerData> modeset_layer_data;
+  // If a client layer has already been provided, and its size matches the
+  // new config, use it for the modeset.
+  if (client_layer_.IsLayerUsableAsDevice() && current_config &&
+      current_config->mode.GetRawMode().hdisplay == width &&
+      current_config->mode.GetRawMode().vdisplay == height) {
+    ALOGV("Use existing client_layer for blocking config.");
+    modeset_layer_data = client_layer_.GetLayerData();
+  } else {
+    ALOGV("Allocate modeset buffer.");
+    buffer_handle_t modeset_buffer = GetModesetBuffer(width, height);
+    if (modeset_buffer != nullptr) {
+      auto modeset_layer = std::make_unique<HwcLayer>(this);
+      modeset_layer->SetLayerProperties(
+          GetModesetLayerProperties(modeset_buffer, width, height));
+      modeset_layer->PopulateLayerData();
+      modeset_layer_data = modeset_layer->GetLayerData();
+      GraphicBufferAllocator::get().free(modeset_buffer);
+    }
+  }
+
+  ALOGV("Create modeset commit.");
+  // Create atomic commit args for a blocking modeset. There's no need to do a
+  // separate test commit, since the commit does a test anyways.
+  AtomicCommitArgs commit_args = CreateModesetCommit(new_config,
+                                                     modeset_layer_data);
+  commit_args.blocking = true;
+  int ret = GetPipe().atomic_state_manager->ExecuteAtomicCommit(commit_args);
+
+  if (ret) {
+    ALOGE("Blocking config failed: %d", ret);
+    return HwcDisplay::ConfigError::kBadConfig;
+  }
+
+  ALOGV("Blocking config succeeded.");
+  configs_.active_config_id = config;
+  return ConfigError::kNone;
+}
+
 auto HwcDisplay::QueueConfig(hwc2_config_t config, int64_t desired_time,
                              bool seamless, QueuedConfigTiming *out_timing)
     -> ConfigError {
@@ -520,6 +641,34 @@
   return HWC2::Error::None;
 }
 
+AtomicCommitArgs HwcDisplay::CreateModesetCommit(
+    const HwcDisplayConfig *config,
+    const std::optional<LayerData> &modeset_layer) {
+  AtomicCommitArgs args{};
+
+  args.color_matrix = color_matrix_;
+  args.content_type = content_type_;
+  args.colorspace = colorspace_;
+
+  std::vector<LayerData> composition_layers;
+  if (modeset_layer) {
+    composition_layers.emplace_back(modeset_layer.value());
+  }
+
+  if (composition_layers.empty()) {
+    ALOGW("Attempting to create a modeset commit without a layer.");
+  }
+
+  args.display_mode = config->mode;
+  args.active = true;
+  args.composition = DrmKmsPlan::CreateDrmKmsPlan(GetPipe(),
+                                                  std::move(
+                                                      composition_layers));
+  ALOGW_IF(!args.composition, "No composition for blocking modeset");
+
+  return args;
+}
+
 HWC2::Error HwcDisplay::CreateComposition(AtomicCommitArgs &a_args) {
   if (IsInHeadlessMode()) {
     ALOGE("%s: Display is in headless mode, should never reach here", __func__);
@@ -673,6 +822,7 @@
   color_matrix_ = {};
 
   ++frame_no_;
+
   return HWC2::Error::None;
 }
 
diff --git a/hwc2_device/HwcDisplay.h b/hwc2_device/HwcDisplay.h
index 7cc1bee..ecca514 100644
--- a/hwc2_device/HwcDisplay.h
+++ b/hwc2_device/HwcDisplay.h
@@ -74,6 +74,11 @@
   // is queued up to take effect in the future.
   auto GetLastRequestedConfig() const -> const HwcDisplayConfig *;
 
+  // Set a config synchronously. If the requested config fails to be committed,
+  // this will return with an error. Otherwise, the config will have been
+  // committed to the kernel on successful return.
+  ConfigError SetConfig(hwc2_config_t config);
+
   // Queue a configuration change to take effect in the future.
   auto QueueConfig(hwc2_config_t config, int64_t desired_time, bool seamless,
                    QueuedConfigTiming *out_timing) -> ConfigError;
@@ -223,6 +228,10 @@
   auto getDisplayPhysicalOrientation() -> std::optional<PanelOrientation>;
 
  private:
+  AtomicCommitArgs CreateModesetCommit(
+      const HwcDisplayConfig *config,
+      const std::optional<LayerData> &modeset_layer);
+
   HwcDisplayConfigs configs_;
 
   DrmHwc *const hwc_;
diff --git a/hwc3/ComposerClient.cpp b/hwc3/ComposerClient.cpp
index dca118f..47ffc7e 100644
--- a/hwc3/ComposerClient.cpp
+++ b/hwc3/ComposerClient.cpp
@@ -1081,13 +1081,36 @@
     return ToBinderStatus(hwc3::Error::kBadDisplay);
   }
 
-  ::QueuedConfigTiming timing{};
-  HwcDisplay::ConfigError
-      result = display->QueueConfig(config, constraints.desiredTimeNanos,
-                                    constraints.seamlessRequired, &timing);
-  timeline->newVsyncAppliedTimeNanos = timing.new_vsync_time_ns;
-  timeline->refreshTimeNanos = timing.refresh_time_ns;
-  timeline->refreshRequired = true;
+  if (constraints.seamlessRequired) {
+    return ToBinderStatus(hwc3::Error::kSeamlessNotAllowed);
+  }
+
+  const bool future_config = constraints.desiredTimeNanos >
+                             ::android::ResourceManager::GetTimeMonotonicNs();
+  const HwcDisplayConfig* current_config = display->GetCurrentConfig();
+  const HwcDisplayConfig* next_config = display->GetConfig(config);
+  const bool same_config_group = current_config != nullptr &&
+                                 next_config != nullptr &&
+                                 current_config->group_id ==
+                                     next_config->group_id;
+  // If the contraints dictate that this is to be applied in the future, it
+  // must be queued. If the new config is in the same config group as the
+  // current one, then queue it to reduce jank.
+  HwcDisplay::ConfigError result{};
+  if (future_config || same_config_group) {
+    QueuedConfigTiming timing = {};
+    result = display->QueueConfig(config, constraints.desiredTimeNanos,
+                                  constraints.seamlessRequired, &timing);
+    timeline->newVsyncAppliedTimeNanos = timing.new_vsync_time_ns;
+    timeline->refreshTimeNanos = timing.refresh_time_ns;
+    timeline->refreshRequired = true;
+  } else {
+    // Fall back to a blocking commit, which may modeset.
+    result = display->SetConfig(config);
+    timeline->newVsyncAppliedTimeNanos = ::android::ResourceManager::
+        GetTimeMonotonicNs();
+    timeline->refreshRequired = false;
+  }
 
   switch (result) {
     case HwcDisplay::ConfigError::kBadConfig: