waylandsink: stack the video subsurface into another subsurface that covers the whole render rectangle

The main reason behind this is that when the video caps change and the video
subsurface needs to resize and change position, the wl_subsurface.set_position
call needs a commit in its parent in order to take effect. Previously,
the parent was the application's surface, over which there is no control.
Now, the parent is inside the sink, so we can commit it and change size smoothly.

As a side effect, this also allows the sink to draw its black borders on
its own, without the need for the application to do that. And another side
effect is that this can now allow resizing the sink when it is in top-level
mode and have it respect the aspect ratio.
This commit is contained in:
George Kiagiadakis 2014-07-01 11:43:20 +03:00
parent ee7968dd4a
commit 5b1c5dbf99
6 changed files with 163 additions and 90 deletions

View file

@ -333,11 +333,7 @@ gst_wayland_sink_change_state (GstElement * element, GstStateChange transition)
g_clear_object (&sink->window);
} else {
/* remove buffer from surface, show nothing */
wl_surface_attach (sink->window->surface, NULL, 0, 0);
wl_surface_damage (sink->window->surface, 0, 0,
sink->window->surface_width, sink->window->surface_height);
wl_surface_commit (sink->window->surface);
wl_display_flush (sink->display->display);
gst_wl_window_render (sink->window, NULL, NULL);
}
}
break;
@ -558,6 +554,7 @@ static void
render_last_buffer (GstWaylandSink * sink)
{
GstWlBuffer *wlbuffer;
const GstVideoInfo *info = NULL;
struct wl_surface *surface;
struct wl_callback *callback;
@ -568,12 +565,11 @@ render_last_buffer (GstWaylandSink * sink)
callback = wl_surface_frame (surface);
wl_callback_add_listener (callback, &frame_callback_listener, sink);
gst_wl_buffer_attach (wlbuffer, sink->window);
wl_surface_damage (surface, 0, 0, sink->window->surface_width,
sink->window->surface_height);
wl_surface_commit (surface);
wl_display_flush (sink->display->display);
if (G_UNLIKELY (sink->video_info_changed)) {
info = &sink->video_info;
sink->video_info_changed = FALSE;
}
gst_wl_window_render (sink->window, wlbuffer, info);
}
static GstFlowReturn
@ -595,33 +591,19 @@ gst_wayland_sink_render (GstBaseSink * bsink, GstBuffer * buffer)
gst_video_overlay_prepare_window_handle (GST_VIDEO_OVERLAY (sink));
g_mutex_lock (&sink->render_lock);
if (sink->window) {
/* inform the window about our caps */
gst_wl_window_set_video_info (sink->window, &sink->video_info);
} else {
if (!sink->window) {
/* if we were not provided a window, create one ourselves */
sink->window =
gst_wl_window_new_toplevel (sink->display, &sink->video_info);
}
sink->video_info_changed = FALSE;
}
/* drop buffers until we get a frame callback */
if (g_atomic_int_get (&sink->redraw_pending) == TRUE)
goto done;
if (G_UNLIKELY (sink->video_info_changed)) {
gst_wl_window_set_video_info (sink->window, &sink->video_info);
sink->video_info_changed = FALSE;
}
/* now that we have for sure set the video info on the window, it must have
* a valid size, otherwise this means that the application has called
* set_window_handle() without calling set_render_rectangle(), which is
* absolutely necessary for us.
*/
if (G_UNLIKELY (sink->window->surface_width == 0 ||
sink->window->surface_height == 0))
/* make sure that the application has called set_render_rectangle() */
if (G_UNLIKELY (sink->window->render_rectangle.w == 0))
goto no_window_size;
wlbuffer = gst_buffer_get_wl_buffer (buffer);
@ -822,14 +804,14 @@ gst_wayland_sink_begin_geometry_change (GstWaylandVideo * video)
g_return_if_fail (sink != NULL);
g_mutex_lock (&sink->render_lock);
if (!sink->window || !sink->window->subsurface) {
if (!sink->window || !sink->window->area_subsurface) {
g_mutex_unlock (&sink->render_lock);
GST_INFO_OBJECT (sink,
"begin_geometry_change called without window, ignoring");
return;
}
wl_subsurface_set_sync (sink->window->subsurface);
wl_subsurface_set_sync (sink->window->area_subsurface);
g_mutex_unlock (&sink->render_lock);
}
@ -840,14 +822,14 @@ gst_wayland_sink_end_geometry_change (GstWaylandVideo * video)
g_return_if_fail (sink != NULL);
g_mutex_lock (&sink->render_lock);
if (!sink->window || !sink->window->subsurface) {
if (!sink->window || !sink->window->area_subsurface) {
g_mutex_unlock (&sink->render_lock);
GST_INFO_OBJECT (sink,
"end_geometry_change called without window, ignoring");
return;
}
wl_subsurface_set_desync (sink->window->subsurface);
wl_subsurface_set_desync (sink->window->area_subsurface);
g_mutex_unlock (&sink->render_lock);
}

