drm_hwcomposer: add scene flattening

Flattening of a scene is triggered if it doesn't change for a while.
As of now there is a separate thread which triggers flattening if the
scene did not change in last 60 vsyncs.

There are two options for flattening a scene:
* Serial, by using a writeback connector attached to the same crtc as
  the one driving the display. This happens only if possible_clones
  mask reports that the display encoder and writeback encoder could
  work simultaneously.
The steps for achieving this are:
1. Build a commit that enables writeback connector, we don't need to
   build a commit that contains the entire active_composition, just
   set the writeback specific properties a let the kernel duplicate
   the rest of the state.
2. Commit and wait for writeback_fence to fire.
3. Setup a composition with just one plane enabled.
4. Apply the composition if a new one has not been applied meanwhile.

* Concurrent, by comitting the scene to a new unused crtc (state !=
  DRM_MODE_CONNECTED) and getting the result back through a writeback
  connector.
The steps for achieving this are:
1. Copy layers from active composition.
2. Plan layers of copy on the unused crtc. This is realized by using a
   newly created DrmDisplayCompositor object.
3. Commit copy to the unsed crtc and get the result as a drmhwclayer.
4. Setup a composition with just one plane enabled. Re-importing the
   buffers might be needed since we might have been using a different
   dri node.
5. Apply the composition if a new one has not been applied while doing
   the flattening

Signed-off-by: Alexandru Gheorghe <alexandru-cosmin.gheorghe@arm.com>
diff --git a/drmdisplaycompositor.cpp b/drmdisplaycompositor.cpp
index 05832cb..7427729 100644
--- a/drmdisplaycompositor.cpp
+++ b/drmdisplaycompositor.cpp
@@ -36,8 +36,24 @@
 #include "drmdevice.h"
 #include "drmplane.h"
 
