Merge tag 'android-6.0.1_r3' into 601r3

Android 6.0.1 release 3

* tag 'android-6.0.1_r3': (29 commits)
  drm_hwcomposer: Use mode vrefresh if provided
  drm_hwcomposer: C++ coding style cleanup, no functional changes.
  drm_hwcomposer: move atomic modeset calls into a separate worker
  drm_hwcomposer: get gralloc buffer usage during hwc_set
  drm_hwcomposer: remove unused and confusing HWC_FB_BUFFERS definition.
  drm_hwcomposer: implement squashing
  drm_hwcomposer: lazily generate shaders for GLWorkerCompositor
  drm_hwcomposer: use latest patchset of "separate C compliant header file."
  drm_hwcomposer: Remove hwc.drm.use_framebuffer_target property
  drm_hwcomposer: separate C compliant header file.
  drm_hwcomposer: Add sanity check on display in SetLayers
  drm_hwcomposer: fix missing assignment of premult flag
  drm_hwcomposer: do not queue layers map for virtual display
  drm_hwcomposer: add missing break statement
  drm_hwcomposer: Use layer alpha to blend planes
  drm_hwcomposer: reimplement Dump for DrmDisplayCompositor
  drm_hwcomposer: Do not close duplicate gem handles in nvimporter
  drm_hwcomposer: ground work for squashing
  drm_hwcomposer: name the texture samplers individually and unroll loop
  drm_hwcomposer: fix logic error in premult blending
  ...

Change-Id: I099a48bb319d3c59336b3bc2f7ec054685c37c3e
diff --git a/autogl.h b/autogl.h
index 5d25e44..fc77fb0 100644
--- a/autogl.h
+++ b/autogl.h
@@ -52,8 +52,7 @@
 AUTO_GL_TYPE(AutoGLProgram, GLint, 0, glDeleteProgram(p))
 
 struct AutoEGLDisplayImage {
-  AutoEGLDisplayImage() : display_(EGL_NO_DISPLAY), image_(EGL_NO_IMAGE_KHR) {
-  }
+  AutoEGLDisplayImage() = default;
 
   AutoEGLDisplayImage(EGLDisplay display, EGLImageKHR image)
       : display_(display), image_(image) {
@@ -61,9 +60,10 @@
 
   AutoEGLDisplayImage(const AutoEGLDisplayImage& rhs) = delete;
   AutoEGLDisplayImage(AutoEGLDisplayImage&& rhs) {
-    clear();
-    std::swap(display_, rhs.display_);
-    std::swap(image_, rhs.image_);
+    display_ = rhs.display_;
+    image_ = rhs.image_;
+    rhs.display_ = EGL_NO_DISPLAY;
+    rhs.image_ = EGL_NO_IMAGE_KHR;
   }
 
   ~AutoEGLDisplayImage() {
@@ -97,8 +97,8 @@
   }
 
  private:
-  EGLDisplay display_;
-  EGLImageKHR image_;
+  EGLDisplay display_ = EGL_NO_DISPLAY;
+  EGLImageKHR image_ = EGL_NO_IMAGE_KHR;
 };
 
 struct AutoEGLImageAndGLTexture {
diff --git a/drm_hwcomposer.h b/drm_hwcomposer.h
index fdf389b..4a8b4a3 100644
--- a/drm_hwcomposer.h
+++ b/drm_hwcomposer.h
@@ -23,47 +23,10 @@
 #include <hardware/hardware.h>
 #include <hardware/hwcomposer.h>
 #include "seperate_rects.h"
+#include "drmhwcgralloc.h"
 
 struct hwc_import_context;
 
-enum {
-  /* perform(const struct gralloc_module_t *mod,
-   *	   int op,
-   *	   int drm_fd,
-   *	   buffer_handle_t buffer,
-   *	   struct hwc_drm_bo *bo);
-   */
-  GRALLOC_MODULE_PERFORM_DRM_IMPORT = 0xffeeff00,
-
-  /* perform(const struct gralloc_module_t *mod,
-   *	   int op,
-   *	   buffer_handle_t buffer,
-   *	   void (*free_callback)(void *),
-   *	   void *priv);
-   */
-  GRALLOC_MODULE_PERFORM_SET_IMPORTER_PRIVATE = 0xffeeff01,
-
-  /* perform(const struct gralloc_module_t *mod,
-   *	   int op,
-   *	   buffer_handle_t buffer,
-   *	   void (*free_callback)(void *),
-   *	   void **priv);
-   */
-  GRALLOC_MODULE_PERFORM_GET_IMPORTER_PRIVATE = 0xffeeff02,
-};
-
-typedef struct hwc_drm_bo {
-  uint32_t width;
-  uint32_t height;
-  uint32_t format; /* DRM_FORMAT_* from drm_fourcc.h */
-  uint32_t pitches[4];
-  uint32_t offsets[4];
-  uint32_t gem_handles[4];
-  uint32_t fb_id;
-  int acquire_fence_fd;
-  void *priv;
-} hwc_drm_bo_t;
-
 int hwc_import_init(struct hwc_import_context **ctx);
 int hwc_import_destroy(struct hwc_import_context *ctx);
 
@@ -171,11 +134,11 @@
     return *this;
   }
 
-  operator bool() {
+  operator bool() const {
     return importer_ != NULL;
   }
 
-  hwc_drm_bo *operator->();
+  const hwc_drm_bo *operator->() const;
 
   void Clear();
 
@@ -245,6 +208,7 @@
 
 struct DrmHwcLayer {
   buffer_handle_t sf_handle = NULL;
+  int gralloc_buffer_usage = 0;
   DrmHwcBuffer buffer;
   DrmHwcNativeHandle handle;
   DrmHwcTransform transform = DrmHwcTransform::kIdentity;
@@ -257,11 +221,12 @@
   UniqueFd acquire_fence;
   OutputFd release_fence;
 
-  DrmHwcLayer() = default;
-  DrmHwcLayer(DrmHwcLayer &&rhs) = default;
-
   int InitFromHwcLayer(hwc_layer_1_t *sf_layer, Importer *importer,
                        const gralloc_module_t *gralloc);
+
+  buffer_handle_t get_usable_handle() const {
+    return handle.get() != NULL ? handle.get() : sf_handle;
+  }
 };
 
 struct DrmHwcDisplayContents {
diff --git a/drmcomposition.cpp b/drmcomposition.cpp
index 55fa00c..e7b02b6 100644
--- a/drmcomposition.cpp
+++ b/drmcomposition.cpp
@@ -58,10 +58,9 @@
     // If the display hasn't been modeset yet, this will be NULL
     DrmCrtc *crtc = drm_->GetCrtcForDisplay(display);
 
-    int ret = composition_map_[(*iter)->display()]->Init(drm_, crtc, importer_,
-                                                         frame_no);
+    int ret = composition_map_[display]->Init(drm_, crtc, importer_, frame_no);
     if (ret) {
-      ALOGE("Failed to init display composition for %d", (*iter)->display());
+      ALOGE("Failed to init display composition for %d", display);
       return ret;
     }
   }
@@ -76,14 +75,18 @@
     DrmCompositionDisplayLayersMap &map = maps[display_index];
     int display = map.display;
 
+    if (!drm_->GetConnectorForDisplay(display)) {
+      ALOGE("Invalid display given to SetLayers %d", display);
+      continue;
+    }
+
     ret = composition_map_[display]->SetLayers(
-        map.layers.data(), map.layers.size(), &primary_planes_,
-        &overlay_planes_);
+        map.layers.data(), map.layers.size(), map.geometry_changed);
     if (ret)
       return ret;
   }
 
-  return DisableUnusedPlanes();
+  return 0;
 }
 
 int DrmComposition::SetDpmsMode(int display, uint32_t dpms_mode) {
@@ -99,6 +102,23 @@
   return std::move(composition_map_[display]);
 }
 