View file

@ -120,7 +120,7 @@ static const struct wl_buffer_listener buffer_listener = {
buffer_release
};
void
GstWlBuffer *
gst_buffer_add_wl_buffer (GstBuffer * gstbuffer, struct wl_buffer *wlbuffer,
GstWlDisplay * display)
{
@ -137,6 +137,8 @@ gst_buffer_add_wl_buffer (GstBuffer * gstbuffer, struct wl_buffer *wlbuffer,
gst_mini_object_set_qdata ((GstMiniObject *) gstbuffer,
gst_wl_buffer_qdata_quark (), self, g_object_unref);
return self;
}
GstWlBuffer *
@ -172,11 +174,11 @@ gst_wl_buffer_force_release_and_unref (GstWlBuffer * self)
}
void
gst_wl_buffer_attach (GstWlBuffer * self, GstWlWindow * target)
gst_wl_buffer_attach (GstWlBuffer * self, struct wl_surface *surface)
{
g_return_if_fail (self->used_by_compositor == FALSE);
wl_surface_attach (target->surface, self->wlbuffer, 0, 0);
wl_surface_attach (surface, self->wlbuffer, 0, 0);
/* Add a reference to the buffer. This represents the fact that
* the compositor is using the buffer and it should not return

View file

@ -21,7 +21,7 @@
#ifndef __GST_WL_BUFFER_H__
#define __GST_WL_BUFFER_H__
#include "wlwindow.h"
#include "wldisplay.h"
G_BEGIN_DECLS
@ -54,13 +54,13 @@ struct _GstWlBufferClass
GType gst_wl_buffer_get_type (void);
void gst_buffer_add_wl_buffer (GstBuffer * gstbuffer,
GstWlBuffer * gst_buffer_add_wl_buffer (GstBuffer * gstbuffer,
struct wl_buffer * wlbuffer, GstWlDisplay * display);
GstWlBuffer * gst_buffer_get_wl_buffer (GstBuffer * gstbuffer);
void gst_wl_buffer_force_release_and_unref (GstWlBuffer * self);
void gst_wl_buffer_attach (GstWlBuffer * self, GstWlWindow * target);
void gst_wl_buffer_attach (GstWlBuffer * self, struct wl_surface *surface);
G_END_DECLS

View file

@ -24,7 +24,7 @@
#ifndef __GST_WL_VIDEO_FORMAT_H__
#define __GST_WL_VIDEO_FORMAT_H__
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <gst/video/video.h>
G_BEGIN_DECLS

View file

@ -25,6 +25,8 @@
#endif
#include "wlwindow.h"
#include "wlshmallocator.h"
#include "wlbuffer.h"
GST_DEBUG_CATEGORY_EXTERN (gstwayland_debug);
#define GST_CAT_DEFAULT gstwayland_debug
@ -78,12 +80,15 @@ gst_wl_window_finalize (GObject * gobject)
wl_shell_surface_destroy (self->shell_surface);
}
if (self->subsurface) {
wl_subsurface_destroy (self->subsurface);
}
wl_viewport_destroy (self->video_viewport);
wl_subsurface_destroy (self->video_subsurface);
wl_surface_destroy (self->video_surface);
wl_viewport_destroy (self->viewport);
wl_surface_destroy (self->surface);
if (self->area_subsurface) {
wl_subsurface_destroy (self->area_subsurface);
}
wl_viewport_destroy (self->area_viewport);
wl_surface_destroy (self->area_surface);
g_clear_object (&self->display);
@ -91,44 +96,84 @@ gst_wl_window_finalize (GObject * gobject)
}
static GstWlWindow *
gst_wl_window_new_internal (GstWlDisplay * display, struct wl_surface *surface)
gst_wl_window_new_internal (GstWlDisplay * display)
{
GstWlWindow *window;
GstVideoInfo info;
GstBuffer *buf;
GstMapInfo mapinfo;
struct wl_buffer *wlbuf;
GstWlBuffer *gwlbuf;
struct wl_region *region;
g_return_val_if_fail (surface != NULL, NULL);
window = g_object_new (GST_TYPE_WL_WINDOW, NULL);
window->display = g_object_ref (display);
window->surface = surface;
/* make sure the surface runs on our local queue */
wl_proxy_set_queue ((struct wl_proxy *) surface, display->queue);
window->area_surface = wl_compositor_create_surface (display->compositor);
window->video_surface = wl_compositor_create_surface (display->compositor);
window->viewport = wl_scaler_get_viewport (display->scaler, window->surface);
wl_proxy_set_queue ((struct wl_proxy *) window->area_surface, display->queue);
wl_proxy_set_queue ((struct wl_proxy *) window->video_surface,
display->queue);
/* embed video_surface in area_surface */
window->video_subsurface =
wl_subcompositor_get_subsurface (display->subcompositor,
window->video_surface, window->area_surface);
wl_subsurface_set_desync (window->video_subsurface);
window->area_viewport = wl_scaler_get_viewport (display->scaler,
window->area_surface);
window->video_viewport = wl_scaler_get_viewport (display->scaler,
window->video_surface);
/* draw the area_subsurface */
gst_video_info_set_format (&info,
/* we want WL_SHM_FORMAT_XRGB8888 */
#if G_BYTE_ORDER == G_BIG_ENDIAN
GST_VIDEO_FORMAT_xRGB,
#else
GST_VIDEO_FORMAT_BGRx,
#endif
1, 1);
buf = gst_buffer_new_allocate (gst_wl_shm_allocator_get (), info.size, NULL);
gst_buffer_map (buf, &mapinfo, GST_MAP_WRITE);
*((guint32 *) mapinfo.data) = 0; /* paint it black */
gst_buffer_unmap (buf, &mapinfo);
wlbuf =
gst_wl_shm_memory_construct_wl_buffer (gst_buffer_peek_memory (buf, 0),
display, &info);
gwlbuf = gst_buffer_add_wl_buffer (buf, wlbuf, display);
gst_wl_buffer_attach (gwlbuf, window->area_surface);
/* at this point, the GstWlBuffer keeps the buffer
* alive and will free it on wl_buffer::release */
gst_buffer_unref (buf);
/* do not accept input */
region = wl_compositor_create_region (display->compositor);
wl_surface_set_input_region (surface, region);
wl_surface_set_input_region (window->area_surface, region);
wl_region_destroy (region);
region = wl_compositor_create_region (display->compositor);
wl_surface_set_input_region (window->video_surface, region);
wl_region_destroy (region);
return window;
}
GstWlWindow *
gst_wl_window_new_toplevel (GstWlDisplay * display, GstVideoInfo * video_info)
gst_wl_window_new_toplevel (GstWlDisplay * display, const GstVideoInfo * info)
{
GstWlWindow *window;
gint width;
window = gst_wl_window_new_internal (display,
wl_compositor_create_surface (display->compositor));
gst_wl_window_set_video_info (window, video_info);
gst_wl_window_set_render_rectangle (window, 0, 0, window->video_width,
window->video_height);
window = gst_wl_window_new_internal (display);
/* go toplevel */
window->shell_surface = wl_shell_get_shell_surface (display->shell,
window->surface);
window->area_surface);
if (window->shell_surface) {
wl_shell_surface_add_listener (window->shell_surface,
@ -141,6 +186,11 @@ gst_wl_window_new_toplevel (GstWlDisplay * display, GstVideoInfo * video_info)
return NULL;
}
/* set the initial size to be the same as the reported video size */
width =
gst_util_uint64_scale_int_round (info->width, info->par_n, info->par_d);
gst_wl_window_set_render_rectangle (window, 0, 0, width, info->height);
return window;
}
@ -149,13 +199,13 @@ gst_wl_window_new_in_surface (GstWlDisplay * display,
struct wl_surface * parent)
{
GstWlWindow *window;
window = gst_wl_window_new_internal (display);
window = gst_wl_window_new_internal (display,
wl_compositor_create_surface (display->compositor));
window->subsurface = wl_subcompositor_get_subsurface (display->subcompositor,
window->surface, parent);
wl_subsurface_set_desync (window->subsurface);
/* embed in parent */
window->area_subsurface =
wl_subcompositor_get_subsurface (display->subcompositor,
window->area_surface, parent);
wl_subsurface_set_desync (window->area_subsurface);
return window;
}
@ -173,7 +223,7 @@ gst_wl_window_get_wl_surface (GstWlWindow * window)
{
g_return_val_if_fail (window != NULL, NULL);
return window->surface;
return window->video_surface;
}
gboolean
@ -185,22 +235,21 @@ gst_wl_window_is_toplevel (GstWlWindow * window)
}
static void
gst_wl_window_resize_internal (GstWlWindow * window, gboolean commit)
gst_wl_window_resize_video_surface (GstWlWindow * window, gboolean commit)
{
GstVideoRectangle src, res;
/* center the video_subsurface inside area_subsurface */
src.w = window->video_width;
src.h = window->video_height;
gst_video_sink_center_rect (src, window->render_rectangle, &res, TRUE);
if (window->subsurface)
wl_subsurface_set_position (window->subsurface,
window->render_rectangle.x + res.x, window->render_rectangle.y + res.y);
wl_viewport_set_destination (window->viewport, res.w, res.h);
wl_subsurface_set_position (window->video_subsurface, res.x, res.y);
wl_viewport_set_destination (window->video_viewport, res.w, res.h);
if (commit) {
wl_surface_damage (window->surface, 0, 0, res.w, res.h);
wl_surface_commit (window->surface);
wl_surface_damage (window->video_surface, 0, 0, res.w, res.h);
wl_surface_commit (window->video_surface);
}
/* this is saved for use in wl_surface_damage */
@ -209,16 +258,37 @@ gst_wl_window_resize_internal (GstWlWindow * window, gboolean commit)
}
void
gst_wl_window_set_video_info (GstWlWindow * window, GstVideoInfo * info)
gst_wl_window_render (GstWlWindow * window, GstWlBuffer * buffer,
const GstVideoInfo * info)
{
g_return_if_fail (window != NULL);
if (G_UNLIKELY (info)) {
window->video_width =
gst_util_uint64_scale_int_round (info->width, info->par_n, info->par_d);
window->video_height = info->height;
window->video_width =
gst_util_uint64_scale_int_round (info->width, info->par_n, info->par_d);
window->video_height = info->height;
wl_subsurface_set_sync (window->video_subsurface);
gst_wl_window_resize_video_surface (window, FALSE);
}
if (window->render_rectangle.w != 0)
gst_wl_window_resize_internal (window, FALSE);
if (G_LIKELY (buffer))
gst_wl_buffer_attach (buffer, window->video_surface);
else
wl_surface_attach (window->video_surface, NULL, 0, 0);
wl_surface_damage (window->video_surface, 0, 0, window->surface_width,
window->surface_height);
wl_surface_commit (window->video_surface);
if (G_UNLIKELY (info)) {
/* commit also the parent (area_surface) in order to change
* the position of the video_subsurface */
wl_surface_damage (window->area_surface, 0, 0, window->render_rectangle.w,
window->render_rectangle.h);
wl_surface_commit (window->area_surface);
wl_subsurface_set_desync (window->video_subsurface);
}
wl_display_flush (window->display->display);
}
void
@ -232,6 +302,21 @@ gst_wl_window_set_render_rectangle (GstWlWindow * window, gint x, gint y,
window->render_rectangle.w = w;
window->render_rectangle.h = h;
/* position the area inside the parent - needs a parent commit to apply */
if (window->area_subsurface)
wl_subsurface_set_position (window->area_subsurface, x, y);
/* change the size of the area */
wl_viewport_set_destination (window->area_viewport, w, h);
if (window->video_width != 0) {
wl_subsurface_set_sync (window->video_subsurface);
gst_wl_window_resize_video_surface (window, TRUE);
}
wl_surface_damage (window->area_surface, 0, 0, w, h);
wl_surface_commit (window->area_surface);
if (window->video_width != 0)
gst_wl_window_resize_internal (window, TRUE);
wl_subsurface_set_desync (window->video_subsurface);
}

