sde: resource manager: multi display support

Move some state info into display ctx.
Reorganize layer config for display split config case.
Add macro tile format check for scaling limit difference.

Change-Id: If9c3bed6161343828c14f784f69269c181510223
diff --git a/displayengine/include/core/layer_buffer.h b/displayengine/include/core/layer_buffer.h
index 5e813ea..c069c60 100755
--- a/displayengine/include/core/layer_buffer.h
+++ b/displayengine/include/core/layer_buffer.h
@@ -108,11 +108,13 @@
   @sa LayerBuffer
 */
 struct LayerBufferFlags {
-  uint64_t secure : 1;  //!< This flag shall be set by client to indicate that the buffer need
-                        //!< to be handled securely.
-  uint64_t video  : 1;  //!< This flag shall be set by client to indicate that the buffer is
-                        //!< video/ui buffer
-  LayerBufferFlags() : secure(0) { }
+  uint64_t secure : 1;      //!< This flag shall be set by client to indicate that the buffer need
+                            //!< to be handled securely.
+  uint64_t video  : 1;      //!< This flag shall be set by client to indicate that the buffer is
+                            //!< video/ui buffer
+  uint64_t macro_tile : 1;  //!< This flag shall be set by client to indicate that the buffer format
+                            //!< is macro tiled.
+  LayerBufferFlags() : secure(0), video(0), macro_tile(0) { }
 };
 
 /*! @brief This structure defines a layer buffer handle which contains raw buffer and its associated
diff --git a/displayengine/libs/core/hw_interface.h b/displayengine/libs/core/hw_interface.h
index 84870b8..bbedc65 100644
--- a/displayengine/libs/core/hw_interface.h
+++ b/displayengine/libs/core/hw_interface.h
@@ -99,15 +99,17 @@
   uint8_t decimation;
 
   HWPipeInfo() : pipe_id(0), decimation(1) { }
+
+  inline void Reset() { *this = HWPipeInfo(); }
 };
 
 struct HWLayerConfig {
-  bool use_non_dma_pipe;
-  bool is_right_pipe;
-  HWPipeInfo left_pipe;
-  HWPipeInfo right_pipe;
+  bool use_non_dma_pipe;  // set by client
+  bool is_right_pipe;  // indicate if right pipe is valid
+  HWPipeInfo left_pipe;  // pipe for left side of the buffer
+  HWPipeInfo right_pipe;  // pipe for right side of the buffer
 
-  HWLayerConfig() : use_non_dma_pipe(false), is_right_pipe(true) { }
+  HWLayerConfig() : use_non_dma_pipe(false), is_right_pipe(false) { }
 };
 
 struct HWLayers {
diff --git a/displayengine/libs/core/res_config.cpp b/displayengine/libs/core/res_config.cpp
old mode 100755
new mode 100644
index 73ea4a4..d8b8430
--- a/displayengine/libs/core/res_config.cpp
+++ b/displayengine/libs/core/res_config.cpp
@@ -32,115 +32,200 @@
 
 namespace sde {
 
-DisplayError ResManager::Config(DisplayResourceContext *display_resource_ctx, HWLayers *hw_layers) {
-  HWBlockType hw_block_id = display_resource_ctx->hw_block_id;
+DisplayError ResManager::DisplaySplitConfig(DisplayResourceContext *display_resource_ctx,
+                                            const Layer &layer, const LayerRect &src_rect,
+                                            const LayerRect &dst_rect,
+                                            HWLayerConfig *layer_config) {
   HWDisplayAttributes &display_attributes = display_resource_ctx->display_attributes;
-  HWLayersInfo &layer_info = hw_layers->info;
+  // for display split case
+  HWPipeInfo *left_pipe = &layer_config->left_pipe;
+  HWPipeInfo *right_pipe = &layer_config->right_pipe;
+  LayerRect scissor, dst_left, crop_left, crop_right, dst_right;
+  layer_config->is_right_pipe = false;
+  scissor.right = FLOAT(display_attributes.split_left);
+  scissor.bottom = FLOAT(display_attributes.y_pixels);
 
-  for (uint32_t i = 0; i < layer_info.count; i++) {
-    Layer& layer = layer_info.stack->layers[layer_info.index[i]];
-    float w_scale, h_scale;
-    if (!IsValidDimension(layer, &w_scale, &h_scale)) {
-      DLOGV("Invalid dimension");
+  crop_left = src_rect;
+  dst_left = dst_rect;
+  crop_right = crop_left;
+  dst_right = dst_left;
+  LayerTransform transform = layer.transform;
+  CalculateCropRects(scissor, transform, &crop_left, &dst_left);
+  scissor.left = FLOAT(display_attributes.split_left);
+  scissor.top = 0.0f;
+  scissor.right = FLOAT(display_attributes.x_pixels);
+  scissor.bottom = FLOAT(display_attributes.y_pixels);
+  CalculateCropRects(scissor, transform, &crop_right, &dst_right);
+  if ((crop_left.right - crop_left.left) >= kMaxSourcePipeWidth) {
+    if (crop_right.right != crop_right.left)
       return kErrorNotSupported;
-    }
+    // 2 pipes both are on the left
+    SplitRect(transform.flip_horizontal, crop_left, dst_left, &left_pipe->src_roi,
+              &left_pipe->dst_roi, &right_pipe->src_roi, &right_pipe->dst_roi);
+    left_pipe->pipe_id = kPipeIdNeedsAssignment;
+    right_pipe->pipe_id = kPipeIdNeedsAssignment;
+    layer_config->is_right_pipe = true;
+  } else if ((crop_right.right - crop_right.left) >= kMaxSourcePipeWidth) {
+    if (crop_left.right != crop_left.left)
+      return kErrorNotSupported;
+    // 2 pipes both are on the right
+    SplitRect(transform.flip_horizontal, crop_right, dst_right, &left_pipe->src_roi,
+              &left_pipe->dst_roi, &right_pipe->src_roi, &right_pipe->dst_roi);
+    left_pipe->pipe_id = kPipeIdNeedsAssignment;
+    right_pipe->pipe_id = kPipeIdNeedsAssignment;
+    layer_config->is_right_pipe = true;
+  } else if (UINT32(dst_left.right) > UINT32(dst_left.left)) {
+    // assign left pipe
+    left_pipe->src_roi = crop_left;
+    left_pipe->dst_roi = dst_left;
+    left_pipe->pipe_id = kPipeIdNeedsAssignment;
+  } else {
+    // Set default value, left_pipe is not needed.
+    left_pipe->Reset();
+  }
 
-    LayerRect scissor;
-    scissor.right = FLOAT(display_attributes.split_left);
-    scissor.bottom = FLOAT(display_attributes.y_pixels);
-    LayerRect crop = layer.src_rect;
-    LayerRect dst = layer.dst_rect;
-    LayerRect cropRight = crop;
-    LayerRect dstRight = dst;
-    CalculateCropRects(&crop, &dst, scissor, layer.transform);
-    HWPipeInfo *pipe_info = &hw_layers->config[i].left_pipe;
-
-    pipe_info->src_roi = crop;
-    pipe_info->dst_roi = dst;
-
-    float crop_width = cropRight.right - cropRight.left;
-    pipe_info = &hw_layers->config[i].right_pipe;
-    if ((dstRight.right - dstRight.left) > kMaxInterfaceWidth ||
-         crop_width > kMaxInterfaceWidth ||
-        ((hw_block_id == kHWPrimary) &&
-         (crop_width > display_attributes.split_left))) {
-      scissor.left = FLOAT(display_attributes.split_left);
-      scissor.top = 0.0f;
-      scissor.right = FLOAT(display_attributes.x_pixels);
-      scissor.bottom = FLOAT(display_attributes.y_pixels);
-      CalculateCropRects(&cropRight, &dstRight, scissor, layer.transform);
-      pipe_info->src_roi = cropRight;
-      pipe_info->dst_roi = dstRight;
-      pipe_info->pipe_id = -1;
+  // assign right pipe if needed
+  if (UINT32(dst_right.right) > UINT32(dst_right.left)) {
+    if (left_pipe->pipe_id) {
+      right_pipe->src_roi = crop_right;
+      right_pipe->dst_roi = dst_right;
+      right_pipe->pipe_id = kPipeIdNeedsAssignment;
+      layer_config->is_right_pipe = true;
     } else {
-      // need not right pipe
-      pipe_info->pipe_id = 0;
+      // If left pipe is not used, use left pipe first.
+      left_pipe->src_roi = crop_right;
+      left_pipe->dst_roi = dst_right;
+      left_pipe->pipe_id = kPipeIdNeedsAssignment;
+      right_pipe->Reset();
     }
+  } else {
+    // need not right pipe
+    right_pipe->Reset();
   }
 
   return kErrorNone;
 }
 
-bool ResManager::IsValidDimension(const Layer& layer, float *width_scale, float *height_scale) {
-  if (IsNonIntegralSrcCrop(layer.src_rect)) {
-    return false;
-  }
+DisplayError ResManager::Config(DisplayResourceContext *display_resource_ctx, HWLayers *hw_layers,
+                                uint32_t *rotate_count) {
+  HWBlockType hw_block_id = display_resource_ctx->hw_block_id;
+  HWDisplayAttributes &display_attributes = display_resource_ctx->display_attributes;
+  HWLayersInfo &layer_info = hw_layers->info;
+  DisplayError error = kErrorNone;
 
-  LayerRect crop;
-  LayerRect dst;
-  IntegerizeRect(&crop, layer.src_rect);
-  IntegerizeRect(&dst, layer.dst_rect);
+  for (uint32_t i = 0; i < layer_info.count; i++) {
+    Layer& layer = layer_info.stack->layers[layer_info.index[i]];
+    float rot_scale_x = 1.0f, rot_scale_y = 1.0f;
+    if (!IsValidDimension(layer.src_rect, layer.dst_rect)) {
+      DLOGE_IF(kTagResources, "Input is invalid");
+      LogRectVerbose("input layer src_rect", layer.src_rect);
+      LogRectVerbose("input layer dst_rect", layer.dst_rect);
+      return kErrorNotSupported;
+    }
 
-  bool rotated90 = (static_cast<int>(layer.transform.rotation) == 90);
-  float crop_w = rotated90 ? crop.bottom - crop.top : crop.right - crop.left;
-  float crop_h = rotated90 ? crop.right - crop.left : crop.bottom - crop.top;
-  float dst_w = dst.right - dst.left;
-  float dst_h = dst.bottom - dst.top;
+    LayerRect scissor, src_rect, dst_rect;
+    src_rect = layer.src_rect;
+    dst_rect = layer.dst_rect;
+    scissor.right = FLOAT(display_attributes.x_pixels);
+    scissor.bottom = FLOAT(display_attributes.y_pixels);
+    CalculateCropRects(scissor, layer.transform, &src_rect, &dst_rect);
 
-  if ((dst_w < 1) || (dst_h < 1)) {
-    return false;
-  }
+    if (ValidateScaling(layer, src_rect, dst_rect, &rot_scale_x, &rot_scale_y))
+      return kErrorNotSupported;
 
-  float w_scale = crop_w / dst_w;
-  float h_scale = crop_h / dst_h;
+    error = DisplaySplitConfig(display_resource_ctx, layer, src_rect,
+                               dst_rect, &hw_layers->config[i]);
+    if (error != kErrorNone)
+      break;
 
-  if ((crop_w < kMaxCropWidth) ||(crop_h < kMaxCropHeight)) {
-    return false;
-  }
-
-  if ((w_scale > 1.0f) || (h_scale > 1.0f)) {
-    const uint32_t max_scale_down = hw_res_info_.max_scale_down;
-
-    if (!hw_res_info_.has_decimation) {
-      if (crop_w > kMaxSourcePipeWidth || w_scale > max_scale_down || h_scale > max_scale_down) {
-        return false;
-      }
-    } else {
-      if (w_scale > max_scale_down || h_scale > max_scale_down) {
-        return false;
-      }
+    DLOGV_IF(kTagResources, "layer = %d, left pipe_id = %x",
+             i, hw_layers->config[i].left_pipe.pipe_id);
+    LogRectVerbose("input layer src_rect", layer.src_rect);
+    LogRectVerbose("input layer dst_rect", layer.dst_rect);
+    LogRectVerbose("cropped src_rect", src_rect);
+    LogRectVerbose("cropped dst_rect", dst_rect);
+    LogRectVerbose("left pipe src", hw_layers->config[i].left_pipe.src_roi);
+    LogRectVerbose("left pipe dst", hw_layers->config[i].left_pipe.dst_roi);
+    if (hw_layers->config[i].right_pipe.pipe_id) {
+      LogRectVerbose("right pipe src", hw_layers->config[i].right_pipe.src_roi);
+      LogRectVerbose("right pipe dst", hw_layers->config[i].right_pipe.dst_roi);
     }
   }
 
-  if (((w_scale < 1.0f) || (h_scale < 1.0f)) && (w_scale > 0.0f) && (h_scale > 0.0f)) {
-    const uint32_t max_scale_up = hw_res_info_.max_scale_up;
-    const float w_uscale = 1.0f / w_scale;
-    const float h_uscale = 1.0f / h_scale;
-
-    if (w_uscale > max_scale_up || h_uscale > max_scale_up) {
-      return false;
-    }
-  }
-
-  *width_scale = w_scale;
-  *height_scale = h_scale;
-
-  return true;
+  return error;
 }
 
-void ResManager::CalculateCut(float *left_cut_ratio,
-    float *top_cut_ratio, float *right_cut_ratio, float *bottom_cut_ratio,
-    const LayerTransform& transform) {
+DisplayError ResManager::ValidateScaling(const Layer &layer, const LayerRect &crop,
+                                         const LayerRect &dst, float *rot_scale_x,
+                                         float *rot_scale_y) {
+  bool rotated90 = (UINT32(layer.transform.rotation) == 90);
+  float crop_width = rotated90 ? crop.bottom - crop.top : crop.right - crop.left;
+  float crop_height = rotated90 ? crop.right - crop.left : crop.bottom - crop.top;
+  float dst_width = dst.right - dst.left;
+  float dst_height = dst.bottom - dst.top;
+
+  if ((dst_width < 1.0f) || (dst_height < 1.0f)) {
+    DLOGV_IF(kTagResources, "Destination region is too small w = %d, h = %d",
+    dst_width, dst_height);
+    return kErrorNotSupported;
+  }
+
+  if ((crop_width < 1.0f) || (crop_height < 1.0f)) {
+    DLOGV_IF(kTagResources, "source region is too small w = %d, h = %d", crop_width, crop_height);
+    return kErrorNotSupported;
+  }
+
+  float scale_x = crop_width / dst_width;
+  float scale_y = crop_height / dst_height;
+
+  if ((UINT32(scale_x) > 1) || (UINT32(scale_y) > 1)) {
+    const uint32_t max_scale_down = hw_res_info_.max_scale_down;
+    if (((!hw_res_info_.has_decimation) || (IsMacroTileFormat(layer.input_buffer))) &&
+        (scale_x > max_scale_down || scale_y > max_scale_down)) {
+      DLOGV_IF(kTagResources,
+               "Scaling down is over the limit is_tile = %d, scale_x = %d, scale_y = %d",
+               IsMacroTileFormat(layer.input_buffer), scale_x, scale_y);
+      return kErrorNotSupported;
+    }
+  }
+
+  const uint32_t max_scale_up = hw_res_info_.max_scale_up;
+  if (UINT32(scale_x) < 1 && scale_x > 0.0f) {
+    if ((1.0f / scale_x) > max_scale_up) {
+      DLOGV_IF(kTagResources, "Scaling up is over limit scale_x = %d", 1.0f / scale_x);
+      return kErrorNotSupported;
+    }
+  }
+
+  if (UINT32(scale_y) < 1 && scale_y > 0.0f) {
+    if ((1.0f / scale_y) > max_scale_up) {
+      DLOGV_IF(kTagResources, "Scaling up is over limit scale_y = %d", 1.0f / scale_y);
+      return kErrorNotSupported;
+    }
+  }
+
+  // Calculate rotator downscale ratio
+  float rot_scale = 1.0f;
+  while (scale_x > hw_res_info_.max_scale_down) {
+    scale_x /= 2;
+    rot_scale *= 2;
+  }
+  *rot_scale_x = rot_scale;
+
+  rot_scale = 1.0f;
+  while (scale_y > hw_res_info_.max_scale_down) {
+    scale_y /= 2;
+    rot_scale *= 2;
+  }
+  *rot_scale_y = rot_scale;
+  DLOGV_IF(kTagResources, "rotator scaling hor = %.0f, ver = %.0f", *rot_scale_x, *rot_scale_y);
+
+  return kErrorNone;
+}
+
+void ResManager::CalculateCut(const LayerTransform &transform, float *left_cut_ratio,
+                              float *top_cut_ratio, float *right_cut_ratio,
+                              float *bottom_cut_ratio) {
   if (transform.flip_horizontal) {
     Swap(*left_cut_ratio, *right_cut_ratio);
   }
@@ -159,75 +244,80 @@
   }
 }
 
-void ResManager::CalculateCropRects(LayerRect *crop, LayerRect *dst,
-    const LayerRect& scissor, const LayerTransform& transform) {
-  float& crop_l = crop->left;
-  float& crop_t = crop->top;
-  float& crop_r = crop->right;
-  float& crop_b = crop->bottom;
-  float crop_w = crop->right - crop->left;
-  float crop_h = crop->bottom - crop->top;
+void ResManager::CalculateCropRects(const LayerRect &scissor, const LayerTransform &transform,
+                                    LayerRect *crop, LayerRect *dst) {
+  float &crop_left = crop->left;
+  float &crop_top = crop->top;
+  float &crop_right = crop->right;
+  float &crop_bottom = crop->bottom;
+  float crop_width = crop->right - crop->left;
+  float crop_height = crop->bottom - crop->top;
 
-  float& dst_l = dst->left;
-  float& dst_t = dst->top;
-  float& dst_r = dst->right;
-  float& dst_b = dst->bottom;
-  float dst_w = (dst->right > dst->left) ? dst->right - dst->left :
-    dst->left - dst->right;
-  float dst_h = (dst->bottom > dst->top) ? dst->bottom > dst->top :
-    dst->top > dst->bottom;
+  float &dst_left = dst->left;
+  float &dst_top = dst->top;
+  float &dst_right = dst->right;
+  float &dst_bottom = dst->bottom;
+  float dst_width = dst->right - dst->left;
+  float dst_height = dst->bottom - dst->top;
 
-  const float& sci_l = scissor.left;
-  const float& sci_t = scissor.top;
-  const float& sci_r = scissor.right;
-  const float& sci_b = scissor.bottom;
+  const float &sci_left = scissor.left;
+  const float &sci_top = scissor.top;
+  const float &sci_right = scissor.right;
+  const float &sci_bottom = scissor.bottom;
 
-  float left_cut_ratio = 0.0, right_cut_ratio = 0.0, top_cut_ratio = 0.0,
-    bottom_cut_ratio = 0.0;
+  float left_cut_ratio = 0.0, right_cut_ratio = 0.0, top_cut_ratio = 0.0, bottom_cut_ratio = 0.0;
+  bool need_cut = false;
 
-  if (dst_l < sci_l) {
-    left_cut_ratio = (sci_l - dst_l) / dst_w;
-    dst_l = sci_l;
+  if (dst_left < sci_left) {
+    left_cut_ratio = (sci_left - dst_left) / dst_width;
+    dst_left = sci_left;
+    need_cut = true;
   }
 
-  if (dst_r > sci_r) {
-    right_cut_ratio = (dst_r - sci_r) / dst_w;
-    dst_r = sci_r;
+  if (dst_right > sci_right) {
+    right_cut_ratio = (dst_right - sci_right) / dst_width;
+    dst_right = sci_right;
+    need_cut = true;
   }
 
-  if (dst_t < sci_t) {
-    top_cut_ratio = (sci_t - dst_t) / (dst_h);
-    dst_t = sci_t;
+  if (dst_top < sci_top) {
+    top_cut_ratio = (sci_top - dst_top) / (dst_height);
+    dst_top = sci_top;
+    need_cut = true;
   }
 
-  if (dst_b > sci_b) {
-    bottom_cut_ratio = (dst_b - sci_b) / (dst_h);
-    dst_b = sci_b;
+  if (dst_bottom > sci_bottom) {
+    bottom_cut_ratio = (dst_bottom - sci_bottom) / (dst_height);
+    dst_bottom = sci_bottom;
+    need_cut = true;
   }
 
-  CalculateCut(&left_cut_ratio, &top_cut_ratio, &right_cut_ratio, &bottom_cut_ratio, transform);
-  crop_l += crop_w * left_cut_ratio;
-  crop_t += crop_h * top_cut_ratio;
-  crop_r -= crop_w * right_cut_ratio;
-  crop_b -= crop_h * bottom_cut_ratio;
+  if (!need_cut)
+    return;
+
+  CalculateCut(transform, &left_cut_ratio, &top_cut_ratio, &right_cut_ratio, &bottom_cut_ratio);
+  crop_left += crop_width * left_cut_ratio;
+  crop_top += crop_height * top_cut_ratio;
+  crop_right -= crop_width * right_cut_ratio;
+  crop_bottom -= crop_height * bottom_cut_ratio;
 }
 
-bool ResManager::IsNonIntegralSrcCrop(const LayerRect& crop) {
-  if (crop.left - roundf(crop.left)     ||
-      crop.top - roundf(crop.top)       ||
-      crop.right - roundf(crop.right)   ||
-      crop.bottom - roundf(crop.bottom)) {
-    return true;
-  } else {
+bool ResManager::IsValidDimension(const LayerRect &src, const LayerRect &dst) {
+  // Make sure source in integral
+  if (src.left - roundf(src.left)     ||
+      src.top - roundf(src.top)       ||
+      src.right - roundf(src.right)   ||
+      src.bottom - roundf(src.bottom)) {
+    DLOGE_IF(kTagResources, "Input ROI is not integral");
     return false;
   }
-}
 
-void ResManager::IntegerizeRect(LayerRect *dst_rect, const LayerRect &src_rect) {
-  dst_rect->left = ceilf(src_rect.left);
-  dst_rect->top = ceilf(src_rect.top);
-  dst_rect->right = floorf(src_rect.right);
-  dst_rect->bottom = floorf(src_rect.bottom);
+  if (src.left > src.right || src.top > src.bottom || dst.left > dst.right ||
+      dst.top > dst.bottom) {
+    return false;
+  } else {
+    return true;
+  }
 }
 
 void ResManager::SetDecimationFactor(HWPipeInfo *pipe) {
@@ -248,5 +338,58 @@
   pipe->decimation = UINT8(powf(2.0f, decimation_factor));
 }
 
-}  // namespace sde
+void ResManager::SplitRect(bool flip_horizontal, const LayerRect &src_rect,
+                           const LayerRect &dst_rect, LayerRect *src_left, LayerRect *dst_left,
+                           LayerRect *src_right, LayerRect *dst_right) {
+  // Split rectangle horizontally and evenly into two.
+  float src_width = src_rect.right - src_rect.left;
+  float dst_width = dst_rect.right - dst_rect.left;
+  if (flip_horizontal) {
+    src_left->top = src_rect.top;
+    src_left->left = src_rect.left;
+    src_left->right = src_rect.left + (src_width / 2);
+    src_left->bottom = src_rect.bottom;
 
+    dst_left->top = dst_rect.top;
+    dst_left->left = dst_rect.left + (dst_width / 2);
+    dst_left->right = dst_rect.right;
+    dst_left->bottom = dst_rect.bottom;
+
+    src_right->top = src_rect.top;
+    src_right->left = src_left->right;
+    src_right->right = src_rect.right;
+    src_right->bottom = src_rect.bottom;
+
+    dst_right->top = dst_rect.top;
+    dst_right->left = dst_rect.left;
+    dst_right->right = dst_left->left;
+    dst_right->bottom = dst_rect.bottom;
+  } else {
+    src_left->top = src_rect.top;
+    src_left->left = src_rect.left;
+    src_left->right = src_rect.left + (src_width / 2);
+    src_left->bottom = src_rect.bottom;
+
+    dst_left->top = dst_rect.top;
+    dst_left->left = dst_rect.left;
+    dst_left->right = dst_rect.left + (dst_width / 2);
+    dst_left->bottom = dst_rect.bottom;
+
+    src_right->top = src_rect.top;
+    src_right->left = src_left->right;
+    src_right->right = src_rect.right;
+    src_right->bottom = src_rect.bottom;
+
+    dst_right->top = dst_rect.top;
+    dst_right->left = dst_left->right;
+    dst_right->right = dst_rect.right;
+    dst_right->bottom = dst_rect.bottom;
+  }
+}
+
+void ResManager::LogRectVerbose(const char *prefix, const LayerRect &roi) {
+  DLOGV_IF(kTagResources,"%s: left = %.0f, top = %.0f, right = %.0f, bottom = %.0f",
+           prefix, roi.left, roi.top, roi.right, roi.bottom);
+}
+
+}  // namespace sde
diff --git a/displayengine/libs/core/res_manager.cpp b/displayengine/libs/core/res_manager.cpp
old mode 100755
new mode 100644
index 1ccb410..7add793
--- a/displayengine/libs/core/res_manager.cpp
+++ b/displayengine/libs/core/res_manager.cpp
@@ -32,7 +32,7 @@
 namespace sde {
 
 ResManager::ResManager()
-  : num_pipe_(0), vig_pipes_(NULL), rgb_pipes_(NULL), dma_pipes_(NULL), frame_start_(false) {
+  : num_pipe_(0), vig_pipes_(NULL), rgb_pipes_(NULL), dma_pipes_(NULL), virtual_count_(0) {
 }
 
 DisplayError ResManager::Init(const HWResourceInfo &hw_res_info) {
@@ -42,7 +42,7 @@
 
   num_pipe_ = hw_res_info_.num_vig_pipe + hw_res_info_.num_rgb_pipe + hw_res_info_.num_dma_pipe;
 
-  if (UNLIKELY(num_pipe_ > kPipeIdMax)) {
+  if (num_pipe_ > kPipeIdMax) {
     DLOGE("Number of pipe is over the limit! %d", num_pipe_);
     return kErrorParameters;
   }
@@ -89,28 +89,27 @@
 }
 
 DisplayError ResManager::RegisterDisplay(DisplayType type, const HWDisplayAttributes &attributes,
-                                        Handle *display_ctx) {
+                                         Handle *display_ctx) {
   DisplayError error = kErrorNone;
 
   HWBlockType hw_block_id = kHWBlockMax;
   switch (type) {
   case kPrimary:
-    if (UNLIKELY(!hw_block_ctx_[kHWPrimary].is_in_use)) {
+    if (!hw_block_ctx_[kHWPrimary].is_in_use) {
       hw_block_id = kHWPrimary;
     }
     break;
 
   case kHDMI:
-    if (UNLIKELY(!hw_block_ctx_[kHWHDMI].is_in_use)) {
+    if (!hw_block_ctx_[kHWHDMI].is_in_use) {
       hw_block_id = kHWHDMI;
     }
     break;
 
   case kVirtual:
-    // assume only WB2 can be used for vitrual display
-    if (UNLIKELY(!hw_block_ctx_[kHWWriteback2].is_in_use)) {
-      hw_block_id = kHWWriteback2;
-    }
+    // assume only WB2 can be used for virtual display
+    virtual_count_++;
+    hw_block_id = kHWWriteback2;
     break;
 
   default:
@@ -118,12 +117,12 @@
     return kErrorParameters;
   }
 
-  if (UNLIKELY(hw_block_id == kHWBlockMax)) {
+  if (hw_block_id == kHWBlockMax) {
     return kErrorResources;
   }
 
   DisplayResourceContext *display_resource_ctx = new DisplayResourceContext();
-  if (UNLIKELY(!display_resource_ctx)) {
+  if (!display_resource_ctx) {
     return kErrorMemory;
   }
 
@@ -132,10 +131,12 @@
   display_resource_ctx->display_attributes = attributes;
   display_resource_ctx->display_type = type;
   display_resource_ctx->hw_block_id = hw_block_id;
+  if (!display_resource_ctx->display_attributes.is_device_split)
+    display_resource_ctx->display_attributes.split_left =
+      display_resource_ctx->display_attributes.x_pixels;
 
   *display_ctx = display_resource_ctx;
-
-  return kErrorNone;
+  return error;
 }
 
 DisplayError ResManager::UnregisterDisplay(Handle display_ctx) {
@@ -143,25 +144,30 @@
                           reinterpret_cast<DisplayResourceContext *>(display_ctx);
 
   Purge(display_ctx);
-  hw_block_ctx_[display_resource_ctx->hw_block_id].is_in_use = false;
+  if (display_resource_ctx->hw_block_id == kHWWriteback2) {
+    virtual_count_--;
+    if (!virtual_count_)
+      hw_block_ctx_[display_resource_ctx->hw_block_id].is_in_use = false;
+  } else {
+    hw_block_ctx_[display_resource_ctx->hw_block_id].is_in_use = false;
+  }
   delete display_resource_ctx;
 
   return kErrorNone;
 }
 
-
 DisplayError ResManager::Start(Handle display_ctx) {
   locker_.Lock();
 
   DisplayResourceContext *display_resource_ctx =
                           reinterpret_cast<DisplayResourceContext *>(display_ctx);
 
-  if (frame_start_) {
+  if (display_resource_ctx->frame_start) {
     return kErrorNone;  // keep context locked.
   }
 
   // First call in the cycle
-  frame_start_ = true;
+  display_resource_ctx->frame_start = true;
   display_resource_ctx->frame_count++;
 
   // Release the pipes not used in the previous cycle
@@ -172,6 +178,7 @@
       src_pipes_[i].state = kPipeStateIdle;
     }
   }
+
   return kErrorNone;
 }
 
@@ -191,22 +198,25 @@
   DisplayError error = kErrorNone;
   const struct HWLayersInfo &layer_info = hw_layers->info;
 
-  if (UNLIKELY(layer_info.count > num_pipe_)) {
+  if (layer_info.count > num_pipe_) {
     return kErrorResources;
   }
 
-  error = Config(display_resource_ctx, hw_layers);
-  if (UNLIKELY(error != kErrorNone)) {
+  uint32_t rotate_count = 0;
+  error = Config(display_resource_ctx, hw_layers, &rotate_count);
+  if (error != kErrorNone) {
     return error;
   }
 
-  uint32_t left_index = 0;
+  uint32_t left_index = kPipeIdMax;
   bool need_scale = false;
   HWBlockType hw_block_id = display_resource_ctx->hw_block_id;
+  HWBlockType rotator_block = kHWBlockMax;
 
   // Clear reserved marking
   for (uint32_t i = 0; i < num_pipe_; i++) {
-    src_pipes_[i].reserved = false;
+    if (src_pipes_[i].reserved_hw_block == hw_block_id)
+      src_pipes_[i].reserved_hw_block = kHWBlockMax;
   }
 
   for (uint32_t i = 0; i < layer_info.count; i++) {
@@ -220,26 +230,28 @@
 
     HWPipeInfo *pipe_info = &hw_layers->config[i].left_pipe;
 
-    need_scale = IsScalingNeeded(pipe_info);
-
     // Should have a generic macro
-    bool is_yuv = (layer.input_buffer->format >= kFormatYCbCr420Planar);
+    bool is_yuv = IsYuvFormat(layer.input_buffer->format);
 
-    left_index = GetPipe(hw_block_id, is_yuv, need_scale, false, use_non_dma_pipe);
-    if (left_index >= num_pipe_) {
-      goto Acquire_failed;
+    // left pipe is needed
+    if (pipe_info->pipe_id) {
+      need_scale = IsScalingNeeded(pipe_info);
+      left_index = GetPipe(hw_block_id, is_yuv, need_scale, false, use_non_dma_pipe);
+      if (left_index >= num_pipe_) {
+        goto CleanupOnError;
+      }
+      src_pipes_[left_index].reserved_hw_block = hw_block_id;
     }
 
-    src_pipes_[left_index].reserved = true;
     SetDecimationFactor(pipe_info);
 
     pipe_info =  &hw_layers->config[i].right_pipe;
     if (pipe_info->pipe_id == 0) {
       // assign single pipe
-      hw_layers->config[i].left_pipe.pipe_id = src_pipes_[left_index].mdss_pipe_id;
-      src_pipes_[left_index].at_right = false;
-      hw_layers->config[i].is_right_pipe = false;
-      src_pipes_[left_index].at_right = false;
+      if (left_index < num_pipe_) {
+        hw_layers->config[i].left_pipe.pipe_id = src_pipes_[left_index].mdss_pipe_id;
+        src_pipes_[left_index].at_right = false;
+      }
       continue;
     }
 
@@ -248,7 +260,7 @@
     uint32_t right_index;
     right_index = GetPipe(hw_block_id, is_yuv, need_scale, true, use_non_dma_pipe);
     if (right_index >= num_pipe_) {
-      goto Acquire_failed;
+      goto CleanupOnError;
     }
 
     if (src_pipes_[right_index].priority < src_pipes_[left_index].priority) {
@@ -257,25 +269,31 @@
     }
 
     // assign dual pipes
-    hw_layers->config[i].right_pipe.pipe_id = src_pipes_[right_index].mdss_pipe_id;
-    src_pipes_[right_index].reserved = true;
+    pipe_info->pipe_id = src_pipes_[right_index].mdss_pipe_id;
+    src_pipes_[right_index].reserved_hw_block = hw_block_id;
     src_pipes_[right_index].at_right = true;
-    src_pipes_[left_index].reserved = true;
+    src_pipes_[left_index].reserved_hw_block = hw_block_id;
     src_pipes_[left_index].at_right = false;
     hw_layers->config[i].left_pipe.pipe_id = src_pipes_[left_index].mdss_pipe_id;
     SetDecimationFactor(pipe_info);
+
+    DLOGV_IF(kTagResources, "Pipe acquired, layer index = %d, left_pipe = %x, right_pipe = %x",
+            i, hw_layers->config[i].left_pipe.pipe_id,  pipe_info->pipe_id);
   }
 
   if (!CheckBandwidth(display_resource_ctx, hw_layers)) {
     DLOGV_IF(kTagResources, "Bandwidth check failed!");
-    goto Acquire_failed;
+    goto CleanupOnError;
   }
 
   return kErrorNone;
 
-Acquire_failed:
-  for (uint32_t i = 0; i < num_pipe_; i++)
-    src_pipes_[i].reserved = false;
+CleanupOnError:
+  DLOGV_IF(kTagResources, "Resource reserving failed! hw_block = %d", hw_block_id);
+  for (uint32_t i = 0; i < num_pipe_; i++) {
+    if (src_pipes_[i].reserved_hw_block == hw_block_id)
+      src_pipes_[i].reserved_hw_block = kHWBlockMax;
+  }
   return kErrorResources;
 }
 
@@ -486,6 +504,7 @@
 }
 
 void ResManager::PostCommit(Handle display_ctx, HWLayers *hw_layers) {
+  SCOPE_LOCK(locker_);
   DisplayResourceContext *display_resource_ctx =
                           reinterpret_cast<DisplayResourceContext *>(display_ctx);
   HWBlockType hw_block_id = display_resource_ctx->hw_block_id;
@@ -494,23 +513,23 @@
   DLOGV_IF(kTagResources, "Resource for hw_block = %d, frame_count = %d", hw_block_id, frame_count);
 
   for (uint32_t i = 0; i < num_pipe_; i++) {
-    if (src_pipes_[i].reserved) {
+    if (src_pipes_[i].reserved_hw_block == hw_block_id) {
       src_pipes_[i].hw_block_id = hw_block_id;
       src_pipes_[i].state = kPipeStateAcquired;
       src_pipes_[i].state_frame_count = frame_count;
       DLOGV_IF(kTagResources, "Pipe acquired index = %d, type = %d, pipe_id = %x", i,
-            src_pipes_[i].type, src_pipes_[i].mdss_pipe_id);
+               src_pipes_[i].type, src_pipes_[i].mdss_pipe_id);
     } else if ((src_pipes_[i].hw_block_id == hw_block_id) &&
                (src_pipes_[i].state == kPipeStateAcquired)) {
       src_pipes_[i].state = kPipeStateToRelease;
       src_pipes_[i].state_frame_count = frame_count;
       DLOGV_IF(kTagResources, "Pipe to release index = %d, type = %d, pipe_id = %x", i,
-            src_pipes_[i].type, src_pipes_[i].mdss_pipe_id);
+               src_pipes_[i].type, src_pipes_[i].mdss_pipe_id);
     }
   }
 
   // handoff pipes which are used by splash screen
-  if (UNLIKELY((frame_count == 1) && (hw_block_id == kHWPrimary))) {
+  if ((frame_count == 1) && (hw_block_id == kHWPrimary)) {
     for (uint32_t i = 0; i < num_pipe_; i++) {
       if ((src_pipes_[i].state == kPipeStateOwnedByKernel)) {
         src_pipes_[i].state = kPipeStateToRelease;
@@ -518,8 +537,7 @@
       }
     }
   }
-
-  frame_start_ = false;
+  display_resource_ctx->frame_start = false;
 }
 
 void ResManager::Purge(Handle display_ctx) {
@@ -531,11 +549,10 @@
 
   for (uint32_t i = 0; i < num_pipe_; i++) {
     if (src_pipes_[i].hw_block_id == hw_block_id)
-      src_pipes_[i].state = kPipeStateIdle;
+      src_pipes_[i].ResetState();
   }
 }
 
-
 uint32_t ResManager::GetMdssPipeId(PipeType type, uint32_t index) {
   uint32_t mdss_id = kPipeIdMax;
   switch (type) {
@@ -572,34 +589,19 @@
   return (1 << mdss_id);
 }
 
-uint32_t ResManager::NextPipe(PipeType type, HWBlockType hw_block_id, bool at_right) {
-  uint32_t num_pipe = 0;
+uint32_t ResManager::SearchPipe(HWBlockType hw_block_id, SourcePipe *src_pipes,
+                                uint32_t num_pipe, bool at_right) {
   uint32_t index = kPipeIdMax;
-  SourcePipe *src_pipe = NULL;
+  SourcePipe *src_pipe;
+  HWBlockType dedicated_block;
 
-  switch (type) {
-  case kPipeTypeVIG:
-    src_pipe = vig_pipes_;
-    num_pipe = hw_res_info_.num_vig_pipe;
-    break;
-  case kPipeTypeRGB:
-    src_pipe = rgb_pipes_;
-    num_pipe = hw_res_info_.num_rgb_pipe;
-    break;
-  case kPipeTypeDMA:
-  default:
-    src_pipe = dma_pipes_;
-    num_pipe = hw_res_info_.num_dma_pipe;
-    break;
-  }
-
-  // search the pipe being used
+  // search dedicated idle pipes
   for (uint32_t i = 0; i < num_pipe; i++) {
-    if (!src_pipe[i].reserved &&
-        (src_pipe[i].state == kPipeStateAcquired) &&
-        (src_pipe[i].hw_block_id == hw_block_id) &&
-        (src_pipe[i].at_right == at_right)) {
-      index = src_pipe[i].index;
+    src_pipe = &src_pipes[i];
+    if (src_pipe->reserved_hw_block == kHWBlockMax &&
+        src_pipe->state == kPipeStateIdle &&
+        src_pipe->dedicated_hw_block == hw_block_id) {
+      index = src_pipe->index;
       break;
     }
   }
@@ -609,19 +611,63 @@
     return index;
   }
 
+  // search the pipe being used
   for (uint32_t i = 0; i < num_pipe; i++) {
-    if (!src_pipe[i].reserved &&
-        ((src_pipe[i].state == kPipeStateIdle) ||
-         ((src_pipe[i].state == kPipeStateAcquired) &&
-         (src_pipe[i].hw_block_id == hw_block_id)))) {
-      index = src_pipe[i].index;
+    src_pipe = &src_pipes[i];
+    dedicated_block = src_pipe->dedicated_hw_block;
+    if (src_pipe->reserved_hw_block == kHWBlockMax &&
+        (src_pipe->state == kPipeStateAcquired) &&
+        (src_pipe->hw_block_id == hw_block_id) &&
+        (src_pipe->at_right == at_right) &&
+        (dedicated_block == hw_block_id || dedicated_block == kHWBlockMax)) {
+      index = src_pipe->index;
       break;
     }
   }
 
+  // found
+  if (index < num_pipe_) {
+    return index;
+  }
+
+  // search the pipes idle or being used but not at the same side
+  for (uint32_t i = 0; i < num_pipe; i++) {
+    src_pipe = &src_pipes[i];
+    dedicated_block = src_pipe->dedicated_hw_block;
+    if (src_pipe->reserved_hw_block == kHWBlockMax &&
+        ((src_pipe->state == kPipeStateIdle) ||
+         (src_pipe->state == kPipeStateAcquired && src_pipe->hw_block_id == hw_block_id)) &&
+         (dedicated_block == hw_block_id || dedicated_block == kHWBlockMax)) {
+      index = src_pipe->index;
+      break;
+    }
+  }
   return index;
 }
 
+uint32_t ResManager::NextPipe(PipeType type, HWBlockType hw_block_id, bool at_right) {
+  uint32_t num_pipe = 0;
+  SourcePipe *src_pipes = NULL;
+
+  switch (type) {
+  case kPipeTypeVIG:
+    src_pipes = vig_pipes_;
+    num_pipe = hw_res_info_.num_vig_pipe;
+    break;
+  case kPipeTypeRGB:
+    src_pipes = rgb_pipes_;
+    num_pipe = hw_res_info_.num_rgb_pipe;
+    break;
+  case kPipeTypeDMA:
+  default:
+    src_pipes = dma_pipes_;
+    num_pipe = hw_res_info_.num_dma_pipe;
+    break;
+  }
+
+  return SearchPipe(hw_block_id, src_pipes, num_pipe, at_right);
+}
+
 uint32_t ResManager::GetPipe(HWBlockType hw_block_id, bool is_yuv, bool need_scale, bool at_right,
                              bool use_non_dma_pipe) {
   uint32_t index = kPipeIdMax;
@@ -634,7 +680,7 @@
       index = NextPipe(kPipeTypeDMA, hw_block_id, at_right);
     }
 
-    if ((index >= num_pipe_) && (!need_scale || hw_res_info_.has_non_scalar_rgb)) {
+    if ((index >= num_pipe_) && (!need_scale || !hw_res_info_.has_non_scalar_rgb)) {
       index = NextPipe(kPipeTypeRGB, hw_block_id, at_right);
     }
 
@@ -656,6 +702,14 @@
 
 void ResManager::AppendDump(char *buffer, uint32_t length) {
   SCOPE_LOCK(locker_);
+  AppendString(buffer, length, "\nresource manager pipe state");
+  for (uint32_t i = 0; i < num_pipe_; i++) {
+    SourcePipe *src_pipe = &src_pipes_[i];
+    AppendString(buffer, length,
+                 "\nindex = %d, id = %x, reserved = %d, state = %d, at_right = %d, dedicated = %d",
+                 src_pipe->index, src_pipe->mdss_pipe_id, src_pipe->reserved_hw_block,
+                 src_pipe->state, src_pipe->at_right, src_pipe->dedicated_hw_block);
+  }
 }
 
 }  // namespace sde
diff --git a/displayengine/libs/core/res_manager.h b/displayengine/libs/core/res_manager.h
index ab1c9c7..88c7fb7 100644
--- a/displayengine/libs/core/res_manager.h
+++ b/displayengine/libs/core/res_manager.h
@@ -39,7 +39,7 @@
   DisplayError Init(const HWResourceInfo &hw_res_info);
   DisplayError Deinit();
   DisplayError RegisterDisplay(DisplayType type, const HWDisplayAttributes &attributes,
-                              Handle *display_ctx);
+                               Handle *display_ctx);
   DisplayError UnregisterDisplay(Handle display_ctx);
   DisplayError Start(Handle display_ctx);
   DisplayError Stop(Handle display_ctx);
@@ -80,11 +80,12 @@
     kPipeStateOwnedByKernel,  // Pipe state when pipe is owned by kernel
   };
 
+  // todo: retrieve all these from kernel
   enum {
     kMaxSourcePipeWidth = 2048,
     kMaxInterfaceWidth = 2048,
-    kMaxCropWidth = 5,
-    kMaxCropHeight = 5,
+    kMaxRotateDownScaleRatio = 8,
+    kMaxNumRotator = 2,
   };
 
   struct SourcePipe {
@@ -96,11 +97,17 @@
     bool at_right;
     uint64_t state_frame_count;
     int priority;
-    bool reserved;
+    HWBlockType reserved_hw_block;
+    HWBlockType dedicated_hw_block;
 
-    SourcePipe() : type(kPipeTypeUnused), mdss_pipe_id(kPipeIdMax), index(0), state(kPipeStateIdle),
-                   hw_block_id(kHWBlockMax), at_right(false), state_frame_count(0), priority(0),
-                   reserved(false) { }
+    SourcePipe() : type(kPipeTypeUnused), mdss_pipe_id(kPipeIdMax), index(0),
+                   state(kPipeStateIdle), hw_block_id(kHWBlockMax), at_right(false),
+                   state_frame_count(0), priority(0), reserved_hw_block(kHWBlockMax),
+                   dedicated_hw_block(kHWBlockMax) { }
+
+    inline void ResetState() { state = kPipeStateIdle; hw_block_id = kHWBlockMax;
+        at_right = false; reserved_hw_block = kHWBlockMax; dedicated_hw_block = kHWBlockMax; }
+
   };
 
   struct DisplayResourceContext {
@@ -109,8 +116,10 @@
     HWBlockType hw_block_id;
     uint64_t frame_count;
     int32_t session_id;  // applicable for virtual display sessions only
-
-    DisplayResourceContext() : hw_block_id(kHWBlockMax), frame_count(0), session_id(-1) { }
+    uint32_t rotate_count;
+    bool frame_start;
+    DisplayResourceContext() : hw_block_id(kHWBlockMax), frame_count(0), session_id(-1),
+                    rotate_count(0), frame_start(false) { }
   };
 
   struct HWBlockContext {
@@ -118,25 +127,46 @@
     HWBlockContext() : is_in_use(false) { }
   };
 
+  struct HWRotator {
+    uint32_t pipe_index;
+    HWBlockType writeback_id;
+    uint32_t client_bit_mask;
+    HWRotator() : pipe_index(0), writeback_id(kHWBlockMax), client_bit_mask(0) { }
+  };
+
+  static const int kPipeIdNeedsAssignment = -1;
+
   uint32_t GetMdssPipeId(PipeType pipe_type, uint32_t index);
   uint32_t NextPipe(PipeType pipe_type, HWBlockType hw_block_id, bool at_right);
+  uint32_t SearchPipe(HWBlockType hw_block_id, SourcePipe *src_pipes, uint32_t num_pipe,
+                      bool at_right);
   uint32_t GetPipe(HWBlockType hw_block_id, bool is_yuv, bool need_scale, bool at_right,
                    bool use_non_dma_pipe);
   bool IsScalingNeeded(const HWPipeInfo *pipe_info);
-  DisplayError Config(DisplayResourceContext *display_resource_ctx, HWLayers *hw_layers);
-  bool IsValidDimension(const Layer &layer, float *width_scale, float *height_scale);
-  void CalculateCut(float *left_cut_ratio, float *top_cut_ratio, float *right_cut_ratio,
-                    float *bottom_cut_ratio, const LayerTransform &transform);
-  void CalculateCropRects(LayerRect *crop, LayerRect *dst,
-                          const LayerRect &scissor, const LayerTransform &transform);
-  bool IsNonIntegralSrcCrop(const LayerRect &crop);
-  void IntegerizeRect(LayerRect *dst_rect, const LayerRect &src_rect);
+  DisplayError Config(DisplayResourceContext *display_resource_ctx, HWLayers *hw_layers,
+                      uint32_t *rotate_count);
+  DisplayError DisplaySplitConfig(DisplayResourceContext *display_resource_ctx,
+                                  const Layer &layer, const LayerRect &src_rect,
+                                  const LayerRect &dst_rect, HWLayerConfig *layer_config);
+  DisplayError ValidateScaling(const Layer &layer, const LayerRect &crop,
+                               const LayerRect &dst, float *rot_scale_x, float *rot_scale_y);
+  void CalculateCut(const LayerTransform &transform, float *left_cut_ratio, float *top_cut_ratio,
+                    float *right_cut_ratio, float *bottom_cut_ratio);
+  void CalculateCropRects(const LayerRect &scissor, const LayerTransform &transform,
+                          LayerRect *crop, LayerRect *dst);
+  bool IsValidDimension(const LayerRect &src, const LayerRect &dst);
   bool CheckBandwidth(DisplayResourceContext *display_ctx, HWLayers *hw_layers);
   float GetPipeBw(DisplayResourceContext *display_ctx, HWPipeInfo *pipe, float bpp);
   float GetClockForPipe(DisplayResourceContext *display_ctx, HWPipeInfo *pipe);
   float GetOverlapBw(HWLayers *hw_layers, float *pipe_bw, bool left_mixer);
   void SetDecimationFactor(HWPipeInfo *pipe);
   float GetBpp(LayerBufferFormat format);
+  void SplitRect(bool flip_horizontal, const LayerRect &src_rect, const LayerRect &dst_rect,
+                 LayerRect *src_left, LayerRect *dst_left, LayerRect *src_right,
+                 LayerRect *dst_right);
+  bool IsMacroTileFormat(const LayerBuffer *buffer) { return buffer->flags.macro_tile; }
+  bool IsYuvFormat(LayerBufferFormat format) { return (format >= kFormatYCbCr420Planar); }
+  void LogRectVerbose(const char *prefix, const LayerRect &roi);
 
   template <class T>
   inline void Swap(T &a, T &b) {
@@ -157,6 +187,7 @@
   float bw_claimed_;  // Bandwidth claimed by other display
   float clk_claimed_;  // Clock claimed by other display
   float last_primary_bw_;
+  uint32_t virtual_count_;
 };
 
 }  // namespace sde