+int DrmComposition::Plan(std::map<int, DrmDisplayCompositor> &compositor_map) {
+  int ret = 0;
+  for (DrmResources::ConnectorIter iter = drm_->begin_connectors();
+       iter != drm_->end_connectors(); ++iter) {
+    int display = (*iter)->display();
+    DrmDisplayComposition *comp = GetDisplayComposition(display);
+    ret = comp->Plan(compositor_map[display].squash_state(), &primary_planes_,
+                     &overlay_planes_);
+    if (ret) {
+      ALOGE("Failed to plan composition for dislay %d", display);
+      return ret;
+    }
+  }
+
+  return 0;
+}
+
 int DrmComposition::DisableUnusedPlanes() {
   for (DrmResources::ConnectorIter iter = drm_->begin_connectors();
        iter != drm_->end_connectors(); ++iter) {
diff --git a/drmcomposition.h b/drmcomposition.h
index ad7a5df..80a7eea 100644
--- a/drmcomposition.h
+++ b/drmcomposition.h
@@ -30,8 +30,11 @@
 
 namespace android {
 
+class DrmDisplayCompositor;
+
 struct DrmCompositionDisplayLayersMap {
   int display;
+  bool geometry_changed = true;
   std::vector<DrmHwcLayer> layers;
 
   DrmCompositionDisplayLayersMap() = default;
@@ -51,6 +54,8 @@
 
   std::unique_ptr<DrmDisplayComposition> TakeDisplayComposition(int display);
   DrmDisplayComposition *GetDisplayComposition(int display);
+
+  int Plan(std::map<int, DrmDisplayCompositor> &compositor_map);
   int DisableUnusedPlanes();
 
  private:
diff --git a/drmcompositor.cpp b/drmcompositor.cpp
index ad4cf88..6b3c294 100644
--- a/drmcompositor.cpp
+++ b/drmcompositor.cpp
@@ -64,11 +64,15 @@
 
 int DrmCompositor::QueueComposition(
     std::unique_ptr<DrmComposition> composition) {
-  int ret = composition->DisableUnusedPlanes();
-  if (ret) {
-    ALOGE("Failed to disable unused planes %d", ret);
+  int ret;
+
+  ret = composition->Plan(compositor_map_);
+  if (ret)
     return ret;
-  }
+
+  ret = composition->DisableUnusedPlanes();
+  if (ret)
+    return ret;
 
   for (DrmResources::ConnectorIter iter = drm_->begin_connectors();
        iter != drm_->end_connectors(); ++iter) {
@@ -76,7 +80,7 @@
     int ret = compositor_map_[display].QueueComposition(
         composition->TakeDisplayComposition(display));
     if (ret) {
-      ALOGE("Failed to queue composition for display %d", display);
+      ALOGE("Failed to queue composition for display %d (%d)", display, ret);
       return ret;
     }
   }
diff --git a/drmcompositorworker.h b/drmcompositorworker.h
index 9f74fb6..00e14ee 100644
--- a/drmcompositorworker.h
+++ b/drmcompositorworker.h
@@ -26,12 +26,12 @@
 class DrmCompositorWorker : public Worker {
  public:
   DrmCompositorWorker(DrmDisplayCompositor *compositor);
-  ~DrmCompositorWorker();
+  ~DrmCompositorWorker() override;
 
   int Init();
 
  protected:
-  virtual void Routine();
+  void Routine() override;
 
   DrmDisplayCompositor *compositor_;
 };
diff --git a/drmdisplaycomposition.cpp b/drmdisplaycomposition.cpp
index d47b5dc..34d879e 100644
--- a/drmdisplaycomposition.cpp
+++ b/drmdisplaycomposition.cpp
@@ -23,6 +23,9 @@
 
 #include <stdlib.h>
 
+#include <algorithm>
+#include <unordered_set>
+
 #include <cutils/log.h>
 #include <sw_sync.h>
 #include <sync/sync.h>
@@ -30,38 +33,15 @@
 
 namespace android {
 
-DrmCompositionLayer::DrmCompositionLayer(DrmCrtc *crtc, DrmHwcLayer &&l)
-    : crtc(crtc),
-      sf_handle(l.sf_handle),
-      buffer(std::move(l.buffer)),
-      handle(std::move(l.handle)),
-      transform(l.transform),
-      blending(l.blending),
-      alpha(l.alpha),
-      source_crop(l.source_crop),
-      display_frame(l.display_frame),
-      source_damage(l.source_damage),
-      acquire_fence(std::move(l.acquire_fence)) {
-}
-
-DrmDisplayComposition::DrmDisplayComposition()
-    : drm_(NULL),
-      importer_(NULL),
-      type_(DRM_COMPOSITION_TYPE_EMPTY),
-      timeline_fd_(-1),
-      timeline_(0),
-      timeline_current_(0),
-      timeline_pre_comp_done_(0),
-      pre_composition_layer_index_(-1),
-      dpms_mode_(DRM_MODE_DPMS_ON),
-      frame_no_(0) {
-}
+const size_t DrmCompositionPlane::kSourceNone;
+const size_t DrmCompositionPlane::kSourcePreComp;
+const size_t DrmCompositionPlane::kSourceSquash;
+const size_t DrmCompositionPlane::kSourceLayerMax;
 
 DrmDisplayComposition::~DrmDisplayComposition() {
   if (timeline_fd_ >= 0) {
-    FinishComposition();
+    SignalCompositionDone();
     close(timeline_fd_);
-    timeline_fd_ = -1;
   }
 }
 
@@ -81,14 +61,79 @@
   return 0;
 }
 
-DrmCompositionType DrmDisplayComposition::type() const {
-  return type_;
-}
-
 bool DrmDisplayComposition::validate_composition_type(DrmCompositionType des) {
   return type_ == DRM_COMPOSITION_TYPE_EMPTY || type_ == des;
 }
 
+int DrmDisplayComposition::CreateNextTimelineFence() {
+  ++timeline_;
+  return sw_sync_fence_create(timeline_fd_, "hwc drm display composition fence",
+                              timeline_);
+}
+
+int DrmDisplayComposition::IncreaseTimelineToPoint(int point) {
+  int timeline_increase = point - timeline_current_;
+  if (timeline_increase <= 0)
+    return 0;
+
+  int ret = sw_sync_timeline_inc(timeline_fd_, timeline_increase);
+  if (ret)
+    ALOGE("Failed to increment sync timeline %d", ret);
+  else
+    timeline_current_ = point;
+
+  return ret;
+}
+
+int DrmDisplayComposition::SetLayers(DrmHwcLayer *layers, size_t num_layers,
+                                     bool geometry_changed) {
+  if (!validate_composition_type(DRM_COMPOSITION_TYPE_FRAME))
+    return -EINVAL;
+
+  geometry_changed_ = geometry_changed;
+
+  for (size_t layer_index = 0; layer_index < num_layers; layer_index++) {
+    layers_.emplace_back(std::move(layers[layer_index]));
+  }
+
+  type_ = DRM_COMPOSITION_TYPE_FRAME;
+  return 0;
+}
+
+int DrmDisplayComposition::SetDpmsMode(uint32_t dpms_mode) {
+  if (!validate_composition_type(DRM_COMPOSITION_TYPE_DPMS))
+    return -EINVAL;
+  dpms_mode_ = dpms_mode;
+  type_ = DRM_COMPOSITION_TYPE_DPMS;
+  return 0;
+}
+
+int DrmDisplayComposition::SetDisplayMode(const DrmMode &display_mode) {
+  if (!validate_composition_type(DRM_COMPOSITION_TYPE_MODESET))
+    return -EINVAL;
+  display_mode_ = display_mode;
+  dpms_mode_ = DRM_MODE_DPMS_ON;
+  type_ = DRM_COMPOSITION_TYPE_MODESET;
+  return 0;
+}
+
+int DrmDisplayComposition::AddPlaneDisable(DrmPlane *plane) {
+  composition_planes_.emplace_back(
+      DrmCompositionPlane{plane, crtc_, DrmCompositionPlane::kSourceNone});
+  return 0;
+}
+
+static size_t CountUsablePlanes(DrmCrtc *crtc,
+                                std::vector<DrmPlane *> *primary_planes,
+                                std::vector<DrmPlane *> *overlay_planes) {
+  return std::count_if(
+             primary_planes->begin(), primary_planes->end(),
+             [=](DrmPlane *plane) { return plane->GetCrtcSupported(*crtc); }) +
+         std::count_if(
+             overlay_planes->begin(), overlay_planes->end(),
+             [=](DrmPlane *plane) { return plane->GetCrtcSupported(*crtc); });
+}
+
 static DrmPlane *TakePlane(DrmCrtc *crtc, std::vector<DrmPlane *> *planes) {
   for (auto iter = planes->begin(); iter != planes->end(); ++iter) {
     if ((*iter)->GetCrtcSupported(*crtc)) {
@@ -109,182 +154,387 @@
   return TakePlane(crtc, overlay_planes);
 }
 
-int DrmDisplayComposition::CreateNextTimelineFence() {
-  ++timeline_;
-  return sw_sync_fence_create(timeline_fd_, "drm_fence", timeline_);
+static std::vector<size_t> SetBitsToVector(uint64_t in, size_t *index_map) {
+  std::vector<size_t> out;
+  size_t msb = sizeof(in) * 8 - 1;
+  uint64_t mask = (uint64_t)1 << msb;
+  for (size_t i = msb; mask != (uint64_t)0; i--, mask >>= 1)
+    if (in & mask)
+      out.push_back(index_map[i]);
+  return out;
 }
 
-int DrmDisplayComposition::IncreaseTimelineToPoint(int point) {
-  int timeline_increase = point - timeline_current_;
-  if (timeline_increase <= 0)
-    return 0;
+static void SeparateLayers(DrmHwcLayer *layers, size_t *used_layers,
+                           size_t num_used_layers,
+                           DrmHwcRect<int> *exclude_rects,
+                           size_t num_exclude_rects,
+                           std::vector<DrmCompositionRegion> &regions) {
+  if (num_used_layers > 64) {
+    ALOGE("Failed to separate layers because there are more than 64");
+    return;
+  }
 
-  int ret = sw_sync_timeline_inc(timeline_fd_, timeline_increase);
-  if (ret)
-    ALOGE("Failed to increment sync timeline %d", ret);
-  else
-    timeline_current_ = point;
+  if (num_used_layers + num_exclude_rects > 64) {
+    ALOGW(
+        "Exclusion rectangles are being truncated to make the rectangle count "
+        "fit into 64");
+    num_exclude_rects = 64 - num_used_layers;
+  }
 
-  return ret;
+  // We inject all the exclude rects into the rects list. Any resulting rect
+  // that includes ANY of the first num_exclude_rects is rejected.
+  std::vector<DrmHwcRect<int>> layer_rects(num_used_layers + num_exclude_rects);
+  std::copy(exclude_rects, exclude_rects + num_exclude_rects,
+            layer_rects.begin());
+  std::transform(
+      used_layers, used_layers + num_used_layers,
+      layer_rects.begin() + num_exclude_rects,
+      [=](size_t layer_index) { return layers[layer_index].display_frame; });
+
+  std::vector<seperate_rects::RectSet<uint64_t, int>> seperate_regions;
+  seperate_rects::seperate_rects_64(layer_rects, &seperate_regions);
+  uint64_t exclude_mask = ((uint64_t)1 << num_exclude_rects) - 1;
+
+  for (seperate_rects::RectSet<uint64_t, int> &region : seperate_regions) {
+    if (region.id_set.getBits() & exclude_mask)
+      continue;
+    regions.emplace_back(DrmCompositionRegion{
+        region.rect,
+        SetBitsToVector(region.id_set.getBits() >> num_exclude_rects,
+                        used_layers)});
+  }
 }
 
-int DrmDisplayComposition::SetLayers(DrmHwcLayer *layers, size_t num_layers,
-                                     std::vector<DrmPlane *> *primary_planes,
-                                     std::vector<DrmPlane *> *overlay_planes) {
-  int ret = 0;
-  if (!validate_composition_type(DRM_COMPOSITION_TYPE_FRAME))
-    return -EINVAL;
+int DrmDisplayComposition::CreateAndAssignReleaseFences() {
+  std::unordered_set<DrmHwcLayer *> squash_layers;
+  std::unordered_set<DrmHwcLayer *> pre_comp_layers;
+  std::unordered_set<DrmHwcLayer *> comp_layers;
 
-  for (size_t layer_index = 0; layer_index < num_layers; layer_index++) {
-    DrmHwcLayer *layer = &layers[layer_index];
-
-    layers_.emplace_back(crtc_, std::move(*layer));
-    DrmCompositionLayer *c_layer = &layers_.back();
-
-    if (pre_composition_layer_index_ == -1) {
-      c_layer->plane = TakePlane(crtc_, primary_planes, overlay_planes);
-      if (c_layer->plane == NULL) {
-        if (layers_.size() <= 1) {
-          ALOGE("Failed to match any planes to the crtc of this display");
-          ret = -ENODEV;
-          goto fail;
-        }
-
-        layers_.emplace_back();
-        // c_layer's address might have changed when we resized the vector
-        c_layer = &layers_[layers_.size() - 2];
-        DrmCompositionLayer &pre_comp_layer = layers_.back();
-        pre_comp_layer.crtc = crtc_;
-
-        pre_composition_layer_index_ = layers_.size() - 1;
-
-        // This is all to fix up the previous layer, which has now become part
-        // of the set of pre-composition layers because we are stealing its
-        // plane.
-        DrmCompositionLayer &last_c_layer = layers_[layers_.size() - 3];
-        std::swap(pre_comp_layer.plane, last_c_layer.plane);
-        OutputFd &last_release_fence = layers[layer_index - 1].release_fence;
-        last_release_fence.Set(CreateNextTimelineFence());
-        ret = last_release_fence.get();
-        if (ret < 0) {
-          ALOGE("Could not create release fence %d", ret);
-          goto fail;
-        }
-      }
-    }
-
-    if (c_layer->plane == NULL) {
-      // Layers to be pre composited all get the earliest release fences as they
-      // will get released soonest.
-      layer->release_fence.Set(CreateNextTimelineFence());
-      ret = layer->release_fence.get();
-      if (ret < 0) {
-        ALOGE("Could not create release fence %d", ret);
-        goto fail;
-      }
+  for (const DrmCompositionRegion &region : squash_regions_) {
+    for (size_t source_layer_index : region.source_layers) {
+      DrmHwcLayer *source_layer = &layers_[source_layer_index];
+      squash_layers.emplace(source_layer);
     }
   }
 
+  for (const DrmCompositionRegion &region : pre_comp_regions_) {
+    for (size_t source_layer_index : region.source_layers) {
+      DrmHwcLayer *source_layer = &layers_[source_layer_index];
+      pre_comp_layers.emplace(source_layer);
+      squash_layers.erase(source_layer);
+    }
+  }
+
+  for (const DrmCompositionPlane &plane : composition_planes_) {
+    if (plane.source_layer <= DrmCompositionPlane::kSourceLayerMax) {
+      DrmHwcLayer *source_layer = &layers_[plane.source_layer];
+      comp_layers.emplace(source_layer);
+      pre_comp_layers.erase(source_layer);
+    }
+  }
+
+  for (DrmHwcLayer *layer : squash_layers) {
+    int ret = layer->release_fence.Set(CreateNextTimelineFence());
+    if (ret < 0)
+      return ret;
+  }
+  timeline_squash_done_ = timeline_;
+
+  for (DrmHwcLayer *layer : pre_comp_layers) {
+    int ret = layer->release_fence.Set(CreateNextTimelineFence());
+    if (ret < 0)
+      return ret;
+  }
   timeline_pre_comp_done_ = timeline_;
 
-  for (size_t layer_index = 0; layer_index < num_layers; layer_index++) {
-    DrmHwcLayer *layer = &layers[layer_index];
-    if (layer->release_fence.get() >= 0)
+  for (DrmHwcLayer *layer : comp_layers) {
+    int ret = layer->release_fence.Set(CreateNextTimelineFence());
+    if (ret < 0)
+      return ret;
+  }
+
+  return 0;
+}
+
+int DrmDisplayComposition::Plan(SquashState *squash,
+                                std::vector<DrmPlane *> *primary_planes,
+                                std::vector<DrmPlane *> *overlay_planes) {
+  if (type_ != DRM_COMPOSITION_TYPE_FRAME)
+    return 0;
+
+  size_t planes_can_use =
+      CountUsablePlanes(crtc_, primary_planes, overlay_planes);
+  if (planes_can_use == 0) {
+    ALOGE("Display %d has no usable planes", crtc_->display());
+    return -ENODEV;
+  }
+
+  bool use_squash_framebuffer = false;
+  // Used to determine which layers were entirely squashed
+  std::vector<int> layer_squash_area(layers_.size(), 0);
+  // Used to avoid rerendering regions that were squashed
+  std::vector<DrmHwcRect<int>> exclude_rects;
+  if (squash != NULL && planes_can_use >= 3) {
+    if (geometry_changed_) {
+      squash->Init(layers_.data(), layers_.size());
+    } else {
+      std::vector<bool> changed_regions;
+      squash->GenerateHistory(layers_.data(), layers_.size(), changed_regions);
+
+      std::vector<bool> stable_regions;
+      squash->StableRegionsWithMarginalHistory(changed_regions, stable_regions);
+
+      // Only if SOME region is stable
+      use_squash_framebuffer =
+          std::find(stable_regions.begin(), stable_regions.end(), true) !=
+          stable_regions.end();
+
+      squash->RecordHistory(layers_.data(), layers_.size(), changed_regions);
+
+      // Changes in which regions are squashed triggers a rerender via
+      // squash_regions.
+      bool render_squash = squash->RecordAndCompareSquashed(stable_regions);
+
+      for (size_t region_index = 0; region_index < stable_regions.size();
+           region_index++) {
+        const SquashState::Region &region = squash->regions()[region_index];
+        if (!stable_regions[region_index])
+          continue;
+
+        exclude_rects.emplace_back(region.rect);
+
+        if (render_squash) {
+          squash_regions_.emplace_back();
+          squash_regions_.back().frame = region.rect;
+        }
+
+        int frame_area = region.rect.area();
+        // Source layers are sorted front to back i.e. top layer has lowest
+        // index.
+        for (size_t layer_index = layers_.size();
+             layer_index-- > 0;  // Yes, I double checked this
+             /* See condition */) {
+          if (!region.layer_refs[layer_index])
+            continue;
+          layer_squash_area[layer_index] += frame_area;
+          if (render_squash)
+            squash_regions_.back().source_layers.push_back(layer_index);
+        }
+      }
+    }
+  }
+
+  std::vector<size_t> layers_remaining;
+  for (size_t layer_index = 0; layer_index < layers_.size(); layer_index++) {
+    // Skip layers that were completely squashed
+    if (layer_squash_area[layer_index] >=
+        layers_[layer_index].display_frame.area()) {
       continue;
+    }
 
-    ret = layer->release_fence.Set(CreateNextTimelineFence());
-    if (ret < 0) {
-      ALOGE("Could not create release fence %d", ret);
-      goto fail;
+    layers_remaining.push_back(layer_index);
+  }
+
+  if (use_squash_framebuffer)
+    planes_can_use--;
+
+  if (layers_remaining.size() > planes_can_use)
+    planes_can_use--;
+
+  size_t last_composition_layer = 0;
+  for (last_composition_layer = 0;
+       last_composition_layer < layers_remaining.size() && planes_can_use > 0;
+       last_composition_layer++, planes_can_use--) {
+    composition_planes_.emplace_back(
+        DrmCompositionPlane{TakePlane(crtc_, primary_planes, overlay_planes),
+                            crtc_, layers_remaining[last_composition_layer]});
+  }
+
+  layers_remaining.erase(layers_remaining.begin(),
+                         layers_remaining.begin() + last_composition_layer);
+
+  if (layers_remaining.size() > 0) {
+    composition_planes_.emplace_back(
+        DrmCompositionPlane{TakePlane(crtc_, primary_planes, overlay_planes),
+                            crtc_, DrmCompositionPlane::kSourcePreComp});
+
+    SeparateLayers(layers_.data(), layers_remaining.data(),
+                   layers_remaining.size(), exclude_rects.data(),
+                   exclude_rects.size(), pre_comp_regions_);
+  }
+
+  if (use_squash_framebuffer) {
+    composition_planes_.emplace_back(
+        DrmCompositionPlane{TakePlane(crtc_, primary_planes, overlay_planes),
+                            crtc_, DrmCompositionPlane::kSourceSquash});
+  }
+
+  return CreateAndAssignReleaseFences();
+}
+
+static const char *DrmCompositionTypeToString(DrmCompositionType type) {
+  switch (type) {
+    case DRM_COMPOSITION_TYPE_EMPTY:
+      return "EMPTY";
+    case DRM_COMPOSITION_TYPE_FRAME:
+      return "FRAME";
+    case DRM_COMPOSITION_TYPE_DPMS:
+      return "DPMS";
+    case DRM_COMPOSITION_TYPE_MODESET:
+      return "MODESET";
+    default:
+      return "<invalid>";
+  }
+}
+
+static const char *DPMSModeToString(int dpms_mode) {
+  switch (dpms_mode) {
+    case DRM_MODE_DPMS_ON:
+      return "ON";
+    case DRM_MODE_DPMS_OFF:
+      return "OFF";
+    default:
+      return "<invalid>";
+  }
+}
+
+static void DumpBuffer(const DrmHwcBuffer &buffer, std::ostringstream *out) {
+  if (!buffer) {
+    *out << "buffer=<invalid>";
+    return;
+  }
+
+  *out << "buffer[w/h/format]=";
+  *out << buffer->width << "/" << buffer->height << "/" << buffer->format;
+}
+
+static const char *TransformToString(DrmHwcTransform transform) {
+  switch (transform) {
+    case DrmHwcTransform::kIdentity:
+      return "IDENTITY";
+    case DrmHwcTransform::kFlipH:
+      return "FLIPH";
+    case DrmHwcTransform::kFlipV:
+      return "FLIPV";
+    case DrmHwcTransform::kRotate90:
+      return "ROTATE90";
+    case DrmHwcTransform::kRotate180:
+      return "ROTATE180";
+    case DrmHwcTransform::kRotate270:
+      return "ROTATE270";
+    default:
+      return "<invalid>";
+  }
+}
+
+static const char *BlendingToString(DrmHwcBlending blending) {
+  switch (blending) {
+    case DrmHwcBlending::kNone:
+      return "NONE";
+    case DrmHwcBlending::kPreMult:
+      return "PREMULT";
+    case DrmHwcBlending::kCoverage:
+      return "COVERAGE";
+    default:
+      return "<invalid>";
+  }
+}
+
+static void DumpRegion(const DrmCompositionRegion &region,
+                       std::ostringstream *out) {
+  *out << "frame";
+  region.frame.Dump(out);
+  *out << " source_layers=(";
+
+  const std::vector<size_t> &source_layers = region.source_layers;
+  for (size_t i = 0; i < source_layers.size(); i++) {
+    *out << source_layers[i];
+    if (i < source_layers.size() - 1) {
+      *out << " ";
     }
   }
 
-  type_ = DRM_COMPOSITION_TYPE_FRAME;
-  return 0;
+  *out << ")";
+}
 
-fail:
+void DrmDisplayComposition::Dump(std::ostringstream *out) const {
+  *out << "----DrmDisplayComposition"
+       << " crtc=" << (crtc_ ? crtc_->id() : -1)
+       << " type=" << DrmCompositionTypeToString(type_);
 
-  for (size_t c_layer_index = 0; c_layer_index < layers_.size();
-       c_layer_index++) {
-    DrmCompositionLayer &c_layer = layers_[c_layer_index];
-    if (c_layer.plane != NULL) {
-      std::vector<DrmPlane *> *return_to =
-          (c_layer.plane->type() == DRM_PLANE_TYPE_PRIMARY) ? primary_planes
-                                                            : overlay_planes;
-      return_to->insert(return_to->begin() + c_layer_index, c_layer.plane);
-    }
+  switch (type_) {
+    case DRM_COMPOSITION_TYPE_DPMS:
+      *out << " dpms_mode=" << DPMSModeToString(dpms_mode_);
+      break;
+    case DRM_COMPOSITION_TYPE_MODESET:
+      *out << " display_mode=" << display_mode_.h_display() << "x"
+           << display_mode_.v_display();
+      break;
+    default:
+      break;
   }
 
-  layers_.clear();
+  *out << " timeline[current/squash/pre-comp/done]=" << timeline_current_ << "/"
+       << timeline_squash_done_ << "/" << timeline_pre_comp_done_ << "/"
+       << timeline_ << "\n";
 
-  sw_sync_timeline_inc(timeline_fd_, timeline_ - timeline_current_);
+  *out << "    Layers: count=" << layers_.size() << "\n";
+  for (size_t i = 0; i < layers_.size(); i++) {
+    const DrmHwcLayer &layer = layers_[i];
+    *out << "      [" << i << "] ";
 
-  timeline_ = timeline_current_;
-  return ret;
-}
+    DumpBuffer(layer.buffer, out);
 
-int DrmDisplayComposition::SetDpmsMode(uint32_t dpms_mode) {
-  if (!validate_composition_type(DRM_COMPOSITION_TYPE_DPMS))
-    return -EINVAL;
-  dpms_mode_ = dpms_mode;
-  type_ = DRM_COMPOSITION_TYPE_DPMS;
-  return 0;
-}
+    *out << " transform=" << TransformToString(layer.transform)
+         << " blending[a=" << (int)layer.alpha
+         << "]=" << BlendingToString(layer.blending) << " source_crop";
+    layer.source_crop.Dump(out);
+    *out << " display_frame";
+    layer.display_frame.Dump(out);
 
-int DrmDisplayComposition::SetDisplayMode(const DrmMode &display_mode) {
-  if (!validate_composition_type(DRM_COMPOSITION_TYPE_MODESET))
-    return -EINVAL;
-  display_mode_ = display_mode;
-  dpms_mode_ = DRM_MODE_DPMS_ON;
-  type_ = DRM_COMPOSITION_TYPE_MODESET;
-  return 0;
-}
+    *out << "\n";
+  }
 
-int DrmDisplayComposition::AddPlaneDisable(DrmPlane *plane) {
-  layers_.emplace_back();
-  DrmCompositionLayer &c_layer = layers_.back();
-  c_layer.crtc = NULL;
-  c_layer.plane = plane;
-  return 0;
-}
+  *out << "    Planes: count=" << composition_planes_.size() << "\n";
+  for (size_t i = 0; i < composition_planes_.size(); i++) {
+    const DrmCompositionPlane &comp_plane = composition_planes_[i];
+    *out << "      [" << i << "]"
+         << " plane=" << (comp_plane.plane ? comp_plane.plane->id() : -1)
+         << " source_layer=";
+    if (comp_plane.source_layer <= DrmCompositionPlane::kSourceLayerMax) {
+      *out << comp_plane.source_layer;
+    } else {
+      switch (comp_plane.source_layer) {
+        case DrmCompositionPlane::kSourceNone:
+          *out << "NONE";
+          break;
+        case DrmCompositionPlane::kSourcePreComp:
+          *out << "PRECOMP";
+          break;
+        case DrmCompositionPlane::kSourceSquash:
+          *out << "SQUASH";
+          break;
+        default:
+          *out << "<invalid>";
+          break;
+      }
+    }
 
-void DrmDisplayComposition::RemoveNoPlaneLayers() {
-  layers_.erase(
-      std::remove_if(layers_.begin(), layers_.end(),
-                     [](DrmCompositionLayer &l) { return l.plane == NULL; }),
-      layers_.end());
-}
+    *out << "\n";
+  }
 
-int DrmDisplayComposition::SignalPreCompositionDone() {
-  return IncreaseTimelineToPoint(timeline_pre_comp_done_);
-}
+  *out << "    Squash Regions: count=" << squash_regions_.size() << "\n";
+  for (size_t i = 0; i < squash_regions_.size(); i++) {
+    *out << "      [" << i << "] ";
+    DumpRegion(squash_regions_[i], out);
+    *out << "\n";
+  }
 
-int DrmDisplayComposition::FinishComposition() {
-  return IncreaseTimelineToPoint(timeline_);
-}
-
-std::vector<DrmCompositionLayer>
-    *DrmDisplayComposition::GetCompositionLayers() {
-  return &layers_;
-}
-
-int DrmDisplayComposition::pre_composition_layer_index() const {
-  return pre_composition_layer_index_;
-}
-
-uint32_t DrmDisplayComposition::dpms_mode() const {
-  return dpms_mode_;
-}
-
-uint64_t DrmDisplayComposition::frame_no() const {
-  return frame_no_;
-}
-
-const DrmMode &DrmDisplayComposition::display_mode() const {
-  return display_mode_;
-}
-
-Importer *DrmDisplayComposition::importer() const {
-  return importer_;
+  *out << "    Pre-Comp Regions: count=" << pre_comp_regions_.size() << "\n";
+  for (size_t i = 0; i < pre_comp_regions_.size(); i++) {
+    *out << "      [" << i << "] ";
+    DumpRegion(pre_comp_regions_[i], out);
+    *out << "\n";
+  }
 }
 }
diff --git a/drmdisplaycomposition.h b/drmdisplaycomposition.h
index 57e8521..69324fd 100644
--- a/drmdisplaycomposition.h
+++ b/drmdisplaycomposition.h
@@ -23,6 +23,7 @@
 #include "glworker.h"
 #include "importer.h"
 
+#include <sstream>
 #include <vector>
 
 #include <hardware/gralloc.h>
@@ -31,6 +32,8 @@
 
 namespace android {
 
+struct SquashState;
+
 enum DrmCompositionType {
   DRM_COMPOSITION_TYPE_EMPTY,
   DRM_COMPOSITION_TYPE_FRAME,
@@ -38,90 +41,119 @@
   DRM_COMPOSITION_TYPE_MODESET,
 };
 
-struct DrmCompositionLayer {
-  DrmCrtc *crtc = NULL;
-  DrmPlane *plane = NULL;
+struct DrmCompositionRegion {
+  DrmHwcRect<int> frame;
+  std::vector<size_t> source_layers;
+};
 
-  buffer_handle_t sf_handle = NULL;
-  DrmHwcBuffer buffer;
-  DrmHwcNativeHandle handle;
-  DrmHwcTransform transform = DrmHwcTransform::kIdentity;
-  DrmHwcBlending blending = DrmHwcBlending::kNone;
-  uint8_t alpha = 0xff;
-  DrmHwcRect<float> source_crop;
-  DrmHwcRect<int> display_frame;
-  std::vector<DrmHwcRect<int>> source_damage;
-
-  UniqueFd acquire_fence;
-
-  DrmCompositionLayer() = default;
-  DrmCompositionLayer(DrmCrtc *crtc, DrmHwcLayer &&l);
-  DrmCompositionLayer(DrmCompositionLayer &&l) = default;
-
-  DrmCompositionLayer &operator=(DrmCompositionLayer &&l) = default;
-
-  buffer_handle_t get_usable_handle() const {
-    return handle.get() != NULL ? handle.get() : sf_handle;
-  }
+struct DrmCompositionPlane {
+  const static size_t kSourceNone = SIZE_MAX;
+  const static size_t kSourcePreComp = kSourceNone - 1;
+  const static size_t kSourceSquash = kSourcePreComp - 1;
+  const static size_t kSourceLayerMax = kSourceSquash - 1;
+  DrmPlane *plane;
+  DrmCrtc *crtc;
+  size_t source_layer;
 };
 
 class DrmDisplayComposition {
  public:
-  DrmDisplayComposition();
+  DrmDisplayComposition() = default;
+  DrmDisplayComposition(const DrmDisplayComposition &) = delete;
   ~DrmDisplayComposition();
 
   int Init(DrmResources *drm, DrmCrtc *crtc, Importer *importer,
            uint64_t frame_no);
 
-  DrmCompositionType type() const;
-
-  int SetLayers(DrmHwcLayer *layers, size_t num_layers,
-                std::vector<DrmPlane *> *primary_planes,
-                std::vector<DrmPlane *> *overlay_planes);
+  int SetLayers(DrmHwcLayer *layers, size_t num_layers, bool geometry_changed);
   int AddPlaneDisable(DrmPlane *plane);
   int SetDpmsMode(uint32_t dpms_mode);
   int SetDisplayMode(const DrmMode &display_mode);
 
-  void RemoveNoPlaneLayers();
-  int SignalPreCompositionDone();
-  int FinishComposition();
-
-  std::vector<DrmCompositionLayer> *GetCompositionLayers();
-  int pre_composition_layer_index() const;
-  uint32_t dpms_mode() const;
-  const DrmMode &display_mode() const;
-
-  uint64_t frame_no() const;
-
-  Importer *importer() const;
-
- private:
-  DrmDisplayComposition(const DrmDisplayComposition &) = delete;
-
-  bool validate_composition_type(DrmCompositionType desired);
+  int Plan(SquashState *squash, std::vector<DrmPlane *> *primary_planes,
+           std::vector<DrmPlane *> *overlay_planes);
 
   int CreateNextTimelineFence();
+  int SignalSquashDone() {
+    return IncreaseTimelineToPoint(timeline_squash_done_);
+  }
+  int SignalPreCompDone() {
+    return IncreaseTimelineToPoint(timeline_pre_comp_done_);
+  }
+  int SignalCompositionDone() {
+    return IncreaseTimelineToPoint(timeline_);
+  }
+
+  std::vector<DrmHwcLayer> &layers() {
+    return layers_;
+  }
+
+  std::vector<DrmCompositionRegion> &squash_regions() {
+    return squash_regions_;
+  }
+
+  std::vector<DrmCompositionRegion> &pre_comp_regions() {
+    return pre_comp_regions_;
+  }
+
+  std::vector<DrmCompositionPlane> &composition_planes() {
+    return composition_planes_;
+  }
+
+  uint64_t frame_no() const {
+    return frame_no_;
+  }
+
+  DrmCompositionType type() const {
+    return type_;
+  }
+
+  uint32_t dpms_mode() const {
+    return dpms_mode_;
+  }
+
+  const DrmMode &display_mode() const {
+    return display_mode_;
+  }
+
+  DrmCrtc *crtc() const {
+    return crtc_;
+  }
+
+  Importer *importer() const {
+    return importer_;
+  }
+
+  void Dump(std::ostringstream *out) const;
+
+ private:
+  bool validate_composition_type(DrmCompositionType desired);
+
   int IncreaseTimelineToPoint(int point);
 
-  DrmResources *drm_;
-  DrmCrtc *crtc_;
-  Importer *importer_;
-  const gralloc_module_t *gralloc_;
-  EGLDisplay egl_display_;
+  int CreateAndAssignReleaseFences();
 
-  DrmCompositionType type_;
+  DrmResources *drm_ = NULL;
+  DrmCrtc *crtc_ = NULL;
+  Importer *importer_ = NULL;
 
-  int timeline_fd_;
-  int timeline_;
-  int timeline_current_;
-  int timeline_pre_comp_done_;
-
-  std::vector<DrmCompositionLayer> layers_;
-  int pre_composition_layer_index_;
-  uint32_t dpms_mode_;
+  DrmCompositionType type_ = DRM_COMPOSITION_TYPE_EMPTY;
+  uint32_t dpms_mode_ = DRM_MODE_DPMS_ON;
   DrmMode display_mode_;
 
-  uint64_t frame_no_;
+  int timeline_fd_ = -1;
+  int timeline_ = 0;
+  int timeline_current_ = 0;
+  int timeline_squash_done_ = 0;
+  int timeline_pre_comp_done_ = 0;
+
+  bool geometry_changed_;
+  std::vector<DrmHwcLayer> layers_;
+  std::vector<DrmCompositionRegion> squash_regions_;
+  std::vector<DrmCompositionRegion> pre_comp_regions_;
+  std::vector<DrmCompositionPlane> composition_planes_;
+
+  uint64_t frame_no_ = 0;
 };
 }
 
diff --git a/drmdisplaycompositor.cpp b/drmdisplaycompositor.cpp
index 5676f1d..3f41fca 100644
--- a/drmdisplaycompositor.cpp
+++ b/drmdisplaycompositor.cpp
@@ -23,7 +23,6 @@
 #include "drmresources.h"
 #include "glworker.h"
 
-#include <algorithm>
 #include <pthread.h>
 #include <sched.h>
 #include <sstream>
@@ -36,18 +35,212 @@
 #include <sync/sync.h>
 #include <utils/Trace.h>
 
-#define DRM_DISPLAY_COMPOSITOR_MAX_QUEUE_DEPTH 3
+#define DRM_DISPLAY_COMPOSITOR_MAX_QUEUE_DEPTH 2
 
 namespace android {
 
+void SquashState::Init(DrmHwcLayer *layers, size_t num_layers) {
+  generation_number_++;
+  valid_history_ = 0;
+  regions_.clear();
+  last_handles_.clear();
+
+  std::vector<DrmHwcRect<int>> in_rects;
+  for (size_t i = 0; i < num_layers; i++) {
+    DrmHwcLayer *layer = &layers[i];
+    in_rects.emplace_back(layer->display_frame);
+    last_handles_.push_back(layer->sf_handle);
+  }
+
+  std::vector<seperate_rects::RectSet<uint64_t, int>> out_regions;
+  seperate_rects::seperate_rects_64(in_rects, &out_regions);
+
+  for (const seperate_rects::RectSet<uint64_t, int> &out_region : out_regions) {
+    regions_.emplace_back();
+    Region &region = regions_.back();
+    region.rect = out_region.rect;
+    region.layer_refs = out_region.id_set.getBits();
+  }
+}
+
+void SquashState::GenerateHistory(DrmHwcLayer *layers, size_t num_layers,
+                                  std::vector<bool> &changed_regions) const {
+  changed_regions.resize(regions_.size());
+  if (num_layers != last_handles_.size()) {
+    ALOGE("SquashState::GenerateHistory expected %zu layers but got %zu layers",
+          last_handles_.size(), num_layers);
+    return;
+  }
+  std::bitset<kMaxLayers> changed_layers;
+  for (size_t i = 0; i < last_handles_.size(); i++) {
+    DrmHwcLayer *layer = &layers[i];
+    if (last_handles_[i] != layer->sf_handle) {
+      changed_layers.set(i);
+    }
+  }
+
+  for (size_t i = 0; i < regions_.size(); i++) {
+    changed_regions[i] = (regions_[i].layer_refs & changed_layers).any();
+  }
+}
+
+void SquashState::StableRegionsWithMarginalHistory(
+    const std::vector<bool> &changed_regions,
+    std::vector<bool> &stable_regions) const {
+  stable_regions.resize(regions_.size());
+  for (size_t i = 0; i < regions_.size(); i++) {
+    stable_regions[i] = !changed_regions[i] && is_stable(i);
+  }
+}
+
+void SquashState::RecordHistory(DrmHwcLayer *layers, size_t num_layers,
+                                const std::vector<bool> &changed_regions) {
+  if (num_layers != last_handles_.size()) {
+    ALOGE("SquashState::RecordHistory expected %zu layers but got %zu layers",
+          last_handles_.size(), num_layers);
+    return;
+  }
+  if (changed_regions.size() != regions_.size()) {
+    ALOGE("SquashState::RecordHistory expected %zu regions but got %zu regions",
+          regions_.size(), changed_regions.size());
+    return;
+  }
+
+  for (size_t i = 0; i < last_handles_.size(); i++) {
+    DrmHwcLayer *layer = &layers[i];
+    last_handles_[i] = layer->sf_handle;
+  }
+
+  for (size_t i = 0; i < regions_.size(); i++) {
+    regions_[i].change_history <<= 1;
+    regions_[i].change_history.set(/* LSB */ 0, changed_regions[i]);
+  }
+
+  valid_history_++;
+}
+
+bool SquashState::RecordAndCompareSquashed(
+    const std::vector<bool> &squashed_regions) {
+  if (squashed_regions.size() != regions_.size()) {
+    ALOGE(
+        "SquashState::RecordAndCompareSquashed expected %zu regions but got "
+        "%zu regions",
+        regions_.size(), squashed_regions.size());
+    return false;
+  }
+  bool changed = false;
+  for (size_t i = 0; i < regions_.size(); i++) {
+    if (regions_[i].squashed != squashed_regions[i]) {
+      regions_[i].squashed = squashed_regions[i];
+      changed = true;
+    }
+  }
+  return changed;
+}
+
+void SquashState::Dump(std::ostringstream *out) const {
+  *out << "----SquashState generation=" << generation_number_
+       << " history=" << valid_history_ << "\n"
+       << "    Regions: count=" << regions_.size() << "\n";
+  for (size_t i = 0; i < regions_.size(); i++) {
+    const Region &region = regions_[i];
+    *out << "      [" << i << "]"
+         << " history=" << region.change_history << " rect";
+    region.rect.Dump(out);
+    *out << " layers=(";
+    bool first = true;
+    for (size_t layer_index = 0; layer_index < kMaxLayers; layer_index++) {
+      if ((region.layer_refs &
+           std::bitset<kMaxLayers>((size_t)1 << layer_index))
+              .any()) {
+        if (!first)
+          *out << " ";
+        first = false;
+        *out << layer_index;
+      }
+    }
+    *out << ")";
+    if (region.squashed)
+      *out << " squashed";
+    *out << "\n";
+  }
+}
+
+static bool UsesSquash(const std::vector<DrmCompositionPlane> &comp_planes) {
+  return std::any_of(comp_planes.begin(), comp_planes.end(),
+                     [](const DrmCompositionPlane &plane) {
+                       return plane.source_layer ==
+                              DrmCompositionPlane::kSourceSquash;
+                     });
+}
+
+DrmDisplayCompositor::FrameWorker::FrameWorker(DrmDisplayCompositor *compositor)
+    : Worker("frame-worker", HAL_PRIORITY_URGENT_DISPLAY),
+      compositor_(compositor) {
+}
+
+DrmDisplayCompositor::FrameWorker::~FrameWorker() {
+}
+
+int DrmDisplayCompositor::FrameWorker::Init() {
+  return InitWorker();
+}
+
+void DrmDisplayCompositor::FrameWorker::QueueFrame(
+    std::unique_ptr<DrmDisplayComposition> composition, int status) {
+  Lock();
+  FrameState frame;
+  frame.composition = std::move(composition);
+  frame.status = status;
+  frame_queue_.push(std::move(frame));
+  SignalLocked();
+  Unlock();
+}
+
+void DrmDisplayCompositor::FrameWorker::Routine() {
+  int ret = Lock();
+  if (ret) {
+    ALOGE("Failed to lock worker, %d", ret);
+    return;
+  }
+
+  int wait_ret = 0;
+  if (frame_queue_.empty()) {
+    wait_ret = WaitForSignalOrExitLocked();
+  }
+
+  FrameState frame;
+  if (!frame_queue_.empty()) {
+    frame = std::move(frame_queue_.front());
+    frame_queue_.pop();
+  }
+
+  ret = Unlock();
+  if (ret) {
+    ALOGE("Failed to unlock worker, %d", ret);
+    return;
+  }
+
+  if (wait_ret == -EINTR) {
+    return;
+  } else if (wait_ret) {
+    ALOGE("Failed to wait for signal, %d", wait_ret);
+    return;
+  }
+
+  compositor_->ApplyFrame(std::move(frame.composition), frame.status);
+}
+
 DrmDisplayCompositor::DrmDisplayCompositor()
     : drm_(NULL),
       display_(-1),
       worker_(this),
+      frame_worker_(this),
       initialized_(false),
       active_(false),
       needs_modeset_(false),
       framebuffer_index_(0),
+      squash_framebuffer_index_(0),
       dump_frames_composited_(0),
       dump_last_timestamp_ns_(0) {
   struct timespec ts;
@@ -61,6 +254,7 @@
     return;
 
   worker_.Exit();
+  frame_worker_.Exit();
 
   int ret = pthread_mutex_lock(&lock_);
   if (ret)
@@ -94,11 +288,22 @@
     ALOGE("Failed to initialize compositor worker %d\n", ret);
     return ret;
   }
+  ret = frame_worker_.Init();
+  if (ret) {
+    pthread_mutex_destroy(&lock_);
+    ALOGE("Failed to initialize frame worker %d\n", ret);
+    return ret;
+  }
 
   initialized_ = true;
   return 0;
 }
 
+std::unique_ptr<DrmDisplayComposition> DrmDisplayCompositor::CreateComposition()
+    const {
+  return std::unique_ptr<DrmDisplayComposition>(new DrmDisplayComposition());
+}
+
 int DrmDisplayCompositor::QueueComposition(
     std::unique_ptr<DrmDisplayComposition> composition) {
   switch (composition->type()) {
@@ -148,94 +353,248 @@
   return 0;
 }
 
-static bool drm_composition_layer_has_plane(
-    const DrmCompositionLayer &comp_layer) {
-  if (comp_layer.plane != NULL)
-    if (comp_layer.plane->type() == DRM_PLANE_TYPE_OVERLAY ||
-        comp_layer.plane->type() == DRM_PLANE_TYPE_PRIMARY)
-      return true;
-  return false;
+std::tuple<uint32_t, uint32_t, int>
+DrmDisplayCompositor::GetActiveModeResolution() {
+  DrmConnector *connector = drm_->GetConnectorForDisplay(display_);
+  if (connector == NULL) {
+    ALOGE("Failed to determine display mode: no connector for display %d",
+          display_);
+    return std::make_tuple(0, 0, -ENODEV);
+  }
+
+  const DrmMode &mode = connector->active_mode();
+  return std::make_tuple(mode.h_display(), mode.v_display(), 0);
 }
 
-static bool drm_composition_layer_has_no_plane(
-    const DrmCompositionLayer &comp_layer) {
-  return comp_layer.plane == NULL;
+int DrmDisplayCompositor::PrepareFramebuffer(
+    DrmFramebuffer &fb, DrmDisplayComposition *display_comp) {
+  int ret = fb.WaitReleased(-1);
+  if (ret) {
+    ALOGE("Failed to wait for framebuffer release %d", ret);
+    return ret;
+  }
+  uint32_t width, height;
+  std::tie(width, height, ret) = GetActiveModeResolution();
+  if (ret) {
+    ALOGE(
+        "Failed to allocate framebuffer because the display resolution could "
+        "not be determined %d",
+        ret);
+    return ret;
+  }
+
+  fb.set_release_fence_fd(-1);
+  if (!fb.Allocate(width, height)) {
+    ALOGE("Failed to allocate framebuffer with size %dx%d", width, height);
+    return -ENOMEM;
+  }
+
+  display_comp->layers().emplace_back();
+  DrmHwcLayer &pre_comp_layer = display_comp->layers().back();
+  pre_comp_layer.sf_handle = fb.buffer()->handle;
+  pre_comp_layer.source_crop = DrmHwcRect<float>(0, 0, width, height);
+  pre_comp_layer.display_frame = DrmHwcRect<int>(0, 0, width, height);
+  ret = pre_comp_layer.buffer.ImportBuffer(fb.buffer()->handle,
+                                           display_comp->importer());
+  if (ret) {
+    ALOGE("Failed to import framebuffer for display %d", ret);
+    return ret;
+  }
+
+  return ret;
+}
+
+int DrmDisplayCompositor::ApplySquash(DrmDisplayComposition *display_comp) {
+  int ret = 0;
+
+  DrmFramebuffer &fb = squash_framebuffers_[squash_framebuffer_index_];
+  ret = PrepareFramebuffer(fb, display_comp);
+  if (ret) {
+    ALOGE("Failed to prepare framebuffer for squash %d", ret);
+    return ret;
+  }
+
+  std::vector<DrmCompositionRegion> &regions = display_comp->squash_regions();
+  ret = pre_compositor_->Composite(display_comp->layers().data(),
+                                   regions.data(), regions.size(), fb.buffer());
+  pre_compositor_->Finish();
+
+  if (ret) {
+    ALOGE("Failed to squash layers");
+    return ret;
+  }
+
+  ret = display_comp->CreateNextTimelineFence();
+  if (ret <= 0) {
+    ALOGE("Failed to create squash framebuffer release fence %d", ret);
+    return ret;
+  }
+
+  fb.set_release_fence_fd(ret);
+  display_comp->SignalSquashDone();
+
+  return 0;
 }
 
 int DrmDisplayCompositor::ApplyPreComposite(
     DrmDisplayComposition *display_comp) {
   int ret = 0;
-  std::vector<DrmCompositionLayer> *layers =
-      display_comp->GetCompositionLayers();
 
-  DrmConnector *connector = drm_->GetConnectorForDisplay(display_);
-  if (connector == NULL) {
-    ALOGE("Failed to determine display mode: no connector for display %d",
-          display_);
-    return -ENODEV;
-  }
-
-  const DrmMode &mode = connector->active_mode();
   DrmFramebuffer &fb = framebuffers_[framebuffer_index_];
-  ret = fb.WaitReleased(fb.kReleaseWaitTimeoutMs);
+  ret = PrepareFramebuffer(fb, display_comp);
   if (ret) {
-    ALOGE("Failed to wait for framebuffer release %d", ret);
+    ALOGE("Failed to prepare framebuffer for pre-composite %d", ret);
     return ret;
   }
-  fb.set_release_fence_fd(-1);
-  if (!fb.Allocate(mode.h_display(), mode.v_display())) {
-    ALOGE("Failed to allocate framebuffer with size %dx%d", mode.h_display(),
-          mode.v_display());
-    return -ENOMEM;
-  }
 
-  std::vector<DrmCompositionLayer> pre_comp_layers;
-  for (auto &comp_layer : *layers) {
-    if (comp_layer.plane == NULL) {
-      pre_comp_layers.emplace_back(std::move(comp_layer));
-    }
-  }
-
-  ret = pre_compositor_->Composite(pre_comp_layers.data(),
-                                   pre_comp_layers.size(), fb.buffer());
+  std::vector<DrmCompositionRegion> &regions = display_comp->pre_comp_regions();
+  ret = pre_compositor_->Composite(display_comp->layers().data(),
+                                   regions.data(), regions.size(), fb.buffer());
   pre_compositor_->Finish();
 
   if (ret) {
-    ALOGE("Failed to composite layers");
+    ALOGE("Failed to pre-composite layers");
     return ret;
   }
 
-  DrmCompositionLayer &pre_comp_layer =
-      layers->at(display_comp->pre_composition_layer_index());
-  ret = pre_comp_layer.buffer.ImportBuffer(fb.buffer()->handle,
-                                           display_comp->importer());
-  if (ret) {
-    ALOGE("Failed to import handle of layer %d", ret);
+  ret = display_comp->CreateNextTimelineFence();
+  if (ret <= 0) {
+    ALOGE("Failed to create pre-composite framebuffer release fence %d", ret);
     return ret;
   }
-  pre_comp_layer.source_crop = DrmHwcRect<float>(0, 0, fb.buffer()->getWidth(),
-                                                 fb.buffer()->getHeight());
-  pre_comp_layer.display_frame =
-      DrmHwcRect<int>(0, 0, fb.buffer()->getWidth(), fb.buffer()->getHeight());
 
-  // TODO(zachr) get a release fence
-  // fb.set_release_fence_fd(pre_comp_layer.release_fence.Release());
-  framebuffer_index_ = (framebuffer_index_ + 1) % DRM_DISPLAY_BUFFERS;
+  fb.set_release_fence_fd(ret);
+  display_comp->SignalPreCompDone();
 
-  display_comp->RemoveNoPlaneLayers();
-  display_comp->SignalPreCompositionDone();
-  return ret;
+  return 0;
 }
 
-int DrmDisplayCompositor::ApplyFrame(DrmDisplayComposition *display_comp) {
+int DrmDisplayCompositor::DisablePlanes(DrmDisplayComposition *display_comp) {
+  drmModePropertySetPtr pset = drmModePropertySetAlloc();
+  if (!pset) {
+    ALOGE("Failed to allocate property set");
+    return -ENOMEM;
+  }
+
+  int ret;
+  std::vector<DrmCompositionPlane> &comp_planes =
+      display_comp->composition_planes();
+  for (DrmCompositionPlane &comp_plane : comp_planes) {
+    DrmPlane *plane = comp_plane.plane;
+    ret =
+        drmModePropertySetAdd(pset, plane->id(), plane->crtc_property().id(),
+                              0) ||
+        drmModePropertySetAdd(pset, plane->id(), plane->fb_property().id(), 0);
+    if (ret) {
+      ALOGE("Failed to add plane %d disable to pset", plane->id());
+      drmModePropertySetFree(pset);
+      return ret;
+    }
+  }
+
+  ret = drmModePropertySetCommit(drm_->fd(), 0, drm_, pset);
+  if (ret) {
+    ALOGE("Failed to commit pset ret=%d\n", ret);
+    drmModePropertySetFree(pset);
+    return ret;
+  }
+
+  drmModePropertySetFree(pset);
+  return 0;
+}
+
+int DrmDisplayCompositor::PrepareFrame(DrmDisplayComposition *display_comp) {
   int ret = 0;
 
-  if (display_comp->pre_composition_layer_index() >= 0) {
+  std::vector<DrmHwcLayer> &layers = display_comp->layers();
+  std::vector<DrmCompositionPlane> &comp_planes =
+      display_comp->composition_planes();
+  std::vector<DrmCompositionRegion> &squash_regions =
+      display_comp->squash_regions();
+  std::vector<DrmCompositionRegion> &pre_comp_regions =
+      display_comp->pre_comp_regions();
+
+  int squash_layer_index = -1;
+  if (squash_regions.size() > 0) {
+    squash_framebuffer_index_ = (squash_framebuffer_index_ + 1) % 2;
+    ret = ApplySquash(display_comp);
+    if (ret)
+      return ret;
+
+    squash_layer_index = layers.size() - 1;
+  } else {
+    if (UsesSquash(comp_planes)) {
+      DrmFramebuffer &fb = squash_framebuffers_[squash_framebuffer_index_];
+      layers.emplace_back();
+      squash_layer_index = layers.size() - 1;
+      DrmHwcLayer &squash_layer = layers.back();
+      ret = squash_layer.buffer.ImportBuffer(fb.buffer()->handle,
+                                             display_comp->importer());
+      if (ret) {
+        ALOGE("Failed to import old squashed framebuffer %d", ret);
+        return ret;
+      }
+      squash_layer.sf_handle = fb.buffer()->handle;
+      squash_layer.source_crop = DrmHwcRect<float>(
+          0, 0, squash_layer.buffer->width, squash_layer.buffer->height);
+      squash_layer.display_frame = DrmHwcRect<int>(
+          0, 0, squash_layer.buffer->width, squash_layer.buffer->height);
+      ret = display_comp->CreateNextTimelineFence();
+
+      if (ret <= 0) {
+        ALOGE("Failed to create squash framebuffer release fence %d", ret);
+        return ret;
+      }
+
+      fb.set_release_fence_fd(ret);
+      ret = 0;
+    }
+  }
+
+  bool do_pre_comp = pre_comp_regions.size() > 0;
+  int pre_comp_layer_index = -1;
+  if (do_pre_comp) {
     ret = ApplyPreComposite(display_comp);
     if (ret)
       return ret;
+
+    pre_comp_layer_index = layers.size() - 1;
+    framebuffer_index_ = (framebuffer_index_ + 1) % DRM_DISPLAY_BUFFERS;
   }
 
+  for (DrmCompositionPlane &comp_plane : comp_planes) {
+    switch (comp_plane.source_layer) {
+      case DrmCompositionPlane::kSourceSquash:
+        comp_plane.source_layer = squash_layer_index;
+        break;
+      case DrmCompositionPlane::kSourcePreComp:
+        if (!do_pre_comp) {
+          ALOGE(
+              "Can not use pre composite framebuffer with no pre composite "
+              "regions");
+          return -EINVAL;
+        }
+        comp_plane.source_layer = pre_comp_layer_index;
+        break;
+      default:
+        break;
+    }
+  }
+
+  return ret;
+}
+
+int DrmDisplayCompositor::CommitFrame(DrmDisplayComposition *display_comp) {
+  int ret = 0;
+
+  std::vector<DrmHwcLayer> &layers = display_comp->layers();
+  std::vector<DrmCompositionPlane> &comp_planes =
+      display_comp->composition_planes();
+  std::vector<DrmCompositionRegion> &pre_comp_regions =
+      display_comp->pre_comp_regions();
+
+  DrmFramebuffer *pre_comp_fb;
+
   DrmConnector *connector = drm_->GetConnectorForDisplay(display_);
   if (!connector) {
     ALOGE("Could not locate connector for display %d", display_);
@@ -295,25 +654,83 @@
     }
   }
 
-  std::vector<DrmCompositionLayer> *layers =
-      display_comp->GetCompositionLayers();
-  for (DrmCompositionLayer &layer : *layers) {
-    int acquire_fence = layer.acquire_fence.get();
-    if (acquire_fence >= 0) {
-      ret = sync_wait(acquire_fence, kAcquireWaitTimeoutMs);
-      if (ret) {
-        ALOGE("Failed to wait for acquire %d/%d", acquire_fence, ret);
-        drmModePropertySetFree(pset);
-        drm_->DestroyPropertyBlob(blob_id);
-        return ret;
+  for (DrmCompositionPlane &comp_plane : comp_planes) {
+    DrmPlane *plane = comp_plane.plane;
+    DrmCrtc *crtc = comp_plane.crtc;
+
+    int fb_id = -1;
+    DrmHwcRect<int> display_frame;
+    DrmHwcRect<float> source_crop;
+    uint64_t rotation = 0;
+    uint64_t alpha = 0xFF;
+    switch (comp_plane.source_layer) {
+      case DrmCompositionPlane::kSourceNone:
+        break;
+      case DrmCompositionPlane::kSourceSquash:
+        ALOGE("Actual source layer index expected for squash layer");
+        break;
+      case DrmCompositionPlane::kSourcePreComp:
+        ALOGE("Actual source layer index expected for pre-comp layer");
+        break;
+      default: {
+        if (comp_plane.source_layer >= layers.size()) {
+          ALOGE("Source layer index %zu out of bounds %zu",
+                comp_plane.source_layer, layers.size());
+          break;
+        }
+        DrmHwcLayer &layer = layers[comp_plane.source_layer];
+        if (layer.acquire_fence.get() >= 0) {
+          int acquire_fence = layer.acquire_fence.get();
+          for (int i = 0; i < kAcquireWaitTries; ++i) {
+            ret = sync_wait(acquire_fence, kAcquireWaitTimeoutMs);
+            if (ret)
+              ALOGW("Acquire fence %d wait %d failed (%d). Total time %d",
+                    acquire_fence, i, ret, (i + 1) * kAcquireWaitTimeoutMs);
+          }
+          if (ret) {
+            ALOGE("Failed to wait for acquire %d/%d", acquire_fence, ret);
+            break;
+          }
+          layer.acquire_fence.Close();
+        }
+        if (!layer.buffer) {
+          ALOGE("Expected a valid framebuffer for pset");
+          break;
+        }
+        fb_id = layer.buffer->fb_id;
+        display_frame = layer.display_frame;
+        source_crop = layer.source_crop;
+        if (layer.blending == DrmHwcBlending::kPreMult)
+          alpha = layer.alpha;
+        switch (layer.transform) {
+          case DrmHwcTransform::kFlipH:
+            rotation = 1 << DRM_REFLECT_X;
+            break;
+          case DrmHwcTransform::kFlipV:
+            rotation = 1 << DRM_REFLECT_Y;
+            break;
+          case DrmHwcTransform::kRotate90:
+            rotation = 1 << DRM_ROTATE_90;
+            break;
+          case DrmHwcTransform::kRotate180:
+            rotation = 1 << DRM_ROTATE_180;
+            break;
+          case DrmHwcTransform::kRotate270:
+            rotation = 1 << DRM_ROTATE_270;
+            break;
+          case DrmHwcTransform::kIdentity:
+            rotation = 0;
+            break;
+          default:
+            ALOGE("Invalid transform value 0x%x given", layer.transform);
+            break;
+        }
+        break;
       }
-      layer.acquire_fence.Close();
     }
 
-    DrmPlane *plane = layer.plane;
-
-    // Disable the plane if there's no crtc
-    if (!layer.crtc) {
+    // Disable the plane if there's no framebuffer
+    if (fb_id < 0) {
       ret = drmModePropertySetAdd(pset, plane->id(),
                                   plane->crtc_property().id(), 0) ||
             drmModePropertySetAdd(pset, plane->id(), plane->fb_property().id(),
@@ -325,40 +742,6 @@
       continue;
     }
 
-    if (!layer.buffer) {
-      ALOGE("Expected a valid framebuffer for pset");
-      ret = -EINVAL;
-      break;
-    }
-
-    uint64_t rotation;
-    switch (layer.transform) {
-      case DrmHwcTransform::kFlipH:
-        rotation = 1 << DRM_REFLECT_X;
-        break;
-      case DrmHwcTransform::kFlipV:
-        rotation = 1 << DRM_REFLECT_Y;
-        break;
-      case DrmHwcTransform::kRotate90:
-        rotation = 1 << DRM_ROTATE_90;
-        break;
-      case DrmHwcTransform::kRotate180:
-        rotation = 1 << DRM_ROTATE_180;
-        break;
-      case DrmHwcTransform::kRotate270:
-        rotation = 1 << DRM_ROTATE_270;
-        break;
-      case DrmHwcTransform::kIdentity:
-        rotation = 0;
-        break;
-      default:
-        ALOGE("Invalid transform value 0x%x given", layer.transform);
-        ret = -EINVAL;
-        break;
-    }
-    if (ret)
-      break;
-
     // TODO: Once we have atomic test, this should fall back to GL
     if (rotation && plane->rotation_property().id() == 0) {
       ALOGE("Rotation is not supported on plane %d", plane->id());
@@ -366,31 +749,36 @@
       break;
     }
 
+    // TODO: Once we have atomic test, this should fall back to GL
+    if (alpha != 0xFF && plane->alpha_property().id() == 0) {
+      ALOGE("Alpha is not supported on plane %d", plane->id());
+      ret = -EINVAL;
+      break;
+    }
+
     ret =
         drmModePropertySetAdd(pset, plane->id(), plane->crtc_property().id(),
-                              layer.crtc->id()) ||
+                              crtc->id()) ||
         drmModePropertySetAdd(pset, plane->id(), plane->fb_property().id(),
-                              layer.buffer->fb_id) ||
+                              fb_id) ||
         drmModePropertySetAdd(pset, plane->id(), plane->crtc_x_property().id(),
-                              layer.display_frame.left) ||
+                              display_frame.left) ||
         drmModePropertySetAdd(pset, plane->id(), plane->crtc_y_property().id(),
-                              layer.display_frame.top) ||
-        drmModePropertySetAdd(
-            pset, plane->id(), plane->crtc_w_property().id(),
-            layer.display_frame.right - layer.display_frame.left) ||
-        drmModePropertySetAdd(
-            pset, plane->id(), plane->crtc_h_property().id(),
-            layer.display_frame.bottom - layer.display_frame.top) ||
+                              display_frame.top) ||
+        drmModePropertySetAdd(pset, plane->id(), plane->crtc_w_property().id(),
+                              display_frame.right - display_frame.left) ||
+        drmModePropertySetAdd(pset, plane->id(), plane->crtc_h_property().id(),
+                              display_frame.bottom - display_frame.top) ||
         drmModePropertySetAdd(pset, plane->id(), plane->src_x_property().id(),
-                              (int)(layer.source_crop.left) << 16) ||
+                              (int)(source_crop.left) << 16) ||
         drmModePropertySetAdd(pset, plane->id(), plane->src_y_property().id(),
-                              (int)(layer.source_crop.top) << 16) ||
-        drmModePropertySetAdd(
-            pset, plane->id(), plane->src_w_property().id(),
-            (int)(layer.source_crop.right - layer.source_crop.left) << 16) ||
-        drmModePropertySetAdd(
-            pset, plane->id(), plane->src_h_property().id(),
-            (int)(layer.source_crop.bottom - layer.source_crop.top) << 16);
+                              (int)(source_crop.top) << 16) ||
+        drmModePropertySetAdd(pset, plane->id(), plane->src_w_property().id(),
+                              (int)(source_crop.right - source_crop.left)
+                                  << 16) ||
+        drmModePropertySetAdd(pset, plane->id(), plane->src_h_property().id(),
+                              (int)(source_crop.bottom - source_crop.top)
+                                  << 16);
     if (ret) {
       ALOGE("Failed to add plane %d to set", plane->id());
       break;
@@ -405,8 +793,19 @@
         break;
       }
     }
+
+    if (plane->alpha_property().id()) {
+      ret = drmModePropertySetAdd(pset, plane->id(),
+                                  plane->alpha_property().id(), alpha);
+      if (ret) {
+        ALOGE("Failed to add alpha property %d to plane %d",
+              plane->alpha_property().id(), plane->id());
+        break;
+      }
+    }
   }
 
+out:
   if (!ret) {
     ret = drmModePropertySetCommit(drm_->fd(), DRM_MODE_ATOMIC_ALLOW_MODESET,
                                    drm_, pset);
@@ -460,6 +859,38 @@
   return 0;
 }
 
+void DrmDisplayCompositor::ApplyFrame(
+    std::unique_ptr<DrmDisplayComposition> composition, int status) {
+  int ret = status;
+
+  if (!ret)
+    ret = CommitFrame(composition.get());
+
+  if (ret) {
+    ALOGE("Composite failed for display %d", display_);
+
+    // Disable the hw used by the last active composition. This allows us to
+    // signal the release fences from that composition to avoid hanging.
+    if (DisablePlanes(active_composition_.get()))
+      return;
+  }
+  ++dump_frames_composited_;
+
+  if (active_composition_)
+    active_composition_->SignalCompositionDone();
+
+  ret = pthread_mutex_lock(&lock_);
+  if (ret)
+    ALOGE("Failed to acquire lock for active_composition swap");
+
+  active_composition_.swap(composition);
+
+  if (!ret)
+    ret = pthread_mutex_unlock(&lock_);
+  if (ret)
+    ALOGE("Failed to release lock for active_composition swap");
+}
+
 int DrmDisplayCompositor::Composite() {
   ATRACE_CALL();
 
@@ -497,12 +928,8 @@
 
   switch (composition->type()) {
     case DRM_COMPOSITION_TYPE_FRAME:
-      ret = ApplyFrame(composition.get());
-      if (ret) {
-        ALOGE("Composite failed for display %d", display_);
-        return ret;
-      }
-      ++dump_frames_composited_;
+      ret = PrepareFrame(composition.get());
+      frame_worker_.QueueFrame(std::move(composition), ret);
       break;
     case DRM_COMPOSITION_TYPE_DPMS:
       ret = ApplyDpms(composition.get());
@@ -518,20 +945,6 @@
       return -EINVAL;
   }
 
-  if (active_composition_)
-    active_composition_->FinishComposition();
-
-  ret = pthread_mutex_lock(&lock_);
-  if (ret)
-    ALOGE("Failed to acquire lock for active_composition swap");
-
-  active_composition_.swap(composition);
-
-  if (!ret)
-    ret = pthread_mutex_unlock(&lock_);
-  if (ret)
-    ALOGE("Failed to release lock for active_composition swap");
-
   return ret;
 }
 
@@ -553,25 +966,7 @@
   return empty_ret;
 }
 
-struct DrmDumpLayer {
-  int plane_id;
-  int crtc_id;
-  DrmHwcTransform transform;
-  DrmHwcRect<float> source_crop;
-  DrmHwcRect<int> display_frame;
-
-  DrmDumpLayer(DrmCompositionLayer &rhs)
-      : plane_id(rhs.plane->id()),
-        crtc_id(rhs.crtc ? rhs.crtc->id() : -1),
-        transform(rhs.transform),
-        source_crop(rhs.source_crop),
-        display_frame(rhs.display_frame) {
-  }
-};
-
 void DrmDisplayCompositor::Dump(std::ostringstream *out) const {
-  uint64_t cur_ts;
-
   int ret = pthread_mutex_lock(&lock_);
   if (ret)
     return;
@@ -581,20 +976,12 @@
 
   struct timespec ts;
   ret = clock_gettime(CLOCK_MONOTONIC, &ts);
-
-  std::vector<DrmCompositionLayer> *input_layers =
-      active_composition_->GetCompositionLayers();
-  std::vector<DrmDumpLayer> layers;
-  if (active_composition_)
-    layers.insert(layers.begin(), input_layers->begin(), input_layers->end());
-  else
-    ret = -EAGAIN;
-
-  ret |= pthread_mutex_unlock(&lock_);
-  if (ret)
+  if (ret) {
+    pthread_mutex_unlock(&lock_);
     return;
+  }
 
-  cur_ts = ts.tv_sec * 1000 * 1000 * 1000 + ts.tv_nsec;
+  uint64_t cur_ts = ts.tv_sec * 1000 * 1000 * 1000 + ts.tv_nsec;
   uint64_t num_ms = (cur_ts - dump_last_timestamp_ns_) / (1000 * 1000);
   float fps = num_ms ? (num_frames * 1000.0f) / (num_ms) : 0.0f;
 
@@ -604,27 +991,11 @@
 
   dump_last_timestamp_ns_ = cur_ts;
 
-  *out << "---- DrmDisplayCompositor Layers: num=" << layers.size() << "\n";
-  for (std::vector<DrmDumpLayer>::iterator iter = layers.begin();
-       iter != layers.end(); ++iter) {
-    *out << "------ DrmDisplayCompositor Layer: plane=" << iter->plane_id
-         << " ";
+  if (active_composition_)
+    active_composition_->Dump(out);
 
-    if (iter->crtc_id < 0) {
-      *out << "disabled\n";
-      continue;
-    }
+  squash_state_.Dump(out);
 
-    *out << "crtc=" << iter->crtc_id
-         << " crtc[x/y/w/h]=" << iter->display_frame.left << "/"
-         << iter->display_frame.top << "/"
-         << iter->display_frame.right - iter->display_frame.left << "/"
-         << iter->display_frame.bottom - iter->display_frame.top << " "
-         << " src[x/y/w/h]=" << iter->source_crop.left << "/"
-         << iter->source_crop.top << "/"
-         << iter->source_crop.right - iter->source_crop.left << "/"
-         << iter->source_crop.bottom - iter->source_crop.top
-         << " transform=" << (uint32_t)iter->transform << "\n";
-  }
+  pthread_mutex_unlock(&lock_);
 }
 }
diff --git a/drmdisplaycompositor.h b/drmdisplaycompositor.h
index 0f3a12f..e756fde 100644
--- a/drmdisplaycompositor.h
+++ b/drmdisplaycompositor.h
@@ -21,10 +21,13 @@
 #include "drmcomposition.h"
 #include "drmcompositorworker.h"
 #include "drmframebuffer.h"
+#include "seperate_rects.h"
 
 #include <pthread.h>
+#include <memory>
 #include <queue>
 #include <sstream>
+#include <tuple>
 
 #include <hardware/hardware.h>
 #include <hardware/hwcomposer.h>
@@ -35,6 +38,47 @@
 
 class GLWorkerCompositor;
 
+class SquashState {
+ public:
+  static const unsigned kHistoryLength = 6;  // TODO: make this number not magic
+  static const unsigned kMaxLayers = 64;
+
+  struct Region {
+    DrmHwcRect<int> rect;
+    std::bitset<kMaxLayers> layer_refs;
+    std::bitset<kHistoryLength> change_history;
+    bool squashed = false;
+  };
+
+  bool is_stable(int region_index) const {
+    return valid_history_ >= kHistoryLength &&
+           regions_[region_index].change_history.none();
+  }
+
+  const std::vector<Region> &regions() const {
+    return regions_;
+  }
+
+  void Init(DrmHwcLayer *layers, size_t num_layers);
+  void GenerateHistory(DrmHwcLayer *layers, size_t num_layers,
+                       std::vector<bool> &changed_regions) const;
+  void StableRegionsWithMarginalHistory(
+      const std::vector<bool> &changed_regions,
+      std::vector<bool> &stable_regions) const;
+  void RecordHistory(DrmHwcLayer *layers, size_t num_layers,
+                     const std::vector<bool> &changed_regions);
+  bool RecordAndCompareSquashed(const std::vector<bool> &squashed_regions);
+
+  void Dump(std::ostringstream *out) const;
+
+ private:
+  size_t generation_number_ = 0;
+  unsigned valid_history_ = 0;
+  std::vector<buffer_handle_t> last_handles_;
+
+  std::vector<Region> regions_;
+};
+
 class DrmDisplayCompositor {
  public:
   DrmDisplayCompositor();
@@ -42,27 +86,66 @@
 
   int Init(DrmResources *drm, int display);
 
+  std::unique_ptr<DrmDisplayComposition> CreateComposition() const;
   int QueueComposition(std::unique_ptr<DrmDisplayComposition> composition);
   int Composite();
   void Dump(std::ostringstream *out) const;
 
+  std::tuple<uint32_t, uint32_t, int> GetActiveModeResolution();
+
   bool HaveQueuedComposites() const;
 
+  SquashState *squash_state() {
+    return &squash_state_;
+  }
+
  private:
+  struct FrameState {
+    std::unique_ptr<DrmDisplayComposition> composition;
+    int status = 0;
+  };
+
+  class FrameWorker : public Worker {
+   public:
+    FrameWorker(DrmDisplayCompositor *compositor);
+    ~FrameWorker() override;
+
+    int Init();
+    void QueueFrame(std::unique_ptr<DrmDisplayComposition> composition,
+                    int status);
+
+   protected:
+    void Routine() override;
+
+   private:
+    DrmDisplayCompositor *compositor_;
+    std::queue<FrameState> frame_queue_;
+  };
+
   DrmDisplayCompositor(const DrmDisplayCompositor &) = delete;
 
-  // Set to 83ms (~12fps) which is somewhere between a reasonable amount of
-  // time to wait for a long render and a small enough delay to limit jank.
-  static const int kAcquireWaitTimeoutMs = 83;
+  // We'll wait for acquire fences to fire for kAcquireWaitTimeoutMs,
+  // kAcquireWaitTries times, logging a warning in between.
+  static const int kAcquireWaitTries = 5;
+  static const int kAcquireWaitTimeoutMs = 100;
 
+  int PrepareFramebuffer(DrmFramebuffer &fb,
+                         DrmDisplayComposition *display_comp);
+  int ApplySquash(DrmDisplayComposition *display_comp);
   int ApplyPreComposite(DrmDisplayComposition *display_comp);
-  int ApplyFrame(DrmDisplayComposition *display_comp);
+  int PrepareFrame(DrmDisplayComposition *display_comp);
+  int CommitFrame(DrmDisplayComposition *display_comp);
   int ApplyDpms(DrmDisplayComposition *display_comp);
+  int DisablePlanes(DrmDisplayComposition *display_comp);
+
+  void ApplyFrame(std::unique_ptr<DrmDisplayComposition> composition,
+                  int status);
 
   DrmResources *drm_;
   int display_;
 
   DrmCompositorWorker worker_;
+  FrameWorker frame_worker_;
 
   std::queue<std::unique_ptr<DrmDisplayComposition>> composite_queue_;
   std::unique_ptr<DrmDisplayComposition> active_composition_;
@@ -77,6 +160,10 @@
   DrmFramebuffer framebuffers_[DRM_DISPLAY_BUFFERS];
   std::unique_ptr<GLWorkerCompositor> pre_compositor_;
 
+  SquashState squash_state_;
+  int squash_framebuffer_index_;
+  DrmFramebuffer squash_framebuffers_[2];
+
   // mutable since we need to acquire in HaveQueuedComposites
   mutable pthread_mutex_t lock_;
 
diff --git a/drmgenericimporter.h b/drmgenericimporter.h
index d509ed8..c362c17 100644
--- a/drmgenericimporter.h
+++ b/drmgenericimporter.h
@@ -27,12 +27,12 @@
 class DrmGenericImporter : public Importer {
  public:
   DrmGenericImporter(DrmResources *drm);
-  virtual ~DrmGenericImporter();
+  ~DrmGenericImporter() override;
 
   int Init();
 
-  virtual int ImportBuffer(buffer_handle_t handle, hwc_drm_bo_t *bo);
-  virtual int ReleaseBuffer(hwc_drm_bo_t *bo);
+  int ImportBuffer(buffer_handle_t handle, hwc_drm_bo_t *bo) override;
+  int ReleaseBuffer(hwc_drm_bo_t *bo) override;
 
  private:
   uint32_t ConvertHalFormatToDrm(uint32_t hal_format);
diff --git a/drmhwcgralloc.h b/drmhwcgralloc.h
new file mode 100644
index 0000000..765c897
--- /dev/null
+++ b/drmhwcgralloc.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_DRMHWCGRALLOC_H_
+#define ANDROID_DRMHWCGRALLOC_H_
+
+#include <stdint.h>
+
+enum {
+  /* perform(const struct gralloc_module_t *mod,
+   *	   int op,
+   *	   int drm_fd,
+   *	   buffer_handle_t buffer,
+   *	   struct hwc_drm_bo *bo);
+   */
+  GRALLOC_MODULE_PERFORM_DRM_IMPORT = 0xffeeff00,
+
+  /* perform(const struct gralloc_module_t *mod,
+   *	   int op,
+   *	   buffer_handle_t buffer,
+   *	   void (*free_callback)(void *),
+   *	   void *priv);
+   */
+  GRALLOC_MODULE_PERFORM_SET_IMPORTER_PRIVATE = 0xffeeff01,
+
+  /* perform(const struct gralloc_module_t *mod,
+   *	   int op,
+   *	   buffer_handle_t buffer,
+   *	   void (*free_callback)(void *),
+   *	   void **priv);
+   */
+  GRALLOC_MODULE_PERFORM_GET_IMPORTER_PRIVATE = 0xffeeff02,
+
+  /* perform(const struct gralloc_module_t *mod,
+   *     int op,
+   *     buffer_handle_t buffer,
+   *     int *usage);
+   */
+  GRALLOC_MODULE_PERFORM_GET_USAGE = 0xffeeff03,
+};
+
+typedef struct hwc_drm_bo {
+  uint32_t width;
+  uint32_t height;
+  uint32_t format; /* DRM_FORMAT_* from drm_fourcc.h */
+  uint32_t pitches[4];
+  uint32_t offsets[4];
+  uint32_t gem_handles[4];
+  uint32_t fb_id;
+  int acquire_fence_fd;
+  void *priv;
+} hwc_drm_bo_t;
+
+#endif  // ANDROID_DRMHWCGRALLOC_H_
diff --git a/drmmode.cpp b/drmmode.cpp
index 7f8c04d..2b558c9 100644
--- a/drmmode.cpp
+++ b/drmmode.cpp
@@ -36,6 +36,7 @@
       v_sync_end_(m->vsync_end),
       v_total_(m->vtotal),
       v_scan_(m->vscan),
+      v_refresh_(m->vrefresh),
       flags_(m->flags),
       type_(m->type),
       name_(m->name) {
@@ -54,6 +55,7 @@
       v_sync_end_(0),
       v_total_(0),
       v_scan_(0),
+      v_refresh_(0),
       flags_(0),
       type_(0),
       name_("") {
@@ -83,6 +85,7 @@
   m->vsync_end = v_sync_end_;
   m->vtotal = v_total_;
   m->vscan = v_scan_;
+  m->vrefresh = v_refresh_;
   m->flags = flags_;
   m->type = type_;
   strncpy(m->name, name_.c_str(), DRM_DISPLAY_MODE_LEN);
@@ -141,7 +144,8 @@
 }
 
 float DrmMode::v_refresh() const {
-  return clock_ / (float)(v_total_ * h_total_) * 1000.0f;
+  return v_refresh_ ? v_refresh_ * 1.0f :
+                      clock_ / (float)(v_total_ * h_total_) * 1000.0f;
 }
 
 uint32_t DrmMode::flags() const {
diff --git a/drmmode.h b/drmmode.h
index 3088b7a..1110ce8 100644
--- a/drmmode.h
+++ b/drmmode.h
@@ -71,6 +71,7 @@
   uint32_t v_sync_end_;
   uint32_t v_total_;
   uint32_t v_scan_;
+  uint32_t v_refresh_;
 
   uint32_t flags_;
   uint32_t type_;
diff --git a/drmplane.cpp b/drmplane.cpp
index 3f17d7c..5785d5a 100644
--- a/drmplane.cpp
+++ b/drmplane.cpp
@@ -124,6 +124,10 @@
   if (ret)
     ALOGE("Could not get rotation property");
 
+  ret = drm_->GetPlaneProperty(*this, "alpha", &alpha_property_);
+  if (ret)
+    ALOGI("Could not get alpha property");
+
   return 0;
 }
 
@@ -182,4 +186,8 @@
 const DrmProperty &DrmPlane::rotation_property() const {
   return rotation_property_;
 }
+
+const DrmProperty &DrmPlane::alpha_property() const {
+  return alpha_property_;
+}
 }
diff --git a/drmplane.h b/drmplane.h
index 1969d52..ff3380f 100644
--- a/drmplane.h
+++ b/drmplane.h
@@ -52,6 +52,7 @@
   const DrmProperty &src_w_property() const;
   const DrmProperty &src_h_property() const;
   const DrmProperty &rotation_property() const;
+  const DrmProperty &alpha_property() const;
 
  private:
   DrmPlane(const DrmPlane &);
@@ -74,6 +75,7 @@
   DrmProperty src_w_property_;
   DrmProperty src_h_property_;
   DrmProperty rotation_property_;
+  DrmProperty alpha_property_;
 };
 }
 
diff --git a/glworker.cpp b/glworker.cpp
index 4cbe142..0b98a51 100644
--- a/glworker.cpp
+++ b/glworker.cpp
@@ -20,6 +20,7 @@
 #include <algorithm>
 #include <string>
 #include <sstream>
+#include <unordered_set>
 
 #include <sys/resource.h>
 
@@ -37,8 +38,6 @@
 
 #include "glworker.h"
 
-#include "seperate_rects.h"
-
 // TODO(zachr): use hwc_drm_bo to turn buffer handles into textures
 #ifndef EGL_NATIVE_HANDLE_ANDROID_NVX
 #define EGL_NATIVE_HANDLE_ANDROID_NVX 0x322A
@@ -48,9 +47,6 @@
 
 namespace android {
 
-typedef seperate_rects::Rect<float> FRect;
-typedef seperate_rects::RectSet<uint64_t, float> FRectSet;
-
 // clang-format off
 // Column-major order:
 // float mat[4] = { 1, 2, 3, 4 } ===
@@ -153,13 +149,15 @@
 
 static AutoGLShader CompileAndCheckShader(GLenum type, unsigned source_count,
                                           const GLchar **sources,
-                                          std::string *shader_log) {
+                                          std::ostringstream *shader_log) {
   GLint status;
   AutoGLShader shader(glCreateShader(type));
   if (shader.get() == 0) {
-    *shader_log = "glCreateShader failed";
+    if (shader_log)
+      *shader_log << "Failed glCreateShader call";
     return 0;
   }
+
   glShaderSource(shader.get(), source_count, sources, NULL);
   glCompileShader(shader.get());
   glGetShaderiv(shader.get(), GL_COMPILE_STATUS, &status);
@@ -167,8 +165,14 @@
     if (shader_log) {
       GLint log_length;
       glGetShaderiv(shader.get(), GL_INFO_LOG_LENGTH, &log_length);
-      shader_log->resize(log_length);
-      glGetShaderInfoLog(shader.get(), log_length, NULL, &(*shader_log)[0]);
+      std::string info_log(log_length, ' ');
+      glGetShaderInfoLog(shader.get(), log_length, NULL, &info_log.front());
+      *shader_log << "Failed to compile shader:\n" << info_log.c_str()
+                  << "\nShader Source:\n";
+      for (unsigned i = 0; i < source_count; i++) {
+        *shader_log << sources[i];
+      }
+      *shader_log << "\n";
     }
     return 0;
   }
@@ -176,118 +180,116 @@
   return shader;
 }
 
-static int GenerateShaders(std::vector<AutoGLProgram> *blend_programs) {
-  // Limits: GL_MAX_VARYING_COMPONENTS, GL_MAX_TEXTURE_IMAGE_UNITS,
-  // GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS
-  // clang-format off
-  const GLchar *shader_preamble = "#version 300 es\n#define LAYER_COUNT ";
+static std::string GenerateVertexShader(int layer_count) {
+  std::ostringstream vertex_shader_stream;
+  vertex_shader_stream
+      << "#version 300 es\n"
+      << "#define LAYER_COUNT " << layer_count << "\n"
+      << "precision mediump int;\n"
+      << "uniform vec4 uViewport;\n"
+      << "uniform vec4 uLayerCrop[LAYER_COUNT];\n"
+      << "uniform mat2 uTexMatrix[LAYER_COUNT];\n"
+      << "in vec2 vPosition;\n"
+      << "in vec2 vTexCoords;\n"
+      << "out vec2 fTexCoords[LAYER_COUNT];\n"
+      << "void main() {\n"
+      << "  for (int i = 0; i < LAYER_COUNT; i++) {\n"
+      << "    vec2 tempCoords = vTexCoords * uTexMatrix[i];\n"
+      << "    fTexCoords[i] =\n"
+      << "        uLayerCrop[i].xy + tempCoords * uLayerCrop[i].zw;\n"
+      << "  }\n"
+      << "  vec2 scaledPosition = uViewport.xy + vPosition * uViewport.zw;\n"
+      << "  gl_Position =\n"
+      << "      vec4(scaledPosition * vec2(2.0) - vec2(1.0), 0.0, 1.0);\n"
+      << "}\n";
+  return vertex_shader_stream.str();
+}
 
-  const GLchar *vertex_shader_source =
-"\n"
-"precision mediump int;                                                     \n"
-"uniform vec4 uViewport;                                                    \n"
-"uniform vec4 uLayerCrop[LAYER_COUNT];                                      \n"
-"uniform mat2 uTexMatrix[LAYER_COUNT];                                      \n"
-"in vec2 vPosition;                                                         \n"
-"in vec2 vTexCoords;                                                        \n"
-"out vec2 fTexCoords[LAYER_COUNT];                                          \n"
-"void main() {                                                              \n"
-"  for (int i = 0; i < LAYER_COUNT; i++) {                                  \n"
-"    vec2 tempCoords = vTexCoords * uTexMatrix[i];                          \n"
-"    fTexCoords[i] = uLayerCrop[i].xy + tempCoords * uLayerCrop[i].zw;      \n"
-"  }                                                                        \n"
-"  vec2 scaledPosition = uViewport.xy + vPosition * uViewport.zw;           \n"
-"  gl_Position = vec4(scaledPosition * vec2(2.0) - vec2(1.0), 0.0, 1.0);    \n"
-"}                                                                          \n";
+static std::string GenerateFragmentShader(int layer_count) {
+  std::ostringstream fragment_shader_stream;
+  fragment_shader_stream << "#version 300 es\n"
+                         << "#define LAYER_COUNT " << layer_count << "\n"
+                         << "#extension GL_OES_EGL_image_external : require\n"
+                         << "precision mediump float;\n";
+  for (int i = 0; i < layer_count; ++i) {
+    fragment_shader_stream << "uniform samplerExternalOES uLayerTexture" << i
+                           << ";\n";
+  }
+  fragment_shader_stream << "uniform float uLayerAlpha[LAYER_COUNT];\n"
+                         << "uniform float uLayerPremult[LAYER_COUNT];\n"
+                         << "in vec2 fTexCoords[LAYER_COUNT];\n"
+                         << "out vec4 oFragColor;\n"
+                         << "void main() {\n"
+                         << "  vec3 color = vec3(0.0, 0.0, 0.0);\n"
+                         << "  float alphaCover = 1.0;\n"
+                         << "  vec4 texSample;\n"
+                         << "  vec3 multRgb;\n";
+  for (int i = 0; i < layer_count; ++i) {
+    if (i > 0)
+      fragment_shader_stream << "  if (alphaCover > 0.5/255.0) {\n";
+    // clang-format off
+    fragment_shader_stream
+        << "  texSample = texture2D(uLayerTexture" << i << ",\n"
+        << "                        fTexCoords[" << i << "]);\n"
+        << "  multRgb = texSample.rgb *\n"
+        << "            max(texSample.a, uLayerPremult[" << i << "]);\n"
+        << "  color += multRgb * uLayerAlpha[" << i << "] * alphaCover;\n"
+        << "  alphaCover *= 1.0 - texSample.a * uLayerAlpha[" << i << "];\n";
+    // clang-format on
+  }
+  for (int i = 0; i < layer_count - 1; ++i)
+    fragment_shader_stream << "  }\n";
+  fragment_shader_stream << "  oFragColor = vec4(color, 1.0 - alphaCover);\n"
+                         << "}\n";
+  return fragment_shader_stream.str();
+}
 
-  const GLchar *fragment_shader_source =
-"\n"
-"#extension GL_OES_EGL_image_external : require                             \n"
-"precision mediump float;                                                   \n"
-"uniform samplerExternalOES uLayerTextures[LAYER_COUNT];                    \n"
-"uniform float uLayerAlpha[LAYER_COUNT];                                    \n"
-"in vec2 fTexCoords[LAYER_COUNT];                                           \n"
-"out vec4 oFragColor;                                                       \n"
-"void main() {                                                              \n"
-"  vec3 color = vec3(0.0, 0.0, 0.0);                                        \n"
-"  float alphaCover = 1.0;                                                  \n"
-"  for (int i = 0; i < LAYER_COUNT; i++) {                                  \n"
-"    vec4 texSample = texture2D(uLayerTextures[i], fTexCoords[i]);          \n"
-"    float a = texSample.a * uLayerAlpha[i];                                \n"
-"    color += a * alphaCover * texSample.rgb;                               \n"
-"    alphaCover *= 1.0 - a;                                                 \n"
-"    if (alphaCover <= 0.5/255.0)                                           \n"
-"      break;                                                               \n"
-"  }                                                                        \n"
-"  oFragColor = vec4(color, 1.0 - alphaCover);                              \n"
-"}                                                                          \n";
-  // clang-format on
+static AutoGLProgram GenerateProgram(unsigned num_textures,
+                                     std::ostringstream *shader_log) {
+  std::string vertex_shader_string = GenerateVertexShader(num_textures);
+  const GLchar *vertex_shader_source = vertex_shader_string.c_str();
+  AutoGLShader vertex_shader = CompileAndCheckShader(
+      GL_VERTEX_SHADER, 1, &vertex_shader_source, shader_log);
+  if (!vertex_shader.get())
+    return 0;
 
-  int i, ret = 1;
-  GLint max_texture_images, status;
-  AutoGLShader vertex_shader, fragment_shader;
-  AutoGLProgram program;
-  std::string shader_log;
+  std::string fragment_shader_string = GenerateFragmentShader(num_textures);
+  const GLchar *fragment_shader_source = fragment_shader_string.c_str();
+  AutoGLShader fragment_shader = CompileAndCheckShader(
+      GL_FRAGMENT_SHADER, 1, &fragment_shader_source, shader_log);
+  if (!fragment_shader.get())
+    return 0;
 
-  glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &max_texture_images);
-
-  for (i = 1; i <= max_texture_images; i++) {
-    std::ostringstream layer_count_formatter;
-    layer_count_formatter << i;
-    std::string layer_count(layer_count_formatter.str());
-    const GLchar *shader_sources[3] = {shader_preamble, layer_count.c_str(),
-                                       NULL};
-
-    shader_sources[2] = vertex_shader_source;
-    vertex_shader = CompileAndCheckShader(GL_VERTEX_SHADER, 3, shader_sources,
-                                          ret ? &shader_log : NULL);
-    if (!vertex_shader.get()) {
-      if (ret)
-        ALOGE("Failed to make vertex shader:\n%s", shader_log.c_str());
-      break;
-    }
-
-    shader_sources[2] = fragment_shader_source;
-    fragment_shader = CompileAndCheckShader(
-        GL_FRAGMENT_SHADER, 3, shader_sources, ret ? &shader_log : NULL);
-    if (!fragment_shader.get()) {
-      if (ret)
-        ALOGE("Failed to make fragment shader:\n%s", shader_log.c_str());
-      break;
-    }
-
-    program = AutoGLProgram(glCreateProgram());
-    if (!program.get()) {
-      if (ret)
-        ALOGE("Failed to create program %s", GetGLError());
-      break;
-    }
-
-    glAttachShader(program.get(), vertex_shader.get());
-    glAttachShader(program.get(), fragment_shader.get());
-    glBindAttribLocation(program.get(), 0, "vPosition");
-    glBindAttribLocation(program.get(), 1, "vTexCoords");
-    glLinkProgram(program.get());
-    glDetachShader(program.get(), vertex_shader.get());
-    glDetachShader(program.get(), fragment_shader.get());
-
-    glGetProgramiv(program.get(), GL_LINK_STATUS, &status);
-    if (!status) {
-      if (ret) {
-        GLint log_length;
-        glGetProgramiv(program.get(), GL_INFO_LOG_LENGTH, &log_length);
-        std::string program_log(log_length, ' ');
-        glGetProgramInfoLog(program.get(), log_length, NULL, &program_log[0]);
-        ALOGE("Failed to link program: \n%s", program_log.c_str());
-      }
-      break;
-    }
-
-    ret = 0;
-    blend_programs->emplace_back(std::move(program));
+  AutoGLProgram program(glCreateProgram());
+  if (!program.get()) {
+    if (shader_log)
+      *shader_log << "Failed to create program: " << GetGLError() << "\n";
+    return 0;
   }
 
-  return ret;
+  glAttachShader(program.get(), vertex_shader.get());
+  glAttachShader(program.get(), fragment_shader.get());
+  glBindAttribLocation(program.get(), 0, "vPosition");
+  glBindAttribLocation(program.get(), 1, "vTexCoords");
+  glLinkProgram(program.get());
+  glDetachShader(program.get(), vertex_shader.get());
+  glDetachShader(program.get(), fragment_shader.get());
+
+  GLint status;
+  glGetProgramiv(program.get(), GL_LINK_STATUS, &status);
+  if (!status) {
+    if (shader_log) {
+      GLint log_length;
+      glGetProgramiv(program.get(), GL_INFO_LOG_LENGTH, &log_length);
+      std::string program_log(log_length, ' ');
+      glGetProgramInfoLog(program.get(), log_length, NULL,
+                          &program_log.front());
+      *shader_log << "Failed to link program:\n" << program_log.c_str() << "\n";
+    }
+    return 0;
+  }
+
+  return program;
 }
 
 struct RenderingCommand {
@@ -295,130 +297,103 @@
     unsigned texture_index;
     float crop_bounds[4];
     float alpha;
+    float premult;
     float texture_matrix[4];
   };
 
   float bounds[4];
-  unsigned texture_count;
+  unsigned texture_count = 0;
   TextureSource textures[MAX_OVERLAPPING_LAYERS];
-
-  RenderingCommand() : texture_count(0) {
-  }
 };
 
-static void ConstructCommands(DrmCompositionLayer *layers, size_t num_layers,
-                              std::vector<RenderingCommand> *commands) {
-  std::vector<FRect> in_rects;
-  std::vector<FRectSet> out_rects;
-  int i;
+static void ConstructCommand(const DrmHwcLayer *layers,
+                             const DrmCompositionRegion &region,
+                             RenderingCommand &cmd) {
+  std::copy_n(region.frame.bounds, 4, cmd.bounds);
 
-  for (unsigned rect_index = 0; rect_index < num_layers; rect_index++) {
-    DrmCompositionLayer &layer = layers[rect_index];
-    in_rects.emplace_back(layer.display_frame);
-  }
+  for (size_t texture_index : region.source_layers) {
+    const DrmHwcLayer &layer = layers[texture_index];
 
-  seperate_frects_64(in_rects, &out_rects);
+    DrmHwcRect<float> display_rect(layer.display_frame);
+    float display_size[2] = {display_rect.bounds[2] - display_rect.bounds[0],
+                             display_rect.bounds[3] - display_rect.bounds[1]};
 
-  for (unsigned rect_index = 0; rect_index < out_rects.size(); rect_index++) {
-    const FRectSet &out_rect = out_rects[rect_index];
-    commands->push_back(RenderingCommand());
-    RenderingCommand &cmd = commands->back();
+    float tex_width = layer.buffer->width;
+    float tex_height = layer.buffer->height;
+    DrmHwcRect<float> crop_rect(layer.source_crop.left / tex_width,
+                                layer.source_crop.top / tex_height,
+                                layer.source_crop.right / tex_width,
+                                layer.source_crop.bottom / tex_height);
 
-    for (int i = 0; i < 4; i++)
-      cmd.bounds[i] = out_rect.rect.bounds[i];
+    float crop_size[2] = {crop_rect.bounds[2] - crop_rect.bounds[0],
+                          crop_rect.bounds[3] - crop_rect.bounds[1]};
 
-    uint64_t tex_set = out_rect.id_set.getBits();
-    for (unsigned i = num_layers - 1; tex_set != 0x0; i--) {
-      if (tex_set & (0x1 << i)) {
-        tex_set &= ~(0x1 << i);
+    RenderingCommand::TextureSource &src = cmd.textures[cmd.texture_count];
+    cmd.texture_count++;
+    src.texture_index = texture_index;
 
-        DrmCompositionLayer &layer = layers[i];
+    bool swap_xy, flip_xy[2];
+    switch (layer.transform) {
+      case DrmHwcTransform::kFlipH:
+        swap_xy = false;
+        flip_xy[0] = true;
+        flip_xy[1] = false;
+        break;
+      case DrmHwcTransform::kFlipV:
+        swap_xy = false;
+        flip_xy[0] = false;
+        flip_xy[1] = true;
+        break;
+      case DrmHwcTransform::kRotate90:
+        swap_xy = true;
+        flip_xy[0] = false;
+        flip_xy[1] = true;
+        break;
+      case DrmHwcTransform::kRotate180:
+        swap_xy = false;
+        flip_xy[0] = true;
+        flip_xy[1] = true;
+        break;
+      case DrmHwcTransform::kRotate270:
+        swap_xy = true;
+        flip_xy[0] = true;
+        flip_xy[1] = false;
+        break;
+      default:
+        ALOGE("Unknown transform for layer: defaulting to identity transform");
+      case DrmHwcTransform::kIdentity:
+        swap_xy = false;
+        flip_xy[0] = false;
+        flip_xy[1] = false;
+        break;
+    }
 
-        FRect display_rect(layer.display_frame);
-        float display_size[2] = {
-            display_rect.bounds[2] - display_rect.bounds[0],
-            display_rect.bounds[3] - display_rect.bounds[1]};
+    if (swap_xy)
+      std::copy_n(&kTextureTransformMatrices[4], 4, src.texture_matrix);
+    else
+      std::copy_n(&kTextureTransformMatrices[0], 4, src.texture_matrix);
 
-        float tex_width = layer.buffer->width;
-        float tex_height = layer.buffer->height;
-        FRect crop_rect(layer.source_crop.left / tex_width,
-                        layer.source_crop.top / tex_height,
-                        layer.source_crop.right / tex_width,
-                        layer.source_crop.bottom / tex_height);
-
-        float crop_size[2] = {crop_rect.bounds[2] - crop_rect.bounds[0],
-                              crop_rect.bounds[3] - crop_rect.bounds[1]};
-
-        RenderingCommand::TextureSource &src = cmd.textures[cmd.texture_count];
-        cmd.texture_count++;
-        src.texture_index = i;
-
-        bool swap_xy, flip_xy[2];
-        switch (layer.transform) {
-          case DrmHwcTransform::kFlipH:
-            swap_xy = false;
-            flip_xy[0] = true;
-            flip_xy[1] = false;
-            break;
-          case DrmHwcTransform::kFlipV:
-            swap_xy = false;
-            flip_xy[0] = false;
-            flip_xy[1] = true;
-            break;
-          case DrmHwcTransform::kRotate90:
-            swap_xy = true;
-            flip_xy[0] = false;
-            flip_xy[1] = true;
-            break;
-          case DrmHwcTransform::kRotate180:
-            swap_xy = false;
-            flip_xy[0] = true;
-            flip_xy[1] = true;
-            break;
-          case DrmHwcTransform::kRotate270:
-            swap_xy = true;
-            flip_xy[0] = true;
-            flip_xy[1] = false;
-            break;
-          default:
-            ALOGE(
-                "Unknown transform for layer: defaulting to identity "
-                "transform");
-          case DrmHwcTransform::kIdentity:
-            swap_xy = false;
-            flip_xy[0] = false;
-            flip_xy[1] = false;
-            break;
-        }
-
-        if (swap_xy)
-          std::copy_n(&kTextureTransformMatrices[4], 4, src.texture_matrix);
-        else
-          std::copy_n(&kTextureTransformMatrices[0], 4, src.texture_matrix);
-
-        for (int j = 0; j < 4; j++) {
-          int b = j ^ (swap_xy ? 1 : 0);
-          float bound_percent = (cmd.bounds[b] - display_rect.bounds[b % 2]) /
-                                display_size[b % 2];
-          if (flip_xy[j % 2]) {
-            src.crop_bounds[j] =
-                crop_rect.bounds[j % 2 + 2] - bound_percent * crop_size[j % 2];
-          } else {
-            src.crop_bounds[j] =
-                crop_rect.bounds[j % 2] + bound_percent * crop_size[j % 2];
-          }
-        }
-
-        if (layer.blending == DrmHwcBlending::kNone) {
-          src.alpha = 1.0f;
-          // This layer is opaque. There is no point in using layers below this
-          // one.
-          break;
-        }
-
-        src.alpha = layer.alpha / 255.0f;
+    for (int j = 0; j < 4; j++) {
+      int b = j ^ (swap_xy ? 1 : 0);
+      float bound_percent =
+          (cmd.bounds[b] - display_rect.bounds[b % 2]) / display_size[b % 2];
+      if (flip_xy[j % 2]) {
+        src.crop_bounds[j] =
+            crop_rect.bounds[j % 2 + 2] - bound_percent * crop_size[j % 2];
+      } else {
+        src.crop_bounds[j] =
+            crop_rect.bounds[j % 2] + bound_percent * crop_size[j % 2];
       }
     }
+
+    if (layer.blending == DrmHwcBlending::kNone) {
+      src.alpha = src.premult = 1.0f;
+      // This layer is opaque. There is no point in using layers below this one.
+      break;
+    }
+
+    src.alpha = layer.alpha / 255.0f;
+    src.premult = (layer.blending == DrmHwcBlending::kPreMult) ? 1.0f : 0.0f;
   }
 }
 
@@ -564,7 +539,10 @@
   glBindBuffer(GL_ARRAY_BUFFER, 0);
   vertex_buffer_.reset(vertex_buffer);
 
-  if (GenerateShaders(&blend_programs_)) {
+  std::ostringstream shader_log;
+  blend_programs_.emplace_back(GenerateProgram(1, &shader_log));
+  if (blend_programs_.back().get() == 0) {
+    ALOGE("%s", shader_log.str().c_str());
     return 1;
   }
 
@@ -577,16 +555,16 @@
       ALOGE("Failed to destroy OpenGL ES Context: %s", GetEGLError());
 }
 
-int GLWorkerCompositor::Composite(DrmCompositionLayer *layers,
-                                  size_t num_layers,
+int GLWorkerCompositor::Composite(DrmHwcLayer *layers,
+                                  DrmCompositionRegion *regions,
+                                  size_t num_regions,
                                   const sp<GraphicBuffer> &framebuffer) {
   ATRACE_CALL();
   int ret = 0;
-  size_t i;
   std::vector<AutoEGLImageAndGLTexture> layer_textures;
   std::vector<RenderingCommand> commands;
 
-  if (num_layers == 0) {
+  if (num_regions == 0) {
     return -EALREADY;
   }
 
@@ -599,10 +577,24 @@
     return -EINVAL;
   }
 
-  for (i = 0; i < num_layers; i++) {
-    DrmCompositionLayer *layer = &layers[i];
+  std::unordered_set<size_t> layers_used_indices;
+  for (size_t region_index = 0; region_index < num_regions; region_index++) {
+    DrmCompositionRegion &region = regions[region_index];
+    layers_used_indices.insert(region.source_layers.begin(),
+                               region.source_layers.end());
+    commands.emplace_back();
+    ConstructCommand(layers, region, commands.back());
+  }
+
+  for (size_t layer_index = 0; layer_index < MAX_OVERLAPPING_LAYERS;
+       layer_index++) {
+    DrmHwcLayer *layer = &layers[layer_index];
 
     layer_textures.emplace_back();
+
+    if (layers_used_indices.count(layer_index) == 0)
+      continue;
+
     ret = CreateTextureFromHandle(egl_display_, layer->get_usable_handle(),
                                   &layer_textures.back());
 
@@ -618,8 +610,6 @@
   if (ret)
     return ret;
 
-  ConstructCommands(layers, num_layers, &commands);
-
   glViewport(0, 0, frame_width, frame_height);
 
   glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
@@ -634,24 +624,23 @@
   glEnable(GL_SCISSOR_TEST);
 
   for (const RenderingCommand &cmd : commands) {
-    if (cmd.texture_count <= 0) {
+    if (cmd.texture_count == 0)
       continue;
-    }
 
     // TODO(zachr): handle the case of too many overlapping textures for one
     // area by falling back to rendering as many layers as possible using
     // multiple blending passes.
-    if (cmd.texture_count > blend_programs_.size()) {
+    GLint program = PrepareAndCacheProgram(cmd.texture_count);
+    if (program == 0) {
       ALOGE("Too many layers to render in one area");
       continue;
     }
 
-    GLint program = blend_programs_[cmd.texture_count - 1].get();
     glUseProgram(program);
     GLint gl_viewport_loc = glGetUniformLocation(program, "uViewport");
-    GLint gl_tex_loc = glGetUniformLocation(program, "uLayerTextures");
     GLint gl_crop_loc = glGetUniformLocation(program, "uLayerCrop");
     GLint gl_alpha_loc = glGetUniformLocation(program, "uLayerAlpha");
+    GLint gl_premult_loc = glGetUniformLocation(program, "uLayerPremult");
     GLint gl_tex_matrix_loc = glGetUniformLocation(program, "uTexMatrix");
     glUniform4f(gl_viewport_loc, cmd.bounds[0] / (float)frame_width,
                 cmd.bounds[1] / (float)frame_height,
@@ -659,12 +648,18 @@
                 (cmd.bounds[3] - cmd.bounds[1]) / (float)frame_height);
 
     for (unsigned src_index = 0; src_index < cmd.texture_count; src_index++) {
+      std::ostringstream texture_name_formatter;
+      texture_name_formatter << "uLayerTexture" << src_index;
+      GLint gl_tex_loc =
+          glGetUniformLocation(program, texture_name_formatter.str().c_str());
+
       const RenderingCommand::TextureSource &src = cmd.textures[src_index];
       glUniform1f(gl_alpha_loc + src_index, src.alpha);
+      glUniform1f(gl_premult_loc + src_index, src.premult);
       glUniform4f(gl_crop_loc + src_index, src.crop_bounds[0],
                   src.crop_bounds[1], src.crop_bounds[2] - src.crop_bounds[0],
                   src.crop_bounds[3] - src.crop_bounds[1]);
-      glUniform1i(gl_tex_loc + src_index, src_index);
+      glUniform1i(gl_tex_loc, src_index);
       glUniformMatrix2fv(gl_tex_matrix_loc + src_index, 1, GL_FALSE,
                          src.texture_matrix);
       glActiveTexture(GL_TEXTURE0 + src_index);
@@ -793,4 +788,22 @@
   return &cached_framebuffers_.back();
 }
 
+GLint GLWorkerCompositor::PrepareAndCacheProgram(unsigned texture_count) {
+  if (blend_programs_.size() >= texture_count) {
+    GLint program = blend_programs_[texture_count - 1].get();
+    if (program != 0)
+      return program;
+  }
+
+  AutoGLProgram program = GenerateProgram(texture_count, NULL);
+  if (program.get() != 0) {
+    if (blend_programs_.size() < texture_count)
+      blend_programs_.resize(texture_count);
+    blend_programs_[texture_count - 1] = std::move(program);
+    return blend_programs_[texture_count - 1].get();
+  }
+
+  return 0;
+}
+
 }  // namespace android
diff --git a/glworker.h b/glworker.h
index b3163ce..222cf6f 100644
--- a/glworker.h
+++ b/glworker.h
@@ -33,7 +33,8 @@
 
 namespace android {
 
-struct DrmCompositionLayer;
+struct DrmHwcLayer;
+struct DrmCompositionRegion;
 
 class GLWorkerCompositor {
  public:
@@ -41,8 +42,8 @@
   ~GLWorkerCompositor();
 
   int Init();
-  int Composite(DrmCompositionLayer *layers, size_t num_layers,
-                const sp<GraphicBuffer> &framebuffer);
+  int Composite(DrmHwcLayer *layers, DrmCompositionRegion *regions,
+                size_t num_regions, const sp<GraphicBuffer> &framebuffer);
   void Finish();
 
  private:
@@ -67,6 +68,8 @@
   CachedFramebuffer *PrepareAndCacheFramebuffer(
       const sp<GraphicBuffer> &framebuffer);
 
+  GLint PrepareAndCacheProgram(unsigned texture_count);
+
   EGLDisplay egl_display_;
   EGLContext egl_ctx_;
 
diff --git a/hwcomposer.cpp b/hwcomposer.cpp
index a7c9e43..de5c66f 100644
--- a/hwcomposer.cpp
+++ b/hwcomposer.cpp
@@ -46,7 +46,6 @@
 #include <utils/Trace.h>
 
 #define UM_PER_INCH 25400
-#define HWC_FB_BUFFERS 3
 
 namespace android {
 
@@ -130,7 +129,7 @@
   typedef std::map<int, hwc_drm_display_t> DisplayMap;
   typedef DisplayMap::iterator DisplayMapIter;
 
-  hwc_context_t() : procs(NULL), importer(NULL), use_framebuffer_target(false) {
+  hwc_context_t() : procs(NULL), importer(NULL) {
   }
 
   ~hwc_context_t() {
@@ -146,7 +145,6 @@
   Importer *importer;
   const gralloc_module_t *gralloc;
   DummySwSyncTimeline dummy_timeline;
-  bool use_framebuffer_target;
   VirtualCompositorWorker virtual_compositor_worker;
 };
 
@@ -191,9 +189,9 @@
   return *this;
 }
 
-hwc_drm_bo *DrmHwcBuffer::operator->() {
+const hwc_drm_bo *DrmHwcBuffer::operator->() const {
   if (importer_ == NULL) {
-    ALOGE("Access of none existent BO");
+    ALOGE("Access of non-existent BO");
     exit(1);
     return NULL;
   }
@@ -264,16 +262,15 @@
 int DrmHwcLayer::InitFromHwcLayer(hwc_layer_1_t *sf_layer, Importer *importer,
                                   const gralloc_module_t *gralloc) {
   sf_handle = sf_layer->handle;
-  int ret = buffer.ImportBuffer(sf_layer->handle, importer);
-  if (ret)
-    return ret;
-
-  ret = handle.CopyBufferHandle(sf_layer->handle, gralloc);
-  if (ret)
-    return ret;
-
   alpha = sf_layer->planeAlpha;
 
+  source_crop = DrmHwcRect<float>(
+      sf_layer->sourceCropf.left, sf_layer->sourceCropf.top,
+      sf_layer->sourceCropf.right, sf_layer->sourceCropf.bottom);
+  display_frame = DrmHwcRect<int>(
+      sf_layer->displayFrame.left, sf_layer->displayFrame.top,
+      sf_layer->displayFrame.right, sf_layer->displayFrame.bottom);
+
   switch (sf_layer->transform) {
     case 0:
       transform = DrmHwcTransform::kIdentity;
@@ -313,12 +310,25 @@
       return -EINVAL;
   }
 
-  source_crop = DrmHwcRect<float>(
-      sf_layer->sourceCropf.left, sf_layer->sourceCropf.top,
-      sf_layer->sourceCropf.right, sf_layer->sourceCropf.bottom);
-  display_frame = DrmHwcRect<int>(
-      sf_layer->displayFrame.left, sf_layer->displayFrame.top,
-      sf_layer->displayFrame.right, sf_layer->displayFrame.bottom);
+  int ret = buffer.ImportBuffer(sf_layer->handle, importer);
+  if (ret)
+    return ret;
+
+  ret = handle.CopyBufferHandle(sf_layer->handle, gralloc);
+  if (ret)
+    return ret;
+
+  ret = gralloc->perform(gralloc, GRALLOC_MODULE_PERFORM_GET_USAGE,
+                         handle.get(), &gralloc_buffer_usage);
+  if (ret) {
+    // TODO(zachr): Once GRALLOC_MODULE_PERFORM_GET_USAGE is implemented, remove
+    // "ret = 0" and enable the error logging code.
+    ret = 0;
+#if 0
+    ALOGE("Failed to get usage for buffer %p (%d)", handle.get(), ret);
+    return ret;
+#endif
+  }
 
   return 0;
 }
@@ -330,26 +340,20 @@
 
   ctx->drm.compositor()->Dump(&out);
   std::string out_str = out.str();
-  strncpy(buff, out_str.c_str(), std::min((size_t)buff_len, out_str.length()));
+  strncpy(buff, out_str.c_str(),
+          std::min((size_t)buff_len, out_str.length() + 1));
+  buff[buff_len - 1] = '\0';
 }
 
 static int hwc_prepare(hwc_composer_device_1_t *dev, size_t num_displays,
                        hwc_display_contents_1_t **display_contents) {
   struct hwc_context_t *ctx = (struct hwc_context_t *)&dev->common;
 
-  char use_framebuffer_target[PROPERTY_VALUE_MAX];
-  property_get("hwc.drm.use_framebuffer_target", use_framebuffer_target, "0");
-  bool new_use_framebuffer_target = atoi(use_framebuffer_target);
-  if (ctx->use_framebuffer_target != new_use_framebuffer_target)
-    ALOGW("Starting to %s HWC_FRAMEBUFFER_TARGET",
-          new_use_framebuffer_target ? "use" : "not use");
-  ctx->use_framebuffer_target = new_use_framebuffer_target;
-
   for (int i = 0; i < (int)num_displays; ++i) {
     if (!display_contents[i])
       continue;
 
-    bool use_framebuffer_target = ctx->use_framebuffer_target;
+    bool use_framebuffer_target = false;
     if (i == HWC_DISPLAY_VIRTUAL) {
       use_framebuffer_target = true;
     } else {
@@ -447,15 +451,10 @@
       if (sf_layer->flags & HWC_SKIP_LAYER)
         continue;
 
-      if (!ctx->use_framebuffer_target) {
-        if (sf_layer->compositionType == HWC_OVERLAY)
-          indices_to_composite.push_back(j);
-        if (sf_layer->compositionType == HWC_FRAMEBUFFER_TARGET)
-          framebuffer_target_index = j;
-      } else {
-        if (sf_layer->compositionType == HWC_FRAMEBUFFER_TARGET)
-          indices_to_composite.push_back(j);
-      }
+      if (sf_layer->compositionType == HWC_OVERLAY)
+        indices_to_composite.push_back(j);
+      if (sf_layer->compositionType == HWC_FRAMEBUFFER_TARGET)
+        framebuffer_target_index = j;
 
       layer.acquire_fence.Set(sf_layer->acquireFenceFd);
       sf_layer->acquireFenceFd = -1;
@@ -470,23 +469,15 @@
       layer.release_fence = OutputFd(&sf_layer->releaseFenceFd);
     }
 
-    if (ctx->use_framebuffer_target) {
-      if (indices_to_composite.size() != 1) {
-        ALOGE("Expected 1 (got %d) layer with HWC_FRAMEBUFFER_TARGET",
-              indices_to_composite.size());
+    if (indices_to_composite.empty() && framebuffer_target_index >= 0) {
+      hwc_layer_1_t *sf_layer = &dc->hwLayers[framebuffer_target_index];
+      if (!sf_layer->handle || (sf_layer->flags & HWC_SKIP_LAYER)) {
+        ALOGE(
+            "Expected valid layer with HWC_FRAMEBUFFER_TARGET when all "
+            "HWC_OVERLAY layers are skipped.");
         ret = -EINVAL;
       }
-    } else {
-      if (indices_to_composite.empty() && framebuffer_target_index >= 0) {
-        hwc_layer_1_t *sf_layer = &dc->hwLayers[framebuffer_target_index];
-        if (!sf_layer->handle || (sf_layer->flags & HWC_SKIP_LAYER)) {
-          ALOGE(
-              "Expected valid layer with HWC_FRAMEBUFFER_TARGET when all "
-              "HWC_OVERLAY layers are skipped.");
-          ret = -EINVAL;
-        }
-        indices_to_composite.push_back(framebuffer_target_index);
-      }
+      indices_to_composite.push_back(framebuffer_target_index);
     }
   }
 
@@ -496,18 +487,25 @@
   for (size_t i = 0; i < num_displays; ++i) {
     hwc_display_contents_1_t *dc = sf_display_contents[i];
     DrmHwcDisplayContents &display_contents = displays_contents[i];
-    if (!sf_display_contents[i])
+    if (!sf_display_contents[i] || i == HWC_DISPLAY_VIRTUAL)
       continue;
 
     layers_map.emplace_back();
     DrmCompositionDisplayLayersMap &map = layers_map.back();
+    map.display = i;
+    map.geometry_changed =
+        (dc->flags & HWC_GEOMETRY_CHANGED) == HWC_GEOMETRY_CHANGED;
     std::vector<size_t> &indices_to_composite = layers_indices[i];
     for (size_t j : indices_to_composite) {
       hwc_layer_1_t *sf_layer = &dc->hwLayers[j];
 
       DrmHwcLayer &layer = display_contents.layers[j];
 
-      layer.InitFromHwcLayer(sf_layer, ctx->importer, ctx->gralloc);
+      ret = layer.InitFromHwcLayer(sf_layer, ctx->importer, ctx->gralloc);
+      if (ret) {
+        ALOGE("Failed to init composition from layer %d", ret);
+        return ret;
+      }
       map.layers.emplace_back(std::move(layer));
     }
   }
@@ -589,7 +587,8 @@
       *value = 1000 * 1000 * 1000 / 60;
       break;
     case HWC_DISPLAY_TYPES_SUPPORTED:
-      *value = HWC_DISPLAY_PRIMARY | HWC_DISPLAY_EXTERNAL | HWC_DISPLAY_VIRTUAL;
+      *value = HWC_DISPLAY_PRIMARY_BIT | HWC_DISPLAY_EXTERNAL_BIT |
+               HWC_DISPLAY_VIRTUAL_BIT;
       break;
   }
   return 0;
diff --git a/nvimporter.cpp b/nvimporter.cpp
index de3ed55..d5c3abc 100644
--- a/nvimporter.cpp
+++ b/nvimporter.cpp
@@ -157,10 +157,15 @@
 
     gem_close.handle = bo->gem_handles[i];
     int ret = drmIoctl(drm_->fd(), DRM_IOCTL_GEM_CLOSE, &gem_close);
-    if (ret)
+    if (ret) {
       ALOGE("Failed to close gem handle %d %d", i, ret);
-    else
+    } else {
+      /* Clear any duplicate gem handle as well but don't close again */
+      for (int j = i + 1; j < num_gem_handles; j++)
+        if (bo->gem_handles[j] == bo->gem_handles[i])
+          bo->gem_handles[j] = 0;
       bo->gem_handles[i] = 0;
+    }
   }
 }
 
diff --git a/nvimporter.h b/nvimporter.h
index a07f577..d6033db 100644
--- a/nvimporter.h
+++ b/nvimporter.h
@@ -29,12 +29,12 @@
 class NvImporter : public Importer {
  public:
   NvImporter(DrmResources *drm);
-  virtual ~NvImporter();
+  ~NvImporter() override;
 
   int Init();
 
-  virtual int ImportBuffer(buffer_handle_t handle, hwc_drm_bo_t *bo);
-  virtual int ReleaseBuffer(hwc_drm_bo_t *bo);
+  int ImportBuffer(buffer_handle_t handle, hwc_drm_bo_t *bo) override;
+  int ReleaseBuffer(hwc_drm_bo_t *bo) override;
 
  private:
   typedef struct NvBuffer {
diff --git a/seperate_rects.cpp b/seperate_rects.cpp
index bdf07bc..06fbe39 100644
--- a/seperate_rects.cpp
+++ b/seperate_rects.cpp
@@ -71,8 +71,8 @@
 }
 
 template <typename TNum, typename TId>
-void seperate_rects(const std::vector<Rect<TNum> > &in,
-                    std::vector<RectSet<TId, TNum> > *out) {
+void seperate_rects(const std::vector<Rect<TNum>> &in,
+                    std::vector<RectSet<TId, TNum>> *out) {
   // Overview:
   // This algorithm is a line sweep algorithm that travels from left to right.
   // The sweep stops at each vertical edge of each input rectangle in sorted
@@ -91,8 +91,8 @@
 
   // Events are when the sweep line encounters the starting or ending edge of
   // any input rectangle.
-  std::set<SweepEvent<TId, TNum> > sweep_h_events;  // Left or right bounds
-  std::set<SweepEvent<TId, TNum> > sweep_v_events;  // Top or bottom bounds
+  std::set<SweepEvent<TId, TNum>> sweep_h_events;  // Left or right bounds
+  std::set<SweepEvent<TId, TNum>> sweep_v_events;  // Top or bottom bounds
 
   // A started rect is a rectangle whose left, top, bottom edge, and set of
   // rectangle IDs is known. The key of this map includes all that information
@@ -102,7 +102,7 @@
 
   // This is cleared after every event. Its declaration is here to avoid
   // reallocating a vector and its buffers every event.
-  std::vector<std::pair<TNum, IdSet<TId> > > active_regions;
+  std::vector<std::pair<TNum, IdSet<TId>>> active_regions;
 
   // This pass will add rectangle start and end events to be triggered as the
   // algorithm sweeps from left to right.
@@ -120,7 +120,7 @@
     sweep_h_events.insert(evt);
   }
 
-  for (typename std::set<SweepEvent<TId, TNum> >::iterator it =
+  for (typename std::set<SweepEvent<TId, TNum>>::iterator it =
            sweep_h_events.begin();
        it != sweep_h_events.end(); ++it) {
     const SweepEvent<TId, TNum> &h_evt = *it;
@@ -142,14 +142,14 @@
     } else {
       v_evt.type = START;
       v_evt.y = rect.top;
-      typename std::set<SweepEvent<TId, TNum> >::iterator start_it =
+      typename std::set<SweepEvent<TId, TNum>>::iterator start_it =
           sweep_v_events.find(v_evt);
       assert(start_it != sweep_v_events.end());
       sweep_v_events.erase(start_it);
 
       v_evt.type = END;
       v_evt.y = rect.bottom;
-      typename std::set<SweepEvent<TId, TNum> >::iterator end_it =
+      typename std::set<SweepEvent<TId, TNum>>::iterator end_it =
           sweep_v_events.find(v_evt);
       assert(end_it != sweep_v_events.end());
       sweep_v_events.erase(end_it);
@@ -159,7 +159,7 @@
     // with the current sweep line. If so, we want to continue marking up the
     // sweep line before actually processing the rectangles the sweep line is
     // intersecting.
-    typename std::set<SweepEvent<TId, TNum> >::iterator next_it = it;
+    typename std::set<SweepEvent<TId, TNum>>::iterator next_it = it;
     ++next_it;
     if (next_it != sweep_h_events.end()) {
       if (next_it->x == h_evt.x) {
@@ -179,7 +179,7 @@
     // 5), active_regions will be [({ 0 }, 3), {}, 5].
     active_regions.clear();
     IdSet<TId> active_set;
-    for (typename std::set<SweepEvent<TId, TNum> >::iterator it =
+    for (typename std::set<SweepEvent<TId, TNum>>::iterator it =
              sweep_v_events.begin();
          it != sweep_v_events.end(); ++it) {
       const SweepEvent<TId, TNum> &v_evt = *it;
@@ -199,7 +199,7 @@
 
 #ifdef RECTS_DEBUG
     std::cout << "x:" << h_evt.x;
-    for (std::vector<std::pair<TNum, IdSet> >::iterator it =
+    for (std::vector<std::pair<TNum, IdSet>>::iterator it =
              active_regions.begin();
          it != active_regions.end(); ++it) {
       std::cout << " " << it->first << "(" << it->second << ")"
@@ -226,7 +226,7 @@
     // case, we have a new rectangle, and the already existing started rectangle
     // will not be marked as seen ("true" in the std::pair) and will get ended
     // by the for loop after this one. This is as intended.
-    for (typename std::vector<std::pair<TNum, IdSet<TId> > >::iterator it =
+    for (typename std::vector<std::pair<TNum, IdSet<TId>>>::iterator it =
              active_regions.begin();
          it != active_regions.end(); ++it) {
       IdSet<TId> region_set = it->second;
@@ -237,8 +237,7 @@
       // An important property of active_regions is that each region where a set
       // of rectangles applies is bounded at the bottom by the next (in the
       // vector) region's starting y-coordinate.
-      typename std::vector<std::pair<TNum, IdSet<TId> > >::iterator next_it =
-          it;
+      typename std::vector<std::pair<TNum, IdSet<TId>>>::iterator next_it = it;
       ++next_it;
       assert(next_it != active_regions.end());
 
@@ -300,8 +299,13 @@
   }
 }
 
-void seperate_frects_64(const std::vector<Rect<float> > &in,
-                        std::vector<RectSet<uint64_t, float> > *out) {
+void seperate_frects_64(const std::vector<Rect<float>> &in,
+                        std::vector<RectSet<uint64_t, float>> *out) {
+  seperate_rects(in, out);
+}
+
+void seperate_rects_64(const std::vector<Rect<int>> &in,
+                       std::vector<RectSet<uint64_t, int>> *out) {
   seperate_rects(in, out);
 }
 
diff --git a/seperate_rects.h b/seperate_rects.h
index 540a5e8..0703c97 100644
--- a/seperate_rects.h
+++ b/seperate_rects.h
@@ -18,6 +18,8 @@
 #define DRM_HWCOMPOSER_SEPERATE_RECTS_H_
 
 #include <stdint.h>
+
+#include <sstream>
 #include <vector>
 
 namespace seperate_rects {
@@ -64,6 +66,23 @@
 
     return true;
   }
+
+  TFloat width() const {
+    return bounds[2] - bounds[0];
+  }
+
+  TFloat height() const {
+    return bounds[3] - bounds[1];
+  }
+
+  TFloat area() const {
+    return width() * height();
+  }
+
+  void Dump(std::ostringstream *out) const {
+    *out << "[x/y/w/h]=" << left << "/" << top << "/" << width() << "/"
+         << height();
+  }
 };
 
 template <typename TUInt>
@@ -140,8 +159,10 @@
 // rectangle indices that overlap the output rectangle encoded in a bitset. For
 // example, an output rectangle that overlaps input rectangles in[0], in[1], and
 // in[4], the bitset would be (ommitting leading zeroes) 10011.
-void seperate_frects_64(const std::vector<Rect<float> > &in,
-                        std::vector<RectSet<uint64_t, float> > *out);
+void seperate_frects_64(const std::vector<Rect<float>> &in,
+                        std::vector<RectSet<uint64_t, float>> *out);
+void seperate_rects_64(const std::vector<Rect<int>> &in,
+                       std::vector<RectSet<uint64_t, int>> *out);
 
 }  // namespace seperate_rects
 
diff --git a/virtualcompositorworker.cpp b/virtualcompositorworker.cpp
index e7acef0..ea46461 100644
--- a/virtualcompositorworker.cpp
+++ b/virtualcompositorworker.cpp
@@ -35,10 +35,10 @@
 static const int kAcquireWaitTimeoutMs = 50;
 
 VirtualCompositorWorker::VirtualCompositorWorker()
-    : Worker("virtual-compositor", HAL_PRIORITY_URGENT_DISPLAY)
-    , timeline_fd_(-1)
-    , timeline_(0)
-    , timeline_current_(0) {
+    : Worker("virtual-compositor", HAL_PRIORITY_URGENT_DISPLAY),
+      timeline_fd_(-1),
+      timeline_(0),
+      timeline_current_(0) {
 }
 
 VirtualCompositorWorker::~VirtualCompositorWorker() {
diff --git a/virtualcompositorworker.h b/virtualcompositorworker.h
index 4a15f0c..3066547 100644
--- a/virtualcompositorworker.h
+++ b/virtualcompositorworker.h
@@ -27,13 +27,13 @@
 class VirtualCompositorWorker : public Worker {
  public:
   VirtualCompositorWorker();
-  ~VirtualCompositorWorker();
+  ~VirtualCompositorWorker() override;
 
   int Init();
   void QueueComposite(hwc_display_contents_1_t *dc);
 
  protected:
-  virtual void Routine();
+  void Routine() override;
 
  private:
   struct VirtualComposition {
diff --git a/vsyncworker.h b/vsyncworker.h
index ce7b94a..98ac546 100644
--- a/vsyncworker.h
+++ b/vsyncworker.h
@@ -31,7 +31,7 @@
 class VSyncWorker : public Worker {
  public:
   VSyncWorker();
-  ~VSyncWorker();
+  ~VSyncWorker() override;
 
   int Init(DrmResources *drm, int display);
   int SetProcs(hwc_procs_t const *procs);
@@ -39,7 +39,7 @@
   int VSyncControl(bool enabled);
 
  protected:
-  virtual void Routine();
+  void Routine() override;
 
  private:
   int64_t GetPhasedVSync(int64_t frame_ns, int64_t current);