View file

@ -22,6 +22,7 @@
#define __GST_WL_WINDOW_H__
#include "wldisplay.h"
#include "wlbuffer.h"
#include <gst/video/video.h>
G_BEGIN_DECLS
@ -41,16 +42,19 @@ struct _GstWlWindow
GObject parent_instance;
GstWlDisplay *display;
struct wl_surface *surface;
struct wl_subsurface *subsurface;
struct wl_viewport *viewport;
struct wl_surface *area_surface;
struct wl_subsurface *area_subsurface;
struct wl_viewport *area_viewport;
struct wl_surface *video_surface;
struct wl_subsurface *video_subsurface;
struct wl_viewport *video_viewport;
struct wl_shell_surface *shell_surface;
/* the size of the destination area where we are overlaying our subsurface */
/* the size and position of the area_(sub)surface */
GstVideoRectangle render_rectangle;
/* the size of the video in the buffers */
gint video_width, video_height;
/* the size of the (sub)surface */
/* the size of the video_(sub)surface */
gint surface_width, surface_height;
};
@ -62,7 +66,7 @@ struct _GstWlWindowClass
GType gst_wl_window_get_type (void);
GstWlWindow *gst_wl_window_new_toplevel (GstWlDisplay * display,
GstVideoInfo * video_info);
const GstVideoInfo * info);
GstWlWindow *gst_wl_window_new_in_surface (GstWlDisplay * display,
struct wl_surface * parent);
@ -70,8 +74,8 @@ GstWlDisplay *gst_wl_window_get_display (GstWlWindow * window);
struct wl_surface *gst_wl_window_get_wl_surface (GstWlWindow * window);
gboolean gst_wl_window_is_toplevel (GstWlWindow *window);
/* functions to manipulate the size on non-toplevel windows */
void gst_wl_window_set_video_info (GstWlWindow * window, GstVideoInfo * info);
void gst_wl_window_render (GstWlWindow * window, GstWlBuffer * buffer,
const GstVideoInfo * info);
void gst_wl_window_set_render_rectangle (GstWlWindow * window, gint x, gint y,
gint w, gint h);