+static const uint32_t kWaitWritebackFence = 100;  // ms
+
 namespace android {
 
+class CompositorVsyncCallback : public VsyncCallback {
+ public:
+  CompositorVsyncCallback(DrmDisplayCompositor *compositor)
+      : compositor_(compositor) {
+  }
+
+  void Callback(int display, int64_t timestamp) {
+    compositor_->Vsync(display, timestamp);
+  }
+
+ private:
+  DrmDisplayCompositor *compositor_;
+};
+
 DrmDisplayCompositor::DrmDisplayCompositor()
     : resource_manager_(NULL),
       display_(-1),
@@ -45,7 +61,9 @@
       active_(false),
       use_hw_overlays_(true),
       dump_frames_composited_(0),
-      dump_last_timestamp_ns_(0) {
+      dump_last_timestamp_ns_(0),
+      flatten_countdown_(FLATTEN_COUNTDOWN_INIT),
+      writeback_fence_(-1) {
   struct timespec ts;
   if (clock_gettime(CLOCK_MONOTONIC, &ts))
     return;
@@ -56,6 +74,7 @@
   if (!initialized_)
     return;
 
+  vsync_worker_.Exit();
   int ret = pthread_mutex_lock(&lock_);
   if (ret)
     ALOGE("Failed to acquire compositor lock %d", ret);
@@ -77,7 +96,8 @@
 int DrmDisplayCompositor::Init(ResourceManager *resource_manager, int display) {
   resource_manager_ = resource_manager;
   display_ = display;
-  if (!resource_manager_->GetDrmDevice(display)) {
+  DrmDevice *drm = resource_manager_->GetDrmDevice(display);
+  if (!drm) {
     ALOGE("Could not find drmdevice for display");
     return -EINVAL;
   }
@@ -86,6 +106,11 @@
     ALOGE("Failed to initialize drm compositor lock %d\n", ret);
     return ret;
   }
+  planner_ = Planner::CreateInstance(drm);
+
+  vsync_worker_.Init(drm, display_);
+  auto callback = std::make_shared<CompositorVsyncCallback>(this);
+  vsync_worker_.RegisterCallback(callback);
 
   initialized_ = true;
   return 0;
@@ -96,6 +121,28 @@
   return std::unique_ptr<DrmDisplayComposition>(new DrmDisplayComposition());
 }
 
+std::unique_ptr<DrmDisplayComposition>
+DrmDisplayCompositor::CreateInitializedComposition() const {
+  DrmDevice *drm = resource_manager_->GetDrmDevice(display_);
+  DrmCrtc *crtc = drm->GetCrtcForDisplay(display_);
+  if (!crtc) {
+    ALOGE("Failed to find crtc for display = %d", display_);
+    return std::unique_ptr<DrmDisplayComposition>();
+  }
+  std::unique_ptr<DrmDisplayComposition> comp = CreateComposition();
+  std::shared_ptr<Importer> importer = resource_manager_->GetImporter(display_);
+  if (!importer) {
+    ALOGE("Failed to find resources for display = %d", display_);
+    return std::unique_ptr<DrmDisplayComposition>();
+  }
+  int ret = comp->Init(drm, crtc, importer.get(), planner_.get(), 0);
+  if (ret) {
+    ALOGE("Failed to init composition for display = %d", display_);
+    return std::unique_ptr<DrmDisplayComposition>();
+  }
+  return comp;
+}
+
 std::tuple<uint32_t, uint32_t, int>
 DrmDisplayCompositor::GetActiveModeResolution() {
   DrmDevice *drm = resource_manager_->GetDrmDevice(display_);
@@ -144,8 +191,49 @@
   return 0;
 }
 
+int DrmDisplayCompositor::SetupWritebackCommit(drmModeAtomicReqPtr pset,
+                                               uint32_t crtc_id,
+                                               DrmConnector *writeback_conn,
+                                               DrmHwcBuffer *writeback_buffer) {
+  int ret = 0;
+  if (writeback_conn->writeback_fb_id().id() == 0 ||
+      writeback_conn->writeback_out_fence().id() == 0) {
+    ALOGE("Writeback properties don't exit");
+    return -EINVAL;
+  }
+  if ((*writeback_buffer)->fb_id == 0) {
+    ALOGE("Invalid writeback buffer");
+    return -EINVAL;
+  }
+  ret = drmModeAtomicAddProperty(pset, writeback_conn->id(),
+                                 writeback_conn->writeback_fb_id().id(),
+                                 (*writeback_buffer)->fb_id);
+  if (ret < 0) {
+    ALOGE("Failed to add writeback_fb_id");
+    return ret;
+  }
+  ret = drmModeAtomicAddProperty(pset, writeback_conn->id(),
+                                 writeback_conn->writeback_out_fence().id(),
+                                 (uint64_t)&writeback_fence_);
+  if (ret < 0) {
+    ALOGE("Failed to add writeback_out_fence");
+    return ret;
+  }
+
+  ret = drmModeAtomicAddProperty(pset, writeback_conn->id(),
+                                 writeback_conn->crtc_id_property().id(),
+                                 crtc_id);
+  if (ret < 0) {
+    ALOGE("Failed to  attach writeback");
+    return ret;
+  }
+  return 0;
+}
+
 int DrmDisplayCompositor::CommitFrame(DrmDisplayComposition *display_comp,
-                                      bool test_only) {
+                                      bool test_only,
+                                      DrmConnector *writeback_conn,
+                                      DrmHwcBuffer *writeback_buffer) {
   ATRACE_CALL();
 
   int ret = 0;
@@ -173,6 +261,18 @@
     return -ENOMEM;
   }
 
+  if (writeback_buffer != NULL) {
+    if (writeback_conn == NULL) {
+      ALOGE("Invalid arguments requested writeback without writeback conn");
+      return -EINVAL;
+    }
+    ret = SetupWritebackCommit(pset, crtc->id(), writeback_conn,
+                               writeback_buffer);
+    if (ret < 0) {
+      ALOGE("Failed to Setup Writeback Commit ret = %d", ret);
+      return ret;
+    }
+  }
   if (crtc->out_fence_ptr_property().id() != 0) {
     ret = drmModeAtomicAddProperty(pset, crtc->id(), crtc->out_fence_ptr_property().id(),
                                    (uint64_t) &out_fences[crtc->pipe()]);
@@ -438,17 +538,24 @@
     return;
 
   active_composition_.reset(NULL);
+  vsync_worker_.VSyncControl(false);
 }
 
 void DrmDisplayCompositor::ApplyFrame(
-    std::unique_ptr<DrmDisplayComposition> composition, int status) {
+    std::unique_ptr<DrmDisplayComposition> composition, int status,
+    bool writeback) {
   AutoLock lock(&lock_, __func__);
   if (lock.Lock())
     return;
   int ret = status;
 
-  if (!ret)
+  if (!ret) {
+    if (writeback && !CountdownExpired()) {
+      ALOGE("Abort playing back scene");
+      return;
+    }
     ret = CommitFrame(composition.get(), false);
+  }
 
   if (ret) {
     ALOGE("Composite failed for display %d", display_);
@@ -461,6 +568,8 @@
 
   active_composition_.swap(composition);
 
+  flatten_countdown_ = FLATTEN_COUNTDOWN_INIT;
+  vsync_worker_.VSyncControl(!writeback);
 }
 
 int DrmDisplayCompositor::ApplyComposition(
@@ -510,6 +619,336 @@
   return CommitFrame(composition, true);
 }
 
+// Flatten a scene on the display by using a writeback connector
+// and returns the composition result as a DrmHwcLayer.
+int DrmDisplayCompositor::FlattenOnDisplay(
+    std::unique_ptr<DrmDisplayComposition> &src, DrmConnector *writeback_conn,
+    DrmMode &src_mode, DrmHwcLayer *writeback_layer) {
+  int ret = 0;
+  DrmDevice *drm = resource_manager_->GetDrmDevice(display_);
+  ret = writeback_conn->UpdateModes();
+  if (ret) {
+    ALOGE("Failed to update modes %d", ret);
+    return ret;
+  }
+  for (const DrmMode &mode : writeback_conn->modes()) {
+    if (mode.h_display() == src_mode.h_display() &&
+        mode.v_display() == src_mode.v_display()) {
+      mode_.mode = mode;
+      if (mode_.blob_id)
+        drm->DestroyPropertyBlob(mode_.blob_id);
+      std::tie(ret, mode_.blob_id) = CreateModeBlob(mode_.mode);
+      if (ret) {
+        ALOGE("Failed to create mode blob for display %d", display_);
+        return ret;
+      }
+      mode_.needs_modeset = true;
+      break;
+    }
+  }
+  if (mode_.blob_id <= 0) {
+    ALOGE("Failed to find similar mode");
+    return -EINVAL;
+  }
+
+  DrmCrtc *crtc = drm->GetCrtcForDisplay(display_);
+  if (!crtc) {
+    ALOGE("Failed to find crtc for display %d", display_);
+    return -EINVAL;
+  }
+  // TODO what happens if planes could go to both CRTCs, I don't think it's
+  // handled anywhere
+  std::vector<DrmPlane *> primary_planes;
+  std::vector<DrmPlane *> overlay_planes;
+  for (auto &plane : drm->planes()) {
+    if (!plane->GetCrtcSupported(*crtc))
+      continue;
+    if (plane->type() == DRM_PLANE_TYPE_PRIMARY)
+      primary_planes.push_back(plane.get());
+    else if (plane->type() == DRM_PLANE_TYPE_OVERLAY)
+      overlay_planes.push_back(plane.get());
+  }
+
+  ret = src->Plan(&primary_planes, &overlay_planes);
+  if (ret) {
+    ALOGE("Failed to plan the composition ret = %d", ret);
+    return ret;
+  }
+
+  // Disable the planes we're not using
+  for (auto i = primary_planes.begin(); i != primary_planes.end();) {
+    src->AddPlaneDisable(*i);
+    i = primary_planes.erase(i);
+  }
+  for (auto i = overlay_planes.begin(); i != overlay_planes.end();) {
+    src->AddPlaneDisable(*i);
+    i = overlay_planes.erase(i);
+  }
+
+  AutoLock lock(&lock_, __func__);
+  ret = lock.Lock();
+  if (ret)
+    return ret;
+  DrmFramebuffer *writeback_fb = &framebuffers_[framebuffer_index_];
+  framebuffer_index_ = (framebuffer_index_ + 1) % DRM_DISPLAY_BUFFERS;
+  if (!writeback_fb->Allocate(mode_.mode.h_display(), mode_.mode.v_display())) {
+    ALOGE("Failed to allocate writeback buffer");
+    return -ENOMEM;
+  }
+  DrmHwcBuffer *writeback_buffer = &writeback_layer->buffer;
+  writeback_layer->sf_handle = writeback_fb->buffer()->handle;
+  ret = writeback_layer->ImportBuffer(
+      resource_manager_->GetImporter(display_).get());
+  if (ret) {
+    ALOGE("Failed to import writeback buffer");
+    return ret;
+  }
+
+  ret = CommitFrame(src.get(), true, writeback_conn, writeback_buffer);
+  if (ret) {
+    ALOGE("Atomic check failed");
+    return ret;
+  }
+  ret = CommitFrame(src.get(), false, writeback_conn, writeback_buffer);
+  if (ret) {
+    ALOGE("Atomic commit failed");
+    return ret;
+  }
+
+  ret = sync_wait(writeback_fence_, kWaitWritebackFence);
+  writeback_layer->acquire_fence.Set(writeback_fence_);
+  writeback_fence_ = -1;
+  if (ret) {
+    ALOGE("Failed to wait on writeback fence");
+    return ret;
+  }
+  return 0;
+}
+
+// Flatten a scene by enabling the writeback connector attached
+// to the same CRTC as the one driving the display.
+int DrmDisplayCompositor::FlattenSerial(DrmConnector *writeback_conn) {
+  ALOGV("FlattenSerial by enabling writeback connector to the same crtc");
+  // Flattened composition with only one layer that is obtained
+  // using the writeback connector
+  std::unique_ptr<DrmDisplayComposition> writeback_comp =
+      CreateInitializedComposition();
+  if (!writeback_comp)
+    return -EINVAL;
+
+  AutoLock lock(&lock_, __func__);
+  int ret = lock.Lock();
+  if (ret)
+    return ret;
+  if (!CountdownExpired() || active_composition_->layers().size() < 2) {
+    ALOGV("Flattening is not needed");
+    return -EALREADY;
+  }
+
+  DrmFramebuffer *writeback_fb = &framebuffers_[framebuffer_index_];
+  framebuffer_index_ = (framebuffer_index_ + 1) % DRM_DISPLAY_BUFFERS;
+  lock.Unlock();
+
+  if (!writeback_fb->Allocate(mode_.mode.h_display(), mode_.mode.v_display())) {
+    ALOGE("Failed to allocate writeback buffer");
+    return -ENOMEM;
+  }
+  writeback_comp->layers().emplace_back();
+
+  DrmHwcLayer &writeback_layer = writeback_comp->layers().back();
+  writeback_layer.sf_handle = writeback_fb->buffer()->handle;
+  writeback_layer.source_crop = {0, 0, (float)mode_.mode.h_display(),
+                                 (float)mode_.mode.v_display()};
+  writeback_layer.display_frame = {0, 0, (int)mode_.mode.h_display(),
+                                   (int)mode_.mode.v_display()};
+  ret = writeback_layer.ImportBuffer(
+      resource_manager_->GetImporter(display_).get());
+  if (ret || writeback_comp->layers().size() != 1) {
+    ALOGE("Failed to import writeback buffer");
+    return ret;
+  }
+
+  drmModeAtomicReqPtr pset = drmModeAtomicAlloc();
+  if (!pset) {
+    ALOGE("Failed to allocate property set");
+    return -ENOMEM;
+  }
+  DrmDevice *drm = resource_manager_->GetDrmDevice(display_);
+  DrmCrtc *crtc = drm->GetCrtcForDisplay(display_);
+  if (!crtc) {
+    ALOGE("Failed to find crtc for display %d", display_);
+    return -EINVAL;
+  }
+  ret = SetupWritebackCommit(pset, crtc->id(), writeback_conn,
+                             &writeback_layer.buffer);
+  if (ret < 0) {
+    ALOGE("Failed to Setup Writeback Commit");
+    return ret;
+  }
+  ret = drmModeAtomicCommit(drm->fd(), pset, 0, drm);
+  if (ret) {
+    ALOGE("Failed to enable writeback %d", ret);
+    return ret;
+  }
+  ret = sync_wait(writeback_fence_, kWaitWritebackFence);
+  writeback_layer.acquire_fence.Set(writeback_fence_);
+  writeback_fence_ = -1;
+  if (ret) {
+    ALOGE("Failed to wait on writeback fence");
+    return ret;
+  }
+
+  DrmCompositionPlane squashed_comp(DrmCompositionPlane::Type::kLayer, NULL,
+                                    crtc);
+  for (auto &drmplane : drm->planes()) {
+    if (!drmplane->GetCrtcSupported(*crtc))
+      continue;
+    if (!squashed_comp.plane() && drmplane->type() == DRM_PLANE_TYPE_PRIMARY)
+      squashed_comp.set_plane(drmplane.get());
+    else
+      writeback_comp->AddPlaneDisable(drmplane.get());
+  }
+  squashed_comp.source_layers().push_back(0);
+  ret = writeback_comp->AddPlaneComposition(std::move(squashed_comp));
+  if (ret) {
+    ALOGE("Failed to add flatten scene");
+    return ret;
+  }
+
+  ApplyFrame(std::move(writeback_comp), 0, true);
+  return 0;
+}
+
+// Flatten a scene by using a crtc which works concurrent with
+// the one driving the display.
+int DrmDisplayCompositor::FlattenConcurrent(DrmConnector *writeback_conn) {
+  ALOGV("FlattenConcurrent by using an unused crtc/display");
+  int ret = 0;
+  DrmDisplayCompositor drmdisplaycompositor;
+  ret = drmdisplaycompositor.Init(resource_manager_, writeback_conn->display());
+  if (ret) {
+    ALOGE("Failed to init  drmdisplaycompositor = %d", ret);
+    return ret;
+  }
+  // Copy of the active_composition, needed because of two things:
+  // 1) Not to hold the lock for the whole time we are accessing
+  //    active_composition
+  // 2) It will be committed on a crtc that might not be on the same
+  //     dri node, so buffers need to be imported on the right node.
+  std::unique_ptr<DrmDisplayComposition> copy_comp =
+      drmdisplaycompositor.CreateInitializedComposition();
+
+  // Writeback composition that will be committed to the display.
+  std::unique_ptr<DrmDisplayComposition> writeback_comp =
+      CreateInitializedComposition();
+
+  if (!copy_comp || !writeback_comp)
+    return -EINVAL;
+  AutoLock lock(&lock_, __func__);
+  ret = lock.Lock();
+  if (ret)
+    return ret;
+  if (!CountdownExpired() || active_composition_->layers().size() < 2) {
+    ALOGV("Flattening is not needed");
+    return -EALREADY;
+  }
+  DrmCrtc *crtc = active_composition_->crtc();
+
+  std::vector<DrmHwcLayer> copy_layers;
+  for (DrmHwcLayer &src_layer : active_composition_->layers()) {
+    DrmHwcLayer copy;
+    ret = copy.InitFromDrmHwcLayer(
+        &src_layer,
+        resource_manager_->GetImporter(writeback_conn->display()).get());
+    if (ret) {
+      ALOGE("Failed to import buffer ret = %d", ret);
+      return -EINVAL;
+    }
+    copy_layers.emplace_back(std::move(copy));
+  }
+  ret = copy_comp->SetLayers(copy_layers.data(), copy_layers.size(), true);
+  if (ret) {
+    ALOGE("Failed to set copy_comp layers");
+    return ret;
+  }
+
+  lock.Unlock();
+  DrmHwcLayer writeback_layer;
+  ret = drmdisplaycompositor.FlattenOnDisplay(copy_comp, writeback_conn,
+                                              mode_.mode, &writeback_layer);
+  if (ret) {
+    ALOGE("Failed to flatten on display ret = %d", ret);
+    return ret;
+  }
+
+  DrmCompositionPlane squashed_comp(DrmCompositionPlane::Type::kLayer, NULL,
+                                    crtc);
+  for (auto &drmplane : resource_manager_->GetDrmDevice(display_)->planes()) {
+    if (!drmplane->GetCrtcSupported(*crtc))
+      continue;
+    if (drmplane->type() == DRM_PLANE_TYPE_PRIMARY)
+      squashed_comp.set_plane(drmplane.get());
+    else
+      writeback_comp->AddPlaneDisable(drmplane.get());
+  }
+  writeback_comp->layers().emplace_back();
+  DrmHwcLayer &next_layer = writeback_comp->layers().back();
+  next_layer.sf_handle = writeback_layer.get_usable_handle();
+  next_layer.blending = DrmHwcBlending::kPreMult;
+  next_layer.source_crop = {0, 0, (float)mode_.mode.h_display(),
+                            (float)mode_.mode.v_display()};
+  next_layer.display_frame = {0, 0, (int)mode_.mode.h_display(),
+                              (int)mode_.mode.v_display()};
+  ret = next_layer.ImportBuffer(resource_manager_->GetImporter(display_).get());
+  if (ret) {
+    ALOGE("Failed to import framebuffer for display %d", ret);
+    return ret;
+  }
+  squashed_comp.source_layers().push_back(0);
+  ret = writeback_comp->AddPlaneComposition(std::move(squashed_comp));
+  if (ret) {
+    ALOGE("Failed to add plane composition %d", ret);
+    return ret;
+  }
+  ApplyFrame(std::move(writeback_comp), 0, true);
+  return ret;
+}
+
+int DrmDisplayCompositor::FlattenActiveComposition() {
+  DrmConnector *writeback_conn =
+      resource_manager_->AvailableWritebackConnector(display_);
+  if (!active_composition_ || !writeback_conn) {
+    ALOGV("No writeback connector available");
+    return -EINVAL;
+  }
+
+  if (writeback_conn->display() != display_) {
+    return FlattenConcurrent(writeback_conn);
+  } else {
+    return FlattenSerial(writeback_conn);
+  }
+
+  return 0;
+}
+
+bool DrmDisplayCompositor::CountdownExpired() const {
+  return flatten_countdown_ <= 0;
+}
+
+void DrmDisplayCompositor::Vsync(int display, int64_t timestamp) {
+  AutoLock lock(&lock_, __func__);
+  if (lock.Lock())
+    return;
+  flatten_countdown_--;
+  if (!CountdownExpired())
+    return;
+  lock.Unlock();
+  int ret = FlattenActiveComposition();
+  ALOGV("scene flattening triggered for display %d at timestamp %" PRIu64
+        " result = %d \n",
+        display, timestamp, ret);
+}
+
 void DrmDisplayCompositor::Dump(std::ostringstream *out) const {
   int ret = pthread_mutex_lock(&lock_);
   if (ret)