kmssink: refine resize logic

Refine scale check to get scale ratio by trying setplane from large
scale to small. When show frame, check if the required ratio in range,
if not, try not scale, if video is totally out of display, use the last
render area.

upstream status: i.Mx specific

Conflicts:
	sys/kms/gstkmssink.c
	sys/kms/gstkmssink.h
diff --git a/sys/kms/gstkmssink.c b/sys/kms/gstkmssink.c
index 19d2614..40c465e 100644
--- a/sys/kms/gstkmssink.c
+++ b/sys/kms/gstkmssink.c
@@ -76,6 +76,7 @@
     GstBuffer * buf);
 static void gst_kms_sink_video_overlay_init (GstVideoOverlayInterface * iface);
 static void gst_kms_sink_drain (GstKMSSink * self);
+static void ensure_kms_allocator (GstKMSSink * self);
 
 #define parent_class gst_kms_sink_parent_class
 G_DEFINE_TYPE_WITH_CODE (GstKMSSink, gst_kms_sink, GST_TYPE_VIDEO_SINK,
@@ -416,14 +417,44 @@
   return TRUE;
 }
 
+static guint
+check_upscale (GstKMSSink * self, guint32 fb_id) {
+  guint32 src_w = self->hdisplay / 10;
+  guint32 src_h = self->vdisplay / 10;
+  guint ratio;
+
+  for (ratio = 10; ratio > 0; ratio--) {
+    if (!drmModeSetPlane (self->ctrl_fd, self->plane_id, self->crtc_id, fb_id, 0,
+          0, 0, src_w * ratio, src_h * ratio,
+          0, 0, src_w << 16, src_h << 16))
+      break;
+  }
+
+  return ratio;
+}
+
+static guint
+check_downscale (GstKMSSink * self, guint32 fb_id) {
+  guint32 src_w = self->hdisplay / 10;
+  guint32 src_h = self->vdisplay / 10;
+  guint ratio;
+
+  for (ratio = 10; ratio > 0; ratio--) {
+    if (!drmModeSetPlane (self->ctrl_fd, self->plane_id, self->crtc_id, fb_id, 0,
+          0, 0, src_w, src_h,
+          0, 0, (src_w * ratio) << 16, (src_h * ratio) << 16))
+      break;
+  }
+
+  return ratio;
+}
+
 static void
 check_scaleable (GstKMSSink * self)
 {
-  gint result;
   guint32 fb_id;
-  guint32 width, height;
-  guint32 crtc_w, crtc_h;
   GstKMSMemory *kmsmem = NULL;
+  GstVideoInfo vinfo;
 
   /* we assume driver can scale at initialize,
    * if scale is checked or can not scale, we
@@ -434,26 +465,23 @@
   if (self->conn_id < 0 || !self->display_connected)
     return;
 
-  kmsmem = (GstKMSMemory *) gst_kms_allocator_bo_alloc (self->allocator, &self->vinfo);
+  gst_video_info_init (&vinfo);
+  gst_video_info_set_format (&vinfo, GST_VIDEO_FORMAT_NV12, self->hdisplay, self->vdisplay);
+
+  ensure_kms_allocator (self);
+
+  kmsmem = (GstKMSMemory *) gst_kms_allocator_bo_alloc (self->allocator, &vinfo);
   if (!kmsmem)
     return;
 
   fb_id = kmsmem->fb_id;
 
   GST_INFO_OBJECT (self, "checking scaleable");
+  self->downscale_ratio = check_downscale (self, fb_id);
+  self->upscale_ratio = check_upscale (self, fb_id);
 
-  width = GST_VIDEO_INFO_WIDTH (&self->vinfo);
-  height = GST_VIDEO_INFO_HEIGHT (&self->vinfo);
-  crtc_w = MIN (width / 2, self->hdisplay);
-  crtc_h = MIN (height / 2, self->vdisplay);
-
-  result = drmModeSetPlane (self->ctrl_fd, self->plane_id, self->crtc_id, fb_id, 0,
-      0, 0, crtc_w, crtc_h,
-      0, 0, width << 16, height << 16);
-  if (result) {
-    self->can_scale = FALSE;
-    GST_INFO_OBJECT (self, "scale is not support");
-  }
+  GST_INFO_OBJECT (self, "got scale ratio: up (%d) down (%d)",
+      self->upscale_ratio, self->downscale_ratio);
 
   self->scale_checked = TRUE;
   g_clear_pointer (&kmsmem, gst_memory_unref);
@@ -762,6 +790,8 @@
   GST_INFO_OBJECT (self, "display size: pixels = %dx%d / millimeters = %dx%d",
       self->hdisplay, self->vdisplay, self->mm_width, self->mm_height);
 
+  check_scaleable (self);
+
   self->pollfd.fd = self->fd;
   gst_poll_add_fd (self->poll, &self->pollfd);
   gst_poll_fd_ctl_read (self->poll, &self->pollfd, TRUE);
@@ -1109,8 +1139,6 @@
 
   self->vinfo = vinfo;
 
-  check_scaleable (self);
-
   GST_OBJECT_LOCK (self);
   if (self->reconfigure) {
     self->reconfigure = FALSE;
@@ -1668,6 +1696,22 @@
   }
 }
 
+static gboolean
+gst_kms_sink_check_scale_ratio (GstKMSSink * self, GstVideoRectangle dst, GstVideoRectangle src)
+{
+  gboolean can_scale = TRUE;
+  GST_INFO_OBJECT (self, "dst rectangle (%d, %d)-(%d x %d)", dst.x, dst.y, dst.w, dst.h);
+  GST_INFO_OBJECT (self, "src rectangle (%d, %d)-(%d x %d)", src.x, src.y, src.w, src.h);
+
+  can_scale = (dst.w * self->downscale_ratio >= src.w
+              && dst.w <= src.w * self->upscale_ratio
+              && dst.h * self->downscale_ratio >= src.h
+              && dst.h <= src.h * self->upscale_ratio);
+
+  GST_INFO_OBJECT (self, "can use hardware scale: %s", can_scale ? "TRUE" : "FALSE");
+  return can_scale;
+}
+
 static GstFlowReturn
 gst_kms_sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
 {
@@ -1682,6 +1726,7 @@
   GstPhyMemMeta *phymemmeta = NULL;
   guint64 dtrc_table_ofs;
   GstFlowReturn res;
+  gboolean can_scale = TRUE;
 
   dump_hdr10meta (GST_KMS_SINK (vsink), buf);
 
@@ -1751,7 +1796,7 @@
   dst.h = self->render_rect.h;
 
 retry_set_plane:
-  gst_video_sink_center_rect (src, dst, &result, self->can_scale);
+  gst_video_sink_center_rect (src, dst, &result, can_scale);
 
   result.x += self->render_rect.x;
   result.y += self->render_rect.y;
@@ -1764,22 +1809,40 @@
     src.h = GST_VIDEO_INFO_HEIGHT (&self->vinfo);
   }
 
-  /* handle out of screen case */
-  if ((result.x + result.w) > self->hdisplay)
-    result.w = self->hdisplay - result.x;
-
-  if ((result.y + result.h) > self->vdisplay)
-    result.h = self->vdisplay - result.y;
-
-  if (result.w <= 0 || result.h <= 0) {
-    GST_WARNING_OBJECT (self, "video is out of display range");
-    goto sync_frame;
+  if (!gst_kms_sink_check_scale_ratio (self, result, src)) {
+    if (can_scale) {
+      can_scale = FALSE;
+      dst.w = MAX (self->hdisplay, src.w);
+      dst.h = MAX (self->vdisplay, src.h);
+      GST_WARNING_OBJECT (self, "try not scale");
+      goto retry_set_plane;
+    } else
+      goto check_ratio_fail;
   }
 
-  /* to make sure it can be show when driver don't support scale */
-  if (!self->can_scale) {
-    src.w = result.w;
-    src.h = result.h;
+  GST_TRACE_OBJECT (self,
+      "scaling result at (%i,%i) %ix%i sourcing at (%i,%i) %ix%i",
+      result.x, result.y, result.w, result.h, src.x, src.y, src.w, src.h);
+
+  /* handle out of screen case */
+  if ((result.x + result.w) > self->hdisplay) {
+    gint crop_width = self->hdisplay - result.x;
+    if (crop_width > 0)
+      src.w = crop_width * src.w / result.w;
+    result.w = crop_width;
+  }
+
+  if ((result.y + result.h) > self->vdisplay) {
+    gint crop_height = self->vdisplay - result.y;
+    if (crop_height > 0)
+      src.h = crop_height * src.h / result.h;
+    result.h = crop_height;
+  }
+
+  if (result.w <= 0 || result.h <= 0 || src.h <= 0 || src.w <= 0) {
+    GST_WARNING_OBJECT (self, "video is out of display range, use previous area");
+    self->render_rect = self->last_rect;
+    goto done;
   }
 
   GST_TRACE_OBJECT (self,
@@ -1791,10 +1854,6 @@
       /* source/cropping coordinates are given in Q16 */
       src.x << 16, src.y << 16, src.w << 16, src.h << 16);
   if (ret) {
-    if (self->can_scale) {
-      self->can_scale = FALSE;
-      goto retry_set_plane;
-    }
     goto set_plane_failed;
   } else
     goto done;
@@ -1815,6 +1874,7 @@
   res = GST_FLOW_OK;
 
   self->frame_showed++;
+  self->last_rect = self->render_rect;
 
 bail:
   if (buf) {
@@ -1856,6 +1916,13 @@
         ("Error calculating the output display ratio of the video."));
     goto bail;
   }
+check_ratio_fail:
+  {
+    GST_OBJECT_UNLOCK (self);
+    GST_ELEMENT_ERROR (self, RESOURCE, FAILED, (NULL),
+        ("Checking scale ratio fail."));
+    goto bail;
+  }
 }
 
 static void
@@ -2005,6 +2072,8 @@
   sink->plane_id = -1;
   sink->can_scale = TRUE;
   sink->scale_checked = FALSE;
+  sink->upscale_ratio = 1;
+  sink->downscale_ratio = 1;
   gst_poll_fd_init (&sink->pollfd);
   sink->poll = gst_poll_new (TRUE);
   gst_video_info_init (&sink->vinfo);
diff --git a/sys/kms/gstkmssink.h b/sys/kms/gstkmssink.h
index 0c30eeb..15f6fc1 100644
--- a/sys/kms/gstkmssink.h
+++ b/sys/kms/gstkmssink.h
@@ -71,6 +71,8 @@
   gboolean has_async_page_flip;
   gboolean can_scale;
   gboolean scale_checked;
+  gint upscale_ratio;
+  gint downscale_ratio;
 
   /* global alpha */
   gboolean is_kmsproperty_set;
@@ -97,6 +99,7 @@
 
   /* render video rectangle */
   GstVideoRectangle render_rect;
+  GstVideoRectangle last_rect;
 
   /* reconfigure info if driver doesn't scale */
   GstVideoRectangle pending_rect;