Merge "sde: Add support for horizontal and vertical flip."
diff --git a/common.mk b/common.mk
index cdac0fa..768696a 100644
--- a/common.mk
+++ b/common.mk
@@ -27,15 +27,8 @@
ifeq ($(call is-board-platform-in-list, $(MSM_VIDC_TARGET_LIST)), true)
common_flags += -DVENUS_COLOR_FORMAT
-endif
-
-ifeq ($(call is-board-platform-in-list, msm8974 msm8226 msm8610 apq8084 \
- mpq8092 msm_bronze msm8916 msm8994), true)
common_flags += -DMDSS_TARGET
endif
-ifeq ($(call is-board-platform-in-list, msm8909), true)
- common_flags += -DVENUS_COLOR_FORMAT
-endif
common_deps :=
kernel_includes :=
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..99d392d 100644
--- a/displayengine/libs/core/hw_interface.h
+++ b/displayengine/libs/core/hw_interface.h
@@ -92,6 +92,20 @@
has_non_scalar_rgb(false), is_src_split(false), always_src_split(false) { }
};
+struct HWRotateInfo {
+ uint32_t pipe_id;
+ LayerRect src_roi;
+ LayerRect dst_roi;
+ HWBlockType writeback_id;
+ float downscale_ratio_x;
+ float downscale_ratio_y;
+
+ HWRotateInfo() : pipe_id(0), writeback_id(kHWWriteback0), downscale_ratio_x(1.0f),
+ downscale_ratio_y(1.0f) { }
+
+ inline void Reset() { *this = HWRotateInfo(); }
+};
+
struct HWPipeInfo {
uint32_t pipe_id;
LayerRect src_roi;
@@ -99,15 +113,19 @@
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
+ HWRotateInfo left_rotate; // rotation for left side of the buffer
+ HWRotateInfo right_rotate; // rotation 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..974897c
--- a/displayengine/libs/core/res_config.cpp
+++ b/displayengine/libs/core/res_config.cpp
@@ -32,115 +32,285 @@
namespace sde {
-DisplayError ResManager::Config(DisplayResourceContext *display_resource_ctx, HWLayers *hw_layers) {
- HWBlockType hw_block_id = display_resource_ctx->hw_block_id;
+void ResManager::RotationConfig(const LayerTransform &transform, LayerRect *src_rect,
+ HWRotateInfo *left_rotate, HWRotateInfo *right_rotate,
+ uint32_t *rotate_count) {
+ float src_width = src_rect->right - src_rect->left;
+ float src_height = src_rect->bottom - src_rect->top;
+ LayerRect dst_rect;
+ // Rotate output is a temp buffer, always output to the top left corner for saving memory
+ dst_rect.top = 0.0f;
+ dst_rect.left = 0.0f;
+ // downscale when doing rotation
+ dst_rect.right = src_height / left_rotate->downscale_ratio_x;
+ dst_rect.bottom = src_width / left_rotate->downscale_ratio_y;
+
+ left_rotate->src_roi = *src_rect;
+ left_rotate->pipe_id = kPipeIdNeedsAssignment;
+ left_rotate->dst_roi = dst_rect;
+ // Always use one rotator for now
+ right_rotate->Reset();
+
+ *src_rect = dst_rect;
+ (*rotate_count)++;
+}
+
+DisplayError ResManager::SrcSplitConfig(DisplayResourceContext *display_resource_ctx,
+ const LayerTransform &transform, 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;
+ HWPipeInfo *left_pipe = &layer_config->left_pipe;
+ HWPipeInfo *right_pipe = &layer_config->right_pipe;
+ layer_config->is_right_pipe = false;
- 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");
+ if ((src_rect.right - src_rect.left) >= kMaxSourcePipeWidth ||
+ (dst_rect.right - dst_rect.left) >= kMaxInterfaceWidth || hw_res_info_.always_src_split) {
+ SplitRect(transform.flip_horizontal, src_rect, dst_rect, &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 {
+ left_pipe->src_roi = src_rect;
+ left_pipe->dst_roi = dst_rect;
+ left_pipe->pipe_id = kPipeIdNeedsAssignment;
+ right_pipe->Reset();
+ }
+ return kErrorNone;
+}
+
+DisplayError ResManager::DisplaySplitConfig(DisplayResourceContext *display_resource_ctx,
+ const LayerTransform &transform,
+ const LayerRect &src_rect, const LayerRect &dst_rect,
+ HWLayerConfig *layer_config) {
+ HWDisplayAttributes &display_attributes = display_resource_ctx->display_attributes;
+ // 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);
+
+ crop_left = src_rect;
+ dst_left = dst_rect;
+ crop_right = crop_left;
+ dst_right = dst_left;
+ 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;
+ HWRotateInfo *left_rotate, *right_rotate;
+ // config rotator first
+ left_rotate = &hw_layers->config[i].left_rotate;
+ right_rotate = &hw_layers->config[i].right_rotate;
- if ((crop_w < kMaxCropWidth) ||(crop_h < kMaxCropHeight)) {
- return false;
- }
+ LayerTransform transform = layer.transform;
+ if (IsRotationNeeded(transform.rotation) ||
+ UINT32(rot_scale_x) != 1 || UINT32(rot_scale_y) != 1) {
+ left_rotate->downscale_ratio_x = rot_scale_x;
+ right_rotate->downscale_ratio_x = rot_scale_x;
+ left_rotate->downscale_ratio_y = rot_scale_y;
+ right_rotate->downscale_ratio_y = rot_scale_y;
- 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;
- }
+ RotationConfig(layer.transform, &src_rect, left_rotate, right_rotate, rotate_count);
+ // rotator will take care of flipping, reset tranform
+ transform = LayerTransform();
} else {
- if (w_scale > max_scale_down || h_scale > max_scale_down) {
- return false;
- }
+ left_rotate->Reset();
+ right_rotate->Reset();
+ }
+
+ if (hw_res_info_.is_src_split) {
+ error = SrcSplitConfig(display_resource_ctx, transform, src_rect,
+ dst_rect, &hw_layers->config[i]);
+ } else {
+ error = DisplaySplitConfig(display_resource_ctx, transform, src_rect,
+ dst_rect, &hw_layers->config[i]);
+ }
+
+ if (error != kErrorNone)
+ break;
+
+ 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 = IsRotationNeeded(layer.transform.rotation);
+ 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;
+ uint32_t max_downscale_with_rotator;
+
+ if (hw_res_info_.has_rotator_downscale)
+ max_downscale_with_rotator = max_scale_down * kMaxRotateDownScaleRatio;
+ else
+ max_downscale_with_rotator = 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;
+ } else if (scale_x > max_downscale_with_rotator || scale_y > max_downscale_with_rotator) {
+ DLOGV_IF(kTagResources, "Scaling down is over the limit scale_x = %d, scale_y = %d",
+ 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);
}
@@ -149,7 +319,7 @@
Swap(*top_cut_ratio, *bottom_cut_ratio);
}
- if (UINT32(transform.rotation) == 90) {
+ if (IsRotationNeeded(transform.rotation)) {
// Anti clock swapping
float tmp_cut_ratio = *left_cut_ratio;
*left_cut_ratio = *top_cut_ratio;
@@ -159,75 +329,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 +423,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..38ef319
--- 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) {
@@ -40,9 +40,11 @@
DisplayError error = kErrorNone;
+ // TODO: Remove this. Disable src_split as kernel not supported yet
+ hw_res_info_.is_src_split = false;
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;
}
@@ -77,6 +79,20 @@
DLOGI("hw_rev=%x, DMA=%d RGB=%d VIG=%d", hw_res_info_.hw_revision, hw_res_info_.num_dma_pipe,
hw_res_info_.num_rgb_pipe, hw_res_info_.num_vig_pipe);
+ if (hw_res_info_.num_rotator > kMaxNumRotator) {
+ DLOGE("Number of rotator is over the limit! %d", hw_res_info_.num_rotator);
+ return kErrorParameters;
+ }
+
+ if (hw_res_info_.num_rotator > 0) {
+ rotators_[0].pipe_index = dma_pipes_[0].index;
+ rotators_[0].writeback_id = kHWWriteback0;
+ }
+ if (hw_res_info_.num_rotator > 1) {
+ rotators_[1].pipe_index = dma_pipes_[1].index;
+ rotators_[1].writeback_id = kHWWriteback1;
+ }
+
// Used by splash screen
rgb_pipes_[0].state = kPipeStateOwnedByKernel;
rgb_pipes_[1].state = kPipeStateOwnedByKernel;
@@ -89,28 +105,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 +133,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 +147,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 +160,33 @@
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;
+ }
+ if (!hw_block_ctx_[display_resource_ctx->hw_block_id].is_in_use)
+ ClearDedicated(display_resource_ctx->hw_block_id);
+
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 +197,20 @@
src_pipes_[i].state = kPipeStateIdle;
}
}
+
+ // Clear rotator usage
+ for (uint32_t i = 0; i < hw_res_info_.num_rotator; i++) {
+ uint32_t pipe_index;
+ pipe_index = rotators_[i].pipe_index;
+ if (rotators_[i].client_bit_mask == 0 &&
+ src_pipes_[pipe_index].state == kPipeStateToRelease &&
+ src_pipes_[pipe_index].hw_block_id == rotators_[i].writeback_id) {
+ src_pipes_[pipe_index].dedicated_hw_block = kHWBlockMax;
+ src_pipes_[pipe_index].state = kPipeStateIdle;
+ }
+ CLEAR_BIT(rotators_[i].client_bit_mask, display_resource_ctx->hw_block_id);
+ }
+
return kErrorNone;
}
@@ -191,24 +230,33 @@
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;
}
+ // allocate rotator
+ error = AcquireRotator(display_resource_ctx, rotate_count);
+ if (error != kErrorNone)
+ return error;
+
+ rotate_count = 0;
for (uint32_t i = 0; i < layer_info.count; i++) {
Layer &layer = layer_info.stack->layers[layer_info.index[i]];
bool use_non_dma_pipe = hw_layers->config[i].use_non_dma_pipe;
@@ -218,28 +266,33 @@
use_non_dma_pipe = true;
}
+ AssignRotator(&hw_layers->config[i].left_rotate, &rotate_count);
+ AssignRotator(&hw_layers->config[i].right_rotate, &rotate_count);
+
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 +301,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 +310,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 +545,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 +554,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 +578,21 @@
}
}
}
-
- frame_start_ = false;
+ // set rotator pipes
+ for (uint32_t i = 0; i < hw_res_info_.num_rotator; i++) {
+ uint32_t pipe_index;
+ pipe_index = rotators_[i].pipe_index;
+ if (IS_BIT_SET(rotators_[i].client_bit_mask, hw_block_id)) {
+ src_pipes_[pipe_index].hw_block_id = rotators_[i].writeback_id;
+ src_pipes_[pipe_index].state = kPipeStateAcquired;
+ } else if (!rotators_[i].client_bit_mask) {
+ src_pipes_[pipe_index].dedicated_hw_block = kHWBlockMax;
+ if (src_pipes_[pipe_index].hw_block_id == rotators_[i].writeback_id &&
+ src_pipes_[pipe_index].state == kPipeStateAcquired)
+ src_pipes_[pipe_index].state = kPipeStateToRelease;
+ }
+ }
+ display_resource_ctx->frame_start = false;
}
void ResManager::Purge(Handle display_ctx) {
@@ -531,11 +604,11 @@
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();
}
+ ClearRotator(display_resource_ctx);
}
-
uint32_t ResManager::GetMdssPipeId(PipeType type, uint32_t index) {
uint32_t mdss_id = kPipeIdMax;
switch (type) {
@@ -572,34 +645,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 +667,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 +736,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 +758,184 @@
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);
+ }
+}
+
+// This is to reserve resources for the device or rotator
+DisplayError ResManager::MarkDedicated(DisplayResourceContext *display_resource_ctx,
+ struct HWRotator *rotator) {
+ uint32_t num_base_pipe = 0, base_pipe_index = 0, num_vig_pipe = 0, i, vig_pipe_index = 0;
+ bool force = false;
+ SourcePipe *src_pipe = NULL;
+ HWBlockType hw_block_id;
+ if (rotator == NULL) {
+ hw_block_id = display_resource_ctx->hw_block_id;
+ switch (display_resource_ctx->display_type) {
+ case kPrimary:
+ src_pipe= rgb_pipes_;
+ if (display_resource_ctx->display_attributes.is_device_split ||
+ hw_res_info_.is_src_split) {
+ num_base_pipe = 2;
+ num_vig_pipe = 2;
+ } else {
+ num_base_pipe = 1;
+ num_vig_pipe = 1;
+ }
+ src_pipe = rgb_pipes_;
+ force = true;
+ break;
+ // HDMI and Virtual are using the same strategy
+ case kHDMI:
+ case kVirtual:
+ if (display_resource_ctx && display_resource_ctx->display_attributes.is_device_split) {
+ num_base_pipe = 2;
+ num_vig_pipe = 2;
+ } else {
+ num_base_pipe = 1;
+ num_vig_pipe = 1;
+ }
+ break;
+ default:
+ DLOGE("Wrong device type %d", display_resource_ctx->display_type);
+ break;
+ }
+ } else {
+ hw_block_id = rotator->writeback_id;
+ num_base_pipe = 1;
+ base_pipe_index = rotator->pipe_index;
+ src_pipe = &src_pipes_[base_pipe_index];
+ force = true;
+ }
+
+ if (!num_base_pipe) {
+ DLOGE("Cannot reserve dedicated pipe %d", hw_block_id);
+ return kErrorResources;
+ }
+
+ // Only search the assigned pipe type
+ if (force) {
+ for (i = 0; i < num_base_pipe; i++) {
+ if (src_pipe->dedicated_hw_block != kHWBlockMax)
+ DLOGV_IF(kTagResources, "Overwrite dedicated block %d", src_pipe->dedicated_hw_block);
+ src_pipe->dedicated_hw_block = hw_block_id;
+ src_pipe++;
+ }
+ num_base_pipe = 0;
+ } else {
+ // search available pipes
+ src_pipe = rgb_pipes_;
+ uint32_t num_pipe = hw_res_info_.num_rgb_pipe + hw_res_info_.num_vig_pipe;
+ for (i = 0; i < num_pipe; i++) {
+ if (src_pipe->dedicated_hw_block == kHWBlockMax ||
+ src_pipe->dedicated_hw_block == hw_block_id) {
+ src_pipe->dedicated_hw_block = hw_block_id;
+ num_base_pipe--;
+ if (!num_base_pipe)
+ break;
+ }
+ src_pipe++;
+ }
+ if (num_base_pipe) {
+ for (i = 0; i < num_pipe; i++) {
+ if (src_pipe->dedicated_hw_block == hw_block_id)
+ src_pipe->dedicated_hw_block = kHWBlockMax;
+ src_pipe++;
+ }
+ DLOGE("Cannot reserve dedicated pipe %d", hw_block_id);
+ return kErrorResources;
+ }
+ }
+ // optional for vig pipes
+ src_pipe= vig_pipes_;
+ for (i = 0; i < num_vig_pipe; i++) {
+ if (src_pipe->dedicated_hw_block == kHWBlockMax ||
+ src_pipe->dedicated_hw_block == hw_block_id) {
+ src_pipe->dedicated_hw_block = hw_block_id;
+ num_vig_pipe--;
+ if (!num_vig_pipe)
+ break;
+ }
+ src_pipe++;
+ }
+ return kErrorNone;
+}
+
+void ResManager::ClearDedicated(HWBlockType hw_block_id) {
+ SourcePipe *src_pipe = src_pipes_;
+ for (uint32_t i = 0; i < num_pipe_; i++) {
+ if (src_pipe->dedicated_hw_block == hw_block_id)
+ src_pipe->dedicated_hw_block = kHWBlockMax;
+ }
+}
+
+DisplayError ResManager::AcquireRotator(DisplayResourceContext *display_resource_ctx,
+ const uint32_t rotate_count) {
+ if (rotate_count == 0)
+ return kErrorNone;
+ if (hw_res_info_.num_rotator == 0)
+ return kErrorResources;
+
+ uint32_t i, j, pipe_index, num_rotator;
+ if (rotate_count > hw_res_info_.num_rotator)
+ num_rotator = hw_res_info_.num_rotator;
+ else
+ num_rotator = rotate_count;
+
+ for (i = 0; i < num_rotator; i++) {
+ uint32_t rotate_pipe_index = rotators_[i].pipe_index;
+ MarkDedicated(display_resource_ctx, &rotators_[i]);
+ if (src_pipes_[rotate_pipe_index].reserved_hw_block != kHWBlockMax) {
+ DLOGV_IF(kTagResources, "pipe %x is reserved by block:%d",
+ src_pipes_[rotate_pipe_index].mdss_pipe_id,
+ src_pipes_[rotate_pipe_index].reserved_hw_block);
+ return kErrorResources;
+ }
+ pipe_index = SearchPipe(rotators_[i].writeback_id, &src_pipes_[rotate_pipe_index], 1, false);
+ if (pipe_index >= num_pipe_) {
+ DLOGV_IF(kTagResources, "pipe %x is not ready for rotator",
+ src_pipes_[rotate_pipe_index].mdss_pipe_id);
+ return kErrorResources;
+ }
+ }
+
+ for (i = 0; i < num_rotator; i++)
+ SET_BIT(rotators_[i].client_bit_mask, display_resource_ctx->hw_block_id);
+
+ return kErrorNone;
+}
+
+void ResManager::AssignRotator(HWRotateInfo *rotate, uint32_t *rotate_count) {
+ if (!rotate->pipe_id)
+ return;
+ // Interleave rotator assignment
+ if ((*rotate_count & 0x1) && (hw_res_info_.num_rotator > 1)) {
+ rotate->pipe_id = src_pipes_[rotators_[1].pipe_index].mdss_pipe_id;
+ rotate->writeback_id = rotators_[1].writeback_id;
+ } else {
+ rotate->pipe_id = src_pipes_[rotators_[0].pipe_index].mdss_pipe_id;
+ rotate->writeback_id = rotators_[0].writeback_id;
+ }
+ (*rotate_count)++;
+}
+
+void ResManager::ClearRotator(DisplayResourceContext *display_resource_ctx) {
+ for (uint32_t i = 0; i < hw_res_info_.num_rotator; i++) {
+ uint32_t pipe_index;
+ pipe_index = rotators_[i].pipe_index;
+ CLEAR_BIT(rotators_[i].client_bit_mask, display_resource_ctx->hw_block_id);
+ if (rotators_[i].client_bit_mask == 0 &&
+ src_pipes_[pipe_index].dedicated_hw_block == rotators_[i].writeback_id) {
+ src_pipes_[pipe_index].dedicated_hw_block = kHWBlockMax;
+ src_pipes_[pipe_index].state = kPipeStateIdle;
+ }
+ }
}
} // namespace sde
diff --git a/displayengine/libs/core/res_manager.h b/displayengine/libs/core/res_manager.h
index ab1c9c7..18900f3 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,60 @@
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 LayerTransform &transform, 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);
+ DisplayError SrcSplitConfig(DisplayResourceContext *display_resource_ctx,
+ const LayerTransform &transform, const LayerRect &src_rect,
+ const LayerRect &dst_rect, HWLayerConfig *layer_config);
+ 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); }
+ bool IsRotationNeeded(float rotation)
+ { return (UINT32(rotation) == 90 || UINT32(rotation) == 270); }
+ void LogRectVerbose(const char *prefix, const LayerRect &roi);
+ DisplayError MarkDedicated(DisplayResourceContext *display_resource_ctx,
+ struct HWRotator *rotator);
+ void ClearDedicated(HWBlockType hw_block_id);
+ void RotationConfig(const LayerTransform &transform, LayerRect *src_rect,
+ HWRotateInfo *left_rotate, HWRotateInfo *right_rotate, uint32_t *rotate_cnt);
+ DisplayError AcquireRotator(DisplayResourceContext *display_resource_ctx,
+ const uint32_t roate_cnt);
+ void AssignRotator(HWRotateInfo *rotate, uint32_t *rotate_cnt);
+ void ClearRotator(DisplayResourceContext *display_resource_ctx);
template <class T>
inline void Swap(T &a, T &b) {
@@ -157,6 +201,8 @@
float bw_claimed_; // Bandwidth claimed by other display
float clk_claimed_; // Clock claimed by other display
float last_primary_bw_;
+ uint32_t virtual_count_;
+ struct HWRotator rotators_[kMaxNumRotator];
};
} // namespace sde
diff --git a/libhwcomposer/hwc_mdpcomp.cpp b/libhwcomposer/hwc_mdpcomp.cpp
index a5bf0b5..569f5d1 100644
--- a/libhwcomposer/hwc_mdpcomp.cpp
+++ b/libhwcomposer/hwc_mdpcomp.cpp
@@ -19,6 +19,7 @@
#include <math.h>
#include "hwc_mdpcomp.h"
#include <sys/ioctl.h>
+#include <dlfcn.h>
#include "hdmi.h"
#include "qdMetaData.h"
#include "mdp_version.h"
@@ -50,6 +51,12 @@
int MDPComp::sMaxSecLayers = 1;
bool MDPComp::enablePartialUpdateForMDP3 = false;
bool MDPComp::sIsPartialUpdateActive = true;
+void *MDPComp::sLibPerfHint = NULL;
+int MDPComp::sPerfLockHandle = 0;
+int (*MDPComp::sPerfLockAcquire)(int, int, int*, int) = NULL;
+int (*MDPComp::sPerfLockRelease)(int value) = NULL;
+int MDPComp::sPerfHintWindow = -1;
+
MDPComp* MDPComp::getObject(hwc_context_t *ctx, const int& dpy) {
if(qdutils::MDPVersion::getInstance().isSrcSplit()) {
sSrcSplitEnabled = true;
@@ -194,6 +201,14 @@
sIsPartialUpdateActive = getPartialUpdatePref(ctx);
+ if(property_get("persist.mdpcomp_perfhint", property, "-1") > 0) {
+ int val = atoi(property);
+ if(val > 0 && loadPerfLib()) {
+ sPerfHintWindow = val;
+ ALOGI("PerfHintWindow = %d", sPerfHintWindow);
+ }
+ }
+
return true;
}
@@ -1895,25 +1910,38 @@
return true;
}
+// Checks only if videos or single layer(RGB) is updating
+// which is used for setting dynamic fps or perf hint for single
+// layer video playback
+bool MDPComp::onlyVideosUpdating(hwc_context_t *ctx,
+ hwc_display_contents_1_t* list) {
+ bool support = false;
+ FrameInfo frame;
+ frame.reset(mCurrentFrame.layerCount);
+ memset(&frame.drop, 0, sizeof(frame.drop));
+ frame.dropCount = 0;
+ ALOGD_IF(isDebug(), "%s: Update Cache and YUVInfo", __FUNCTION__);
+ updateLayerCache(ctx, list, frame);
+ updateYUV(ctx, list, false /*secure only*/, frame);
+ // There are only updating YUV layers or there is single RGB
+ // Layer(Youtube)
+ if((ctx->listStats[mDpy].yuvCount == frame.mdpCount) ||
+ (frame.layerCount == 1)) {
+ support = true;
+ }
+ return support;
+}
+
void MDPComp::setDynRefreshRate(hwc_context_t *ctx, hwc_display_contents_1_t* list) {
//For primary display, set the dynamic refreshrate
if(!mDpy && qdutils::MDPVersion::getInstance().isDynFpsSupported() &&
ctx->mUseMetaDataRefreshRate) {
- FrameInfo frame;
- frame.reset(mCurrentFrame.layerCount);
- memset(&frame.drop, 0, sizeof(frame.drop));
- frame.dropCount = 0;
- ALOGD_IF(isDebug(), "%s: Update Cache and YUVInfo for Dyn Refresh Rate",
- __FUNCTION__);
- updateLayerCache(ctx, list, frame);
- updateYUV(ctx, list, false /*secure only*/, frame);
uint32_t refreshRate = ctx->dpyAttr[mDpy].refreshRate;
MDPVersion& mdpHw = MDPVersion::getInstance();
if(sIdleFallBack) {
//Set minimum panel refresh rate during idle timeout
refreshRate = mdpHw.getMinFpsSupported();
- } else if((ctx->listStats[mDpy].yuvCount == frame.mdpCount) ||
- (frame.layerCount == 1)) {
+ } else if(onlyVideosUpdating(ctx, list)) {
//Set the new fresh rate, if there is only one updating YUV layer
//or there is one single RGB layer with this request
refreshRate = ctx->listStats[mDpy].refreshRateRequest;
@@ -2031,6 +2059,7 @@
#ifdef DYNAMIC_FPS
setDynRefreshRate(ctx, list);
#endif
+ setPerfHint(ctx, list);
mCachedFrame.cacheAll(list);
mCachedFrame.updateCounts(mCurrentFrame);
@@ -2817,5 +2846,66 @@
sIsPartialUpdateActive = enable;
return 0;
}
+
+bool MDPComp::loadPerfLib() {
+ char perfLibPath[PROPERTY_VALUE_MAX] = {0};
+ bool success = false;
+ if((property_get("ro.vendor.extension_library", perfLibPath, NULL) <= 0)) {
+ ALOGE("vendor library not set in ro.vendor.extension_library");
+ return false;
+ }
+
+ sLibPerfHint = dlopen(perfLibPath, RTLD_NOW);
+ if(sLibPerfHint) {
+ *(void **)&sPerfLockAcquire = dlsym(sLibPerfHint, "perf_lock_acq");
+ *(void **)&sPerfLockRelease = dlsym(sLibPerfHint, "perf_lock_rel");
+ if (!sPerfLockAcquire || !sPerfLockRelease) {
+ ALOGE("Failed to load symbols for perfLock");
+ dlclose(sLibPerfHint);
+ sLibPerfHint = NULL;
+ return false;
+ }
+ success = true;
+ ALOGI("Successfully Loaded perf hint API's");
+ } else {
+ ALOGE("Failed to open %s : %s", perfLibPath, dlerror());
+ }
+ return success;
+}
+
+void MDPComp::setPerfHint(hwc_context_t *ctx, hwc_display_contents_1_t* list) {
+ if ((sPerfHintWindow < 0) || mDpy || !sLibPerfHint) {
+ return;
+ }
+ static int count = sPerfHintWindow;
+ static int perflockFlag = 0;
+
+ /* Send hint to mpctl when single layer is updated
+ * for a successful number of windows. Hint release
+ * happens immediately upon multiple layer update.
+ */
+ if (onlyVideosUpdating(ctx, list)) {
+ if(count) {
+ count--;
+ }
+ } else {
+ if (perflockFlag) {
+ perflockFlag = 0;
+ sPerfLockRelease(sPerfLockHandle);
+ }
+ count = sPerfHintWindow;
+ }
+ if (count == 0 && !perflockFlag) {
+ int perfHint = 0x4501; // 45-display layer hint, 01-Enable
+ sPerfLockHandle = sPerfLockAcquire(0 /*handle*/, 0/*duration*/,
+ &perfHint, sizeof(perfHint)/sizeof(int));
+ if(sPerfLockHandle < 0) {
+ ALOGE("Perf Lock Acquire Failed");
+ } else {
+ perflockFlag = 1;
+ }
+ }
+}
+
}; //namespace
diff --git a/libhwcomposer/hwc_mdpcomp.h b/libhwcomposer/hwc_mdpcomp.h
index 4978182..302b047 100644
--- a/libhwcomposer/hwc_mdpcomp.h
+++ b/libhwcomposer/hwc_mdpcomp.h
@@ -247,6 +247,10 @@
hwc_display_contents_1_t* list);
/* checks for conditions to enable partial udpate */
bool canPartialUpdate(hwc_context_t *ctx, hwc_display_contents_1_t* list);
+ // Checks if only videocontent is updating
+ bool onlyVideosUpdating(hwc_context_t *ctx, hwc_display_contents_1_t* list);
+ static bool loadPerfLib();
+ void setPerfHint(hwc_context_t *ctx, hwc_display_contents_1_t* list);
int mDpy;
static bool sEnabled;
@@ -269,6 +273,12 @@
bool allocSplitVGPipesfor4k2k(hwc_context_t *ctx, int index);
//Enable Partial Update for MDP3 targets
static bool enablePartialUpdateForMDP3;
+ static void *sLibPerfHint;
+ static int sPerfLockHandle;
+ static int (*sPerfLockAcquire)(int, int, int*, int);
+ static int (*sPerfLockRelease)(int value);
+ static int sPerfHintWindow;
+
};
class MDPCompNonSplit : public MDPComp {
diff --git a/liboverlay/overlayUtils.cpp b/liboverlay/overlayUtils.cpp
index b772a6e..dd030ef 100644
--- a/liboverlay/overlayUtils.cpp
+++ b/liboverlay/overlayUtils.cpp
@@ -280,10 +280,11 @@
if(src_w > (int) mdpHw.getMaxPipeWidth()) {
//If the client sends us something > what a layer mixer supports
//then it means it doesn't want to use split-pipe but wants us to
- //decimate. A minimum decimation of 2 will ensure that the width is
+ //decimate. A minimum decimation of 1 will ensure that the width is
//always within layer mixer limits.
- if(horzDeci < 2)
- horzDeci = 2;
+ const uint8_t minDeci = 1;
+ if(horzDeci < minDeci)
+ horzDeci = minDeci;
}
}