compositor: Skip pads that are completely obscured by a higher zorder pad

For each frame, compare the frame boundaries, check if the format contains an
alpha channel, check opacity, and skip the frame if it's going to be completely
overwritten by a higher zorder frame. The check is O(n^2), but that doesn't
matter here because the number of sinkpads is small.

More can be done to avoid needless drawing, but this covers the majority of
cases. See TODOs. Ideally, a reverse painter's algorithm should be used for
optimal drawing, but memcpy during compositing is small compared to the CPU used
for frame conversion on each pad.

https://bugzilla.gnome.org/show_bug.cgi?id=746147
This commit is contained in:
Nirbheek Chauhan 2015-04-18 15:09:02 +05:30 committed by Tim-Philipp Müller
parent 6fc55a997e
commit e0fd23cb23

View file

@ -296,6 +296,17 @@ gst_compositor_pad_set_info (GstVideoAggregatorPad * pad,
return TRUE; return TRUE;
} }
/* Test whether rectangle2 contains rectangle 1 (geometrically) */
static gboolean
is_rectangle_contained (GstVideoRectangle rect1, GstVideoRectangle rect2)
{
if ((rect2.x <= rect1.x) && (rect2.y <= rect1.y) &&
((rect2.x + rect2.w) >= (rect1.x + rect1.w)) &&
((rect2.y + rect2.h) >= (rect1.y + rect1.h)))
return TRUE;
return FALSE;
}
static gboolean static gboolean
gst_compositor_pad_prepare_frame (GstVideoAggregatorPad * pad, gst_compositor_pad_prepare_frame (GstVideoAggregatorPad * pad,
GstVideoAggregator * vagg) GstVideoAggregator * vagg)
@ -307,6 +318,11 @@ gst_compositor_pad_prepare_frame (GstVideoAggregatorPad * pad,
GstVideoFrame *frame; GstVideoFrame *frame;
static GstAllocationParams params = { 0, 15, 0, 0, }; static GstAllocationParams params = { 0, 15, 0, 0, };
gint width, height; gint width, height;
gboolean frame_obscured = FALSE;
GList *l;
/* The rectangle representing this frame, clamped to the video's boundaries.
* Due to the clamping, this is different from the frame width/height above. */
GstVideoRectangle frame_rect;
if (!pad->buffer) if (!pad->buffer)
return TRUE; return TRUE;
@ -319,6 +335,17 @@ gst_compositor_pad_prepare_frame (GstVideoAggregatorPad * pad,
return FALSE; return FALSE;
} }
/* There's three types of width/height here:
* 1. GST_VIDEO_FRAME_WIDTH/HEIGHT:
* The frame width/height (same as pad->buffer_vinfo.height/width;
* see gst_video_frame_map())
* 2. cpad->width/height:
* The optional pad property for scaling the frame (if zero, the video is
* left unscaled)
* 3. conversion_info.width/height:
* Equal to cpad->width/height if it's set, otherwise it's the frame
* width/height. See ->set_info()
* */
if (cpad->width > 0) if (cpad->width > 0)
width = cpad->width; width = cpad->width;
else else
@ -394,6 +421,60 @@ gst_compositor_pad_prepare_frame (GstVideoAggregatorPad * pad,
g_free (wanted_colorimetry); g_free (wanted_colorimetry);
} }
/* Clamp the x/y coordinates of this frame to the video boundaries to cover
* the case where (say, with negative xpos/ypos) the non-obscured portion of
* the frame could be outside the bounds of the video itself and hence not
* visible at all */
frame_rect.x = CLAMP (cpad->xpos, 0,
GST_VIDEO_INFO_WIDTH (&pad->buffer_vinfo));
frame_rect.y = CLAMP (cpad->ypos, 0,
GST_VIDEO_INFO_HEIGHT (&pad->buffer_vinfo));
/* Clamp the width/height to the frame boundaries as well */
frame_rect.w = MIN (GST_VIDEO_INFO_WIDTH (&cpad->conversion_info),
GST_VIDEO_INFO_WIDTH (&pad->buffer_vinfo) - cpad->xpos);
frame_rect.h = MIN (GST_VIDEO_INFO_HEIGHT (&cpad->conversion_info),
GST_VIDEO_INFO_HEIGHT (&pad->buffer_vinfo) - cpad->ypos);
GST_OBJECT_LOCK (vagg);
/* Check if this frame is obscured by a higher-zorder frame
* TODO: Also skip a frame if it's obscured by a combination of
* higher-zorder frames */
for (l = g_list_find (GST_ELEMENT (vagg)->sinkpads, pad)->next; l;
l = l->next) {
GstVideoRectangle frame2_rect;
GstVideoAggregatorPad *pad2 = l->data;
GstCompositorPad *cpad2 = GST_COMPOSITOR_PAD (pad2);
/* We don't need to clamp the coords of the second rectangle */
frame2_rect.x = cpad2->xpos;
frame2_rect.y = cpad2->ypos;
/* This is effectively what set_info and the above conversion
* code do to calculate the desired width/height */
frame2_rect.w = cpad2->width ? cpad2->width :
GST_VIDEO_INFO_WIDTH (&cpad2->conversion_info);
frame2_rect.h = cpad2->height ? cpad2->height :
GST_VIDEO_INFO_HEIGHT (&cpad2->conversion_info);
/* Check if there's a buffer to be aggregated, ensure it can't have an alpha
* channel, then check opacity and frame boundaries */
if (pad2->buffer && cpad2->alpha == 1.0 &&
!GST_VIDEO_INFO_HAS_ALPHA (&pad2->info) &&
is_rectangle_contained (frame_rect, frame2_rect)) {
frame_obscured = TRUE;
GST_DEBUG_OBJECT (pad, "Obscured by %s, skipping frame",
GST_PAD_NAME (pad2));
break;
}
}
GST_OBJECT_UNLOCK (vagg);
if (frame_obscured) {
converted_frame = NULL;
gst_video_frame_unmap (frame);
g_slice_free (GstVideoFrame, frame);
goto done;
}
if (cpad->alpha == 0.0) { if (cpad->alpha == 0.0) {
GST_DEBUG_OBJECT (vagg, "Pad has alpha 0.0, not converting frame"); GST_DEBUG_OBJECT (vagg, "Pad has alpha 0.0, not converting frame");
converted_frame = NULL; converted_frame = NULL;
@ -428,6 +509,7 @@ gst_compositor_pad_prepare_frame (GstVideoAggregatorPad * pad,
converted_frame = frame; converted_frame = frame;
} }
done:
pad->aggregated_frame = converted_frame; pad->aggregated_frame = converted_frame;
return TRUE; return TRUE;
@ -808,6 +890,8 @@ gst_compositor_aggregate_frames (GstVideoAggregator * vagg, GstBuffer * outbuf)
outframe = &out_frame; outframe = &out_frame;
/* default to blending */ /* default to blending */
composite = self->blend; composite = self->blend;
/* TODO: If the frames to be composited completely obscure the background,
* don't bother drawing the background at all. */
switch (self->background) { switch (self->background) {
case COMPOSITOR_BACKGROUND_CHECKER: case COMPOSITOR_BACKGROUND_CHECKER:
self->fill_checker (outframe); self->fill_checker (outframe);