vaapisink: add video rotation support.
[platform/upstream/gstreamer-vaapi.git] / gst / vaapi / gstvaapisink.c
1 /*
2  *  gstvaapisink.c - VA-API video sink
3  *
4  *  Copyright (C) 2010-2011 Splitted-Desktop Systems
5  *  Copyright (C) 2011-2012 Intel Corporation
6  *
7  *  This library is free software; you can redistribute it and/or
8  *  modify it under the terms of the GNU Lesser General Public License
9  *  as published by the Free Software Foundation; either version 2.1
10  *  of the License, or (at your option) any later version.
11  *
12  *  This library is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public
18  *  License along with this library; if not, write to the Free
19  *  Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  *  Boston, MA 02110-1301 USA
21  */
22
23 /**
24  * SECTION:gstvaapisink
25  * @short_description: A VA-API based videosink
26  *
27  * vaapisink renders video frames to a drawable (X #Window) on a local
28  * display using the Video Acceleration (VA) API. The element will
29  * create its own internal window and render into it.
30  */
31
32 #include "config.h"
33 #include <gst/gst.h>
34 #include <gst/video/video.h>
35 #include <gst/video/videocontext.h>
36 #include <gst/vaapi/gstvaapivalue.h>
37 #include <gst/vaapi/gstvaapivideobuffer.h>
38 #if USE_DRM
39 # include <gst/vaapi/gstvaapidisplay_drm.h>
40 #endif
41 #if USE_X11
42 # include <gst/vaapi/gstvaapidisplay_x11.h>
43 # include <gst/vaapi/gstvaapiwindow_x11.h>
44 #endif
45 #if USE_GLX
46 # include <gst/vaapi/gstvaapidisplay_glx.h>
47 # include <gst/vaapi/gstvaapiwindow_glx.h>
48 #endif
49 #if USE_WAYLAND
50 # include <gst/vaapi/gstvaapidisplay_wayland.h>
51 # include <gst/vaapi/gstvaapiwindow_wayland.h>
52 #endif
53
54 /* Supported interfaces */
55 #include <gst/interfaces/xoverlay.h>
56
57 #include "gstvaapisink.h"
58 #include "gstvaapipluginutil.h"
59
60 #define GST_PLUGIN_NAME "vaapisink"
61 #define GST_PLUGIN_DESC "A VA-API based videosink"
62
63 GST_DEBUG_CATEGORY_STATIC(gst_debug_vaapisink);
64 #define GST_CAT_DEFAULT gst_debug_vaapisink
65
66 /* ElementFactory information */
67 static const GstElementDetails gst_vaapisink_details =
68     GST_ELEMENT_DETAILS(
69         "VA-API sink",
70         "Sink/Video",
71         GST_PLUGIN_DESC,
72         "Gwenole Beauchesne <gwenole.beauchesne@intel.com>");
73
74 /* Default template */
75 static GstStaticPadTemplate gst_vaapisink_sink_factory =
76     GST_STATIC_PAD_TEMPLATE(
77         "sink",
78         GST_PAD_SINK,
79         GST_PAD_ALWAYS,
80         GST_STATIC_CAPS(GST_VAAPI_SURFACE_CAPS));
81
82 static void
83 gst_vaapisink_implements_iface_init(GstImplementsInterfaceClass *iface);
84
85 static void
86 gst_vaapisink_video_context_iface_init(GstVideoContextInterface *iface);
87
88 static void
89 gst_vaapisink_xoverlay_iface_init(GstXOverlayClass *iface);
90
91 G_DEFINE_TYPE_WITH_CODE(
92     GstVaapiSink,
93     gst_vaapisink,
94     GST_TYPE_VIDEO_SINK,
95     G_IMPLEMENT_INTERFACE(GST_TYPE_IMPLEMENTS_INTERFACE,
96                           gst_vaapisink_implements_iface_init);
97     G_IMPLEMENT_INTERFACE(GST_TYPE_VIDEO_CONTEXT,
98                           gst_vaapisink_video_context_iface_init);
99     G_IMPLEMENT_INTERFACE(GST_TYPE_X_OVERLAY,
100                           gst_vaapisink_xoverlay_iface_init));
101
102 enum {
103     PROP_0,
104
105     PROP_DISPLAY_TYPE,
106     PROP_FULLSCREEN,
107     PROP_SYNCHRONOUS,
108     PROP_USE_REFLECTION,
109     PROP_ROTATION,
110 };
111
112 #define DEFAULT_DISPLAY_TYPE            GST_VAAPI_DISPLAY_TYPE_ANY
113 #define DEFAULT_ROTATION                GST_VAAPI_ROTATION_0
114
115 /* GstImplementsInterface interface */
116
117 static gboolean
118 gst_vaapisink_implements_interface_supported(
119     GstImplementsInterface *iface,
120     GType                   type
121 )
122 {
123     return (type == GST_TYPE_VIDEO_CONTEXT ||
124             type == GST_TYPE_X_OVERLAY);
125 }
126
127 static void
128 gst_vaapisink_implements_iface_init(GstImplementsInterfaceClass *iface)
129 {
130     iface->supported = gst_vaapisink_implements_interface_supported;
131 }
132
133 /* GstVideoContext interface */
134
135 static void
136 gst_vaapisink_set_video_context(GstVideoContext *context, const gchar *type,
137     const GValue *value)
138 {
139   GstVaapiSink *sink = GST_VAAPISINK (context);
140   gst_vaapi_set_display (type, value, &sink->display);
141 }
142
143 static void
144 gst_vaapisink_video_context_iface_init(GstVideoContextInterface *iface)
145 {
146     iface->set_context = gst_vaapisink_set_video_context;
147 }
148
149 /* GstXOverlay interface */
150
151 #if USE_X11
152 static gboolean
153 gst_vaapisink_ensure_window_xid(GstVaapiSink *sink, guintptr window_id);
154 #endif
155
156 static GstFlowReturn
157 gst_vaapisink_show_frame(GstBaseSink *base_sink, GstBuffer *buffer);
158
159 static void
160 gst_vaapisink_xoverlay_set_window_handle(GstXOverlay *overlay, guintptr window)
161 {
162     GstVaapiSink * const sink = GST_VAAPISINK(overlay);
163
164     /* Disable GLX rendering when vaapisink is using a foreign X
165        window. It's pretty much useless */
166     if (sink->display_type == GST_VAAPI_DISPLAY_TYPE_GLX)
167         sink->display_type = GST_VAAPI_DISPLAY_TYPE_X11;
168
169     sink->foreign_window = TRUE;
170
171     switch (sink->display_type) {
172 #if USE_X11
173     case GST_VAAPI_DISPLAY_TYPE_X11:
174         gst_vaapisink_ensure_window_xid(sink, window);
175         break;
176 #endif
177     default:
178         break;
179     }
180 }
181
182 static void
183 gst_vaapisink_xoverlay_set_render_rectangle(
184     GstXOverlay *overlay,
185     gint         x,
186     gint         y,
187     gint         width,
188     gint         height
189 )
190 {
191     GstVaapiSink * const sink = GST_VAAPISINK(overlay);
192     GstVaapiRectangle * const display_rect = &sink->display_rect;
193
194     display_rect->x      = x;
195     display_rect->y      = y;
196     display_rect->width  = width;
197     display_rect->height = height;
198     
199     GST_DEBUG("render rect (%d,%d):%ux%u",
200               display_rect->x, display_rect->y,
201               display_rect->width, display_rect->height);
202 }
203
204 static void
205 gst_vaapisink_xoverlay_expose(GstXOverlay *overlay)
206 {
207     GstBaseSink * const base_sink = GST_BASE_SINK(overlay);
208     GstBuffer *buffer;
209
210     buffer = gst_base_sink_get_last_buffer(base_sink);
211     if (buffer) {
212         gst_vaapisink_show_frame(base_sink, buffer);
213         gst_buffer_unref(buffer);
214     }
215 }
216
217 static void
218 gst_vaapisink_xoverlay_iface_init(GstXOverlayClass *iface)
219 {
220     iface->set_window_handle    = gst_vaapisink_xoverlay_set_window_handle;
221     iface->set_render_rectangle = gst_vaapisink_xoverlay_set_render_rectangle;
222     iface->expose               = gst_vaapisink_xoverlay_expose;
223 }
224
225 static void
226 gst_vaapisink_destroy(GstVaapiSink *sink)
227 {
228     gst_buffer_replace(&sink->video_buffer, NULL);
229     g_clear_object(&sink->texture);
230     g_clear_object(&sink->display);
231
232     gst_caps_replace(&sink->caps, NULL);
233 }
234
235 #if USE_X11
236 /* Checks whether a ConfigureNotify event is in the queue */
237 typedef struct _ConfigureNotifyEventPendingArgs ConfigureNotifyEventPendingArgs;
238 struct _ConfigureNotifyEventPendingArgs {
239     Window      window;
240     guint       width;
241     guint       height;
242     gboolean    match;
243 };
244
245 static Bool
246 configure_notify_event_pending_cb(Display *dpy, XEvent *xev, XPointer arg)
247 {
248     ConfigureNotifyEventPendingArgs * const args =
249         (ConfigureNotifyEventPendingArgs *)arg;
250
251     if (xev->type == ConfigureNotify &&
252         xev->xconfigure.window == args->window &&
253         xev->xconfigure.width  == args->width  &&
254         xev->xconfigure.height == args->height)
255         args->match = TRUE;
256
257     /* XXX: this is a hack to traverse the whole queue because we
258        can't use XPeekIfEvent() since it could block */
259     return False;
260 }
261
262 static gboolean
263 configure_notify_event_pending(
264     GstVaapiSink *sink,
265     Window        window,
266     guint         width,
267     guint         height
268 )
269 {
270     ConfigureNotifyEventPendingArgs args;
271     XEvent xev;
272
273     args.window = window;
274     args.width  = width;
275     args.height = height;
276     args.match  = FALSE;
277
278     /* XXX: don't use XPeekIfEvent() because it might block */
279     XCheckIfEvent(
280         gst_vaapi_display_x11_get_display(GST_VAAPI_DISPLAY_X11(sink->display)),
281         &xev,
282         configure_notify_event_pending_cb, (XPointer)&args
283     );
284     return args.match;
285 }
286 #endif
287
288 static const gchar *
289 get_display_type_name(GstVaapiDisplayType display_type)
290 {
291     gpointer const klass = g_type_class_peek(GST_VAAPI_TYPE_DISPLAY_TYPE);
292     GEnumValue * const e = g_enum_get_value(klass, display_type);
293
294     if (e)
295         return e->value_name;
296     return "<unknown-type>";
297 }
298
299 static inline gboolean
300 gst_vaapisink_ensure_display(GstVaapiSink *sink)
301 {
302     GstVaapiDisplayType display_type;
303     GstVaapiRenderMode render_mode;
304
305     if (!gst_vaapi_ensure_display(sink, sink->display_type, &sink->display))
306         return FALSE;
307
308     display_type = gst_vaapi_display_get_display_type(sink->display);
309     if (display_type != sink->display_type) {
310         GST_INFO("created %s %p", get_display_type_name(display_type),
311             sink->display);
312         sink->display_type = display_type;
313
314         sink->use_overlay =
315             gst_vaapi_display_get_render_mode(sink->display, &render_mode) &&
316             render_mode == GST_VAAPI_RENDER_MODE_OVERLAY;
317         GST_DEBUG("use %s rendering mode", sink->use_overlay ? "overlay" : "texture");
318
319         sink->use_rotation = gst_vaapi_display_has_property(
320             sink->display, GST_VAAPI_DISPLAY_PROP_ROTATION);
321     }
322     return TRUE;
323 }
324
325 static gboolean
326 gst_vaapisink_ensure_render_rect(GstVaapiSink *sink, guint width, guint height)
327 {
328     GstVaapiRectangle * const display_rect = &sink->display_rect;
329     guint num, den, display_par_n, display_par_d;
330     gboolean success;
331
332     /* Return success if caps are not set yet */
333     if (!sink->caps)
334         return TRUE;
335
336     GST_DEBUG("ensure render rect within %ux%u bounds", width, height);
337
338     gst_vaapi_display_get_pixel_aspect_ratio(
339         sink->display,
340         &display_par_n, &display_par_d
341     );
342     GST_DEBUG("display pixel-aspect-ratio %d/%d",
343               display_par_n, display_par_d);
344
345     success = gst_video_calculate_display_ratio(
346         &num, &den,
347         sink->video_width, sink->video_height,
348         sink->video_par_n, sink->video_par_d,
349         display_par_n, display_par_d
350     );
351     if (!success)
352         return FALSE;
353     GST_DEBUG("video size %dx%d, calculated ratio %d/%d",
354               sink->video_width, sink->video_height, num, den);
355
356     display_rect->width = gst_util_uint64_scale_int(height, num, den);
357     if (display_rect->width <= width) {
358         GST_DEBUG("keeping window height");
359         display_rect->height = height;
360     }
361     else {
362         GST_DEBUG("keeping window width");
363         display_rect->width  = width;
364         display_rect->height =
365             gst_util_uint64_scale_int(width, den, num);
366     }
367     GST_DEBUG("scaling video to %ux%u", display_rect->width, display_rect->height);
368
369     g_assert(display_rect->width  <= width);
370     g_assert(display_rect->height <= height);
371
372     display_rect->x = (width  - display_rect->width)  / 2;
373     display_rect->y = (height - display_rect->height) / 2;
374
375     GST_DEBUG("render rect (%d,%d):%ux%u",
376               display_rect->x, display_rect->y,
377               display_rect->width, display_rect->height);
378     return TRUE;
379 }
380
381 static inline gboolean
382 gst_vaapisink_ensure_window(GstVaapiSink *sink, guint width, guint height)
383 {
384     GstVaapiDisplay * const display = sink->display;
385
386     if (!sink->window) {
387         switch (sink->display_type) {
388 #if USE_GLX
389         case GST_VAAPI_DISPLAY_TYPE_GLX:
390             sink->window = gst_vaapi_window_glx_new(display, width, height);
391             goto notify_xoverlay_interface;
392 #endif
393 #if USE_X11
394         case GST_VAAPI_DISPLAY_TYPE_X11:
395             sink->window = gst_vaapi_window_x11_new(display, width, height);
396         notify_xoverlay_interface:
397             if (!sink->window)
398                 break;
399             gst_x_overlay_got_window_handle(
400                 GST_X_OVERLAY(sink),
401                 gst_vaapi_window_x11_get_xid(GST_VAAPI_WINDOW_X11(sink->window))
402             );
403             break;
404 #endif
405 #if USE_WAYLAND
406         case GST_VAAPI_DISPLAY_TYPE_WAYLAND:
407             sink->window = gst_vaapi_window_wayland_new(display, width, height);
408             break;
409 #endif
410         default:
411             GST_ERROR("unsupported display type %d", sink->display_type);
412             return FALSE;
413         }
414     }
415     return sink->window != NULL;
416 }
417
418 #if USE_X11
419 static gboolean
420 gst_vaapisink_ensure_window_xid(GstVaapiSink *sink, guintptr window_id)
421 {
422     Window rootwin;
423     unsigned int width, height, border_width, depth;
424     int x, y;
425     XID xid = window_id;
426
427     if (!gst_vaapisink_ensure_display(sink))
428         return FALSE;
429
430     gst_vaapi_display_lock(sink->display);
431     XGetGeometry(
432         gst_vaapi_display_x11_get_display(GST_VAAPI_DISPLAY_X11(sink->display)),
433         xid,
434         &rootwin,
435         &x, &y, &width, &height, &border_width, &depth
436     );
437     gst_vaapi_display_unlock(sink->display);
438
439     if ((width != sink->window_width || height != sink->window_height) &&
440         !configure_notify_event_pending(sink, xid, width, height)) {
441         if (!gst_vaapisink_ensure_render_rect(sink, width, height))
442             return FALSE;
443         sink->window_width  = width;
444         sink->window_height = height;
445     }
446
447     if (sink->window &&
448         gst_vaapi_window_x11_get_xid(GST_VAAPI_WINDOW_X11(sink->window)) == xid)
449         return TRUE;
450
451     g_clear_object(&sink->window);
452
453     switch (sink->display_type) {
454 #if USE_GLX
455     case GST_VAAPI_DISPLAY_TYPE_GLX:
456         sink->window = gst_vaapi_window_glx_new_with_xid(sink->display, xid);
457         break;
458 #endif
459     case GST_VAAPI_DISPLAY_TYPE_X11:
460         sink->window = gst_vaapi_window_x11_new_with_xid(sink->display, xid);
461         break;
462     default:
463         GST_ERROR("unsupported display type %d", sink->display_type);
464         return FALSE;
465     }
466     return sink->window != NULL;
467 }
468 #endif
469
470 static gboolean
471 gst_vaapisink_ensure_rotation(GstVaapiSink *sink, gboolean recalc_display_rect)
472 {
473     gboolean success = FALSE;
474
475     g_return_val_if_fail(sink->display, FALSE);
476
477     if (sink->rotation == sink->rotation_req)
478         return TRUE;
479
480     if (!sink->use_rotation) {
481         GST_WARNING("VA display does not support rotation");
482         goto end;
483     }
484
485     gst_vaapi_display_lock(sink->display);
486     success = gst_vaapi_display_set_rotation(sink->display, sink->rotation_req);
487     gst_vaapi_display_unlock(sink->display);
488     if (!success) {
489         GST_ERROR("failed to change VA display rotation mode");
490         goto end;
491     }
492
493     if (((sink->rotation + sink->rotation_req) % 180) == 90) {
494         /* Orientation changed */
495         G_PRIMITIVE_SWAP(guint, sink->video_width, sink->video_height);
496         G_PRIMITIVE_SWAP(gint, sink->video_par_n, sink->video_par_d);
497     }
498
499     if (recalc_display_rect && !sink->foreign_window)
500         gst_vaapisink_ensure_render_rect(sink, sink->window_width,
501             sink->window_height);
502     success = TRUE;
503
504 end:
505     sink->rotation = sink->rotation_req;
506     return success;
507 }
508
509 static gboolean
510 gst_vaapisink_start(GstBaseSink *base_sink)
511 {
512     GstVaapiSink * const sink = GST_VAAPISINK(base_sink);
513
514     return gst_vaapisink_ensure_display(sink);
515 }
516
517 static gboolean
518 gst_vaapisink_stop(GstBaseSink *base_sink)
519 {
520     GstVaapiSink * const sink = GST_VAAPISINK(base_sink);
521
522     gst_buffer_replace(&sink->video_buffer, NULL);
523     g_clear_object(&sink->window);
524     g_clear_object(&sink->display);
525
526     return TRUE;
527 }
528
529 static gboolean
530 gst_vaapisink_set_caps(GstBaseSink *base_sink, GstCaps *caps)
531 {
532     GstVaapiSink * const sink = GST_VAAPISINK(base_sink);
533     GstStructure * const structure = gst_caps_get_structure(caps, 0);
534     guint win_width, win_height, display_width, display_height;
535     gint video_width, video_height, video_par_n = 1, video_par_d = 1;
536
537 #if USE_DRM
538     if (sink->display_type == GST_VAAPI_DISPLAY_TYPE_DRM)
539         return TRUE;
540 #endif
541
542     if (!structure)
543         return FALSE;
544     if (!gst_structure_get_int(structure, "width",  &video_width))
545         return FALSE;
546     if (!gst_structure_get_int(structure, "height", &video_height))
547         return FALSE;
548     sink->video_width  = video_width;
549     sink->video_height = video_height;
550
551     gst_video_parse_caps_pixel_aspect_ratio(caps, &video_par_n, &video_par_d);
552     sink->video_par_n  = video_par_n;
553     sink->video_par_d  = video_par_d;
554     GST_DEBUG("video pixel-aspect-ratio %d/%d", video_par_n, video_par_d);
555
556     gst_caps_replace(&sink->caps, caps);
557
558     if (!gst_vaapisink_ensure_display(sink))
559         return FALSE;
560
561     gst_vaapisink_ensure_rotation(sink, FALSE);
562
563     gst_vaapi_display_get_size(sink->display, &display_width, &display_height);
564     if (sink->foreign_window) {
565         win_width  = sink->window_width;
566         win_height = sink->window_height;
567     }
568     else if (sink->fullscreen ||
569              video_width > display_width || video_height > display_height) {
570         win_width  = display_width;
571         win_height = display_height;
572     }
573     else {
574         win_width  = video_width;
575         win_height = video_height;
576     }
577
578     if (sink->window) {
579         if (!sink->foreign_window || sink->fullscreen)
580             gst_vaapi_window_set_size(sink->window, win_width, win_height);
581     }
582     else {
583         gst_vaapi_display_lock(sink->display);
584         gst_x_overlay_prepare_xwindow_id(GST_X_OVERLAY(sink));
585         gst_vaapi_display_unlock(sink->display);
586         if (sink->window)
587             return TRUE;
588         if (!gst_vaapisink_ensure_window(sink, win_width, win_height))
589             return FALSE;
590         gst_vaapi_window_set_fullscreen(sink->window, sink->fullscreen);
591         gst_vaapi_window_show(sink->window);
592         gst_vaapi_window_get_size(sink->window, &win_width, &win_height);
593     }
594     sink->window_width  = win_width;
595     sink->window_height = win_height;
596     GST_DEBUG("window size %ux%u", win_width, win_height);
597
598     return gst_vaapisink_ensure_render_rect(sink, win_width, win_height);
599 }
600
601 #if USE_GLX
602 static void
603 render_background(GstVaapiSink *sink)
604 {
605     /* Original code from Mirco Muller (MacSlow):
606        <http://cgit.freedesktop.org/~macslow/gl-gst-player/> */
607     GLfloat fStartX = 0.0f;
608     GLfloat fStartY = 0.0f;
609     GLfloat fWidth  = (GLfloat)sink->window_width;
610     GLfloat fHeight = (GLfloat)sink->window_height;
611
612     glClear(GL_COLOR_BUFFER_BIT);
613     glBegin(GL_QUADS);
614     {
615         /* top third, darker grey to white */
616         glColor3f(0.85f, 0.85f, 0.85f);
617         glVertex3f(fStartX, fStartY, 0.0f);
618         glColor3f(0.85f, 0.85f, 0.85f);
619         glVertex3f(fStartX + fWidth, fStartY, 0.0f);
620         glColor3f(1.0f, 1.0f, 1.0f);
621         glVertex3f(fStartX + fWidth, fStartY + fHeight / 3.0f, 0.0f);
622         glColor3f(1.0f, 1.0f, 1.0f);
623         glVertex3f(fStartX, fStartY + fHeight / 3.0f, 0.0f);
624
625         /* middle third, just plain white */
626         glColor3f(1.0f, 1.0f, 1.0f);
627         glVertex3f(fStartX, fStartY + fHeight / 3.0f, 0.0f);
628         glVertex3f(fStartX + fWidth, fStartY + fHeight / 3.0f, 0.0f);
629         glVertex3f(fStartX + fWidth, fStartY + 2.0f * fHeight / 3.0f, 0.0f);
630         glVertex3f(fStartX, fStartY + 2.0f * fHeight / 3.0f, 0.0f);
631
632         /* bottom third, white to lighter grey */
633         glColor3f(1.0f, 1.0f, 1.0f);
634         glVertex3f(fStartX, fStartY + 2.0f * fHeight / 3.0f, 0.0f);
635         glColor3f(1.0f, 1.0f, 1.0f);
636         glVertex3f(fStartX + fWidth, fStartY + 2.0f * fHeight / 3.0f, 0.0f);
637         glColor3f(0.62f, 0.66f, 0.69f);
638         glVertex3f(fStartX + fWidth, fStartY + fHeight, 0.0f);
639         glColor3f(0.62f, 0.66f, 0.69f);
640         glVertex3f(fStartX, fStartY + fHeight, 0.0f);
641     }
642     glEnd();
643 }
644
645 static void
646 render_frame(GstVaapiSink *sink)
647 {
648     const guint x1 = sink->display_rect.x;
649     const guint x2 = sink->display_rect.x + sink->display_rect.width;
650     const guint y1 = sink->display_rect.y;
651     const guint y2 = sink->display_rect.y + sink->display_rect.height;
652
653     glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
654     glBegin(GL_QUADS);
655     {
656         glTexCoord2f(0.0f, 0.0f); glVertex2i(x1, y1);
657         glTexCoord2f(0.0f, 1.0f); glVertex2i(x1, y2);
658         glTexCoord2f(1.0f, 1.0f); glVertex2i(x2, y2);
659         glTexCoord2f(1.0f, 0.0f); glVertex2i(x2, y1);
660     }
661     glEnd();
662 }
663
664 static void
665 render_reflection(GstVaapiSink *sink)
666 {
667     const guint x1 = sink->display_rect.x;
668     const guint x2 = sink->display_rect.x + sink->display_rect.width;
669     const guint y1 = sink->display_rect.y;
670     const guint rh = sink->display_rect.height / 5;
671     GLfloat     ry = 1.0f - (GLfloat)rh / (GLfloat)sink->display_rect.height;
672
673     glBegin(GL_QUADS);
674     {
675         glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
676         glTexCoord2f(0.0f, 1.0f); glVertex2i(x1, y1);
677         glTexCoord2f(1.0f, 1.0f); glVertex2i(x2, y1);
678
679         glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
680         glTexCoord2f(1.0f, ry); glVertex2i(x2, y1 + rh);
681         glTexCoord2f(0.0f, ry); glVertex2i(x1, y1 + rh);
682     }
683     glEnd();
684 }
685
686 static gboolean
687 gst_vaapisink_show_frame_glx(
688     GstVaapiSink    *sink,
689     GstVaapiSurface *surface,
690     guint            flags
691 )
692 {
693     GstVaapiWindowGLX * const window = GST_VAAPI_WINDOW_GLX(sink->window);
694     GLenum target;
695     GLuint texture;
696
697     gst_vaapi_window_glx_make_current(window);
698     if (!sink->texture) {
699         sink->texture = gst_vaapi_texture_new(
700             sink->display,
701             GL_TEXTURE_2D,
702             GL_BGRA,
703             sink->video_width,
704             sink->video_height
705         );
706         if (!sink->texture)
707             goto error_create_texture;
708     }
709     if (!gst_vaapi_texture_put_surface(sink->texture, surface, flags))
710         goto error_transfer_surface;
711
712     target  = gst_vaapi_texture_get_target(sink->texture);
713     texture = gst_vaapi_texture_get_id(sink->texture);
714     if (target != GL_TEXTURE_2D || !texture)
715         return FALSE;
716
717     if (sink->use_reflection)
718         render_background(sink);
719
720     glEnable(target);
721     glBindTexture(target, texture);
722     {
723         if (sink->use_reflection) {
724             glPushMatrix();
725             glRotatef(20.0f, 0.0f, 1.0f, 0.0f);
726             glTranslatef(50.0f, 0.0f, 0.0f);
727         }
728         render_frame(sink);
729         if (sink->use_reflection) {
730             glPushMatrix();
731             glTranslatef(0.0, (GLfloat)sink->display_rect.height + 5.0f, 0.0f);
732             render_reflection(sink);
733             glPopMatrix();
734             glPopMatrix();
735         }
736     }
737     glBindTexture(target, 0);
738     glDisable(target);
739     gst_vaapi_window_glx_swap_buffers(window);
740     return TRUE;
741
742     /* ERRORS */
743 error_create_texture:
744     {
745         GST_DEBUG("could not create VA/GLX texture");
746         return FALSE;
747     }
748 error_transfer_surface:
749     {
750         GST_DEBUG("could not transfer VA surface to texture");
751         return FALSE;
752     }
753 }
754 #endif
755
756 static inline gboolean
757 gst_vaapisink_put_surface(
758     GstVaapiSink    *sink,
759     GstVaapiSurface *surface,
760     guint            flags
761 )
762 {
763     if (!gst_vaapi_window_put_surface(sink->window, surface,
764                 NULL, &sink->display_rect, flags)) {
765         GST_DEBUG("could not render VA surface");
766         return FALSE;
767     }
768     return TRUE;
769 }
770
771 static GstFlowReturn
772 gst_vaapisink_show_frame(GstBaseSink *base_sink, GstBuffer *buffer)
773 {
774     GstVaapiSink * const sink = GST_VAAPISINK(base_sink);
775     GstVaapiVideoBuffer * const vbuffer = GST_VAAPI_VIDEO_BUFFER(buffer);
776     GstVaapiSurface *surface;
777     guint flags;
778     gboolean success;
779     GstVideoOverlayComposition * const composition =
780         gst_video_buffer_get_overlay_composition(buffer);
781
782     if (sink->display != gst_vaapi_video_buffer_get_display (vbuffer)) {
783       g_clear_object(&sink->display);
784       sink->display = g_object_ref (gst_vaapi_video_buffer_get_display (vbuffer));
785     }
786
787     if (!sink->window)
788         return GST_FLOW_UNEXPECTED;
789
790     gst_vaapisink_ensure_rotation(sink, TRUE);
791
792     surface = gst_vaapi_video_buffer_get_surface(vbuffer);
793     if (!surface)
794         return GST_FLOW_UNEXPECTED;
795
796     GST_DEBUG("render surface %" GST_VAAPI_ID_FORMAT,
797               GST_VAAPI_ID_ARGS(gst_vaapi_surface_get_id(surface)));
798
799     flags = gst_vaapi_video_buffer_get_render_flags(vbuffer);
800
801     if (!gst_vaapi_surface_set_subpictures_from_composition(surface,
802              composition, TRUE))
803         GST_WARNING("could not update subtitles");
804
805     switch (sink->display_type) {
806 #if USE_GLX
807     case GST_VAAPI_DISPLAY_TYPE_GLX:
808         success = gst_vaapisink_show_frame_glx(sink, surface, flags);
809         break;
810 #endif
811 #if USE_DRM
812     case GST_VAAPI_DISPLAY_TYPE_DRM:
813         success = TRUE;
814         break;
815 #endif
816 #if USE_X11
817     case GST_VAAPI_DISPLAY_TYPE_X11:
818         success = gst_vaapisink_put_surface(sink, surface, flags);
819         break;
820 #endif
821 #if USE_WAYLAND
822     case GST_VAAPI_DISPLAY_TYPE_WAYLAND:
823         success = gst_vaapisink_put_surface(sink, surface, flags);
824         break;
825 #endif
826     default:
827         GST_ERROR("unsupported display type %d", sink->display_type);
828         success = FALSE;
829         break;
830     }
831     if (!success)
832         return GST_FLOW_UNEXPECTED;
833
834     /* Retain VA surface until the next one is displayed */
835     if (sink->use_overlay)
836         gst_buffer_replace(&sink->video_buffer, buffer);
837     return GST_FLOW_OK;
838 }
839
840 static gboolean
841 gst_vaapisink_query(GstBaseSink *base_sink, GstQuery *query)
842 {
843     GstVaapiSink *sink = GST_VAAPISINK(base_sink);
844     GST_DEBUG ("sharing display %p", sink->display);
845     return gst_vaapi_reply_to_query (query, sink->display);
846 }
847
848 static void
849 gst_vaapisink_finalize(GObject *object)
850 {
851     gst_vaapisink_destroy(GST_VAAPISINK(object));
852
853     G_OBJECT_CLASS(gst_vaapisink_parent_class)->finalize(object);
854 }
855
856 static void
857 gst_vaapisink_set_property(
858     GObject      *object,
859     guint         prop_id,
860     const GValue *value,
861     GParamSpec   *pspec
862 )
863 {
864     GstVaapiSink * const sink = GST_VAAPISINK(object);
865
866     switch (prop_id) {
867     case PROP_DISPLAY_TYPE:
868         sink->display_type = g_value_get_enum(value);
869         break;
870     case PROP_FULLSCREEN:
871         sink->fullscreen = g_value_get_boolean(value);
872         break;
873     case PROP_SYNCHRONOUS:
874         sink->synchronous = g_value_get_boolean(value);
875         break;
876     case PROP_USE_REFLECTION:
877         sink->use_reflection = g_value_get_boolean(value);
878         break;
879     case PROP_ROTATION:
880         sink->rotation_req = g_value_get_enum(value);
881         break;
882     default:
883         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
884         break;
885     }
886 }
887
888 static void
889 gst_vaapisink_get_property(
890     GObject    *object,
891     guint       prop_id,
892     GValue     *value,
893     GParamSpec *pspec
894 )
895 {
896     GstVaapiSink * const sink = GST_VAAPISINK(object);
897
898     switch (prop_id) {
899     case PROP_DISPLAY_TYPE:
900         g_value_set_enum(value, sink->display_type);
901         break;
902     case PROP_FULLSCREEN:
903         g_value_set_boolean(value, sink->fullscreen);
904         break;
905     case PROP_SYNCHRONOUS:
906         g_value_set_boolean(value, sink->synchronous);
907         break;
908     case PROP_USE_REFLECTION:
909         g_value_set_boolean(value, sink->use_reflection);
910         break;
911     case PROP_ROTATION:
912         g_value_set_enum(value, sink->rotation);
913         break;
914     default:
915         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
916         break;
917     }
918 }
919
920 static void
921 gst_vaapisink_class_init(GstVaapiSinkClass *klass)
922 {
923     GObjectClass * const     object_class   = G_OBJECT_CLASS(klass);
924     GstElementClass * const  element_class  = GST_ELEMENT_CLASS(klass);
925     GstBaseSinkClass * const basesink_class = GST_BASE_SINK_CLASS(klass);
926     GstPadTemplate *pad_template;
927
928     GST_DEBUG_CATEGORY_INIT(gst_debug_vaapisink,
929                             GST_PLUGIN_NAME, 0, GST_PLUGIN_DESC);
930
931     object_class->finalize       = gst_vaapisink_finalize;
932     object_class->set_property   = gst_vaapisink_set_property;
933     object_class->get_property   = gst_vaapisink_get_property;
934
935     basesink_class->start        = gst_vaapisink_start;
936     basesink_class->stop         = gst_vaapisink_stop;
937     basesink_class->set_caps     = gst_vaapisink_set_caps;
938     basesink_class->preroll      = gst_vaapisink_show_frame;
939     basesink_class->render       = gst_vaapisink_show_frame;
940     basesink_class->query        = gst_vaapisink_query;
941
942     gst_element_class_set_details_simple(
943         element_class,
944         gst_vaapisink_details.longname,
945         gst_vaapisink_details.klass,
946         gst_vaapisink_details.description,
947         gst_vaapisink_details.author
948     );
949
950     pad_template = gst_static_pad_template_get(&gst_vaapisink_sink_factory);
951     gst_element_class_add_pad_template(element_class, pad_template);
952     gst_object_unref(pad_template);
953
954     g_object_class_install_property
955         (object_class,
956          PROP_DISPLAY_TYPE,
957          g_param_spec_enum("display",
958                            "display type",
959                            "display type to use",
960                            GST_VAAPI_TYPE_DISPLAY_TYPE,
961                            GST_VAAPI_DISPLAY_TYPE_ANY,
962                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
963
964 #if USE_GLX
965     g_object_class_install_property
966         (object_class,
967          PROP_USE_REFLECTION,
968          g_param_spec_boolean("use-reflection",
969                               "Reflection effect",
970                               "Enables OpenGL reflection effect",
971                               FALSE,
972                               G_PARAM_READWRITE));
973 #endif
974
975     g_object_class_install_property
976         (object_class,
977          PROP_FULLSCREEN,
978          g_param_spec_boolean("fullscreen",
979                               "Fullscreen",
980                               "Requests window in fullscreen state",
981                               FALSE,
982                               G_PARAM_READWRITE));
983
984     /**
985      * GstVaapiSink:synchronous:
986      *
987      * When enabled, runs the X display in synchronous mode. Note that
988      * this is used only for debugging.
989      */
990     g_object_class_install_property
991         (object_class,
992          PROP_SYNCHRONOUS,
993          g_param_spec_boolean("synchronous",
994                               "Synchronous mode",
995                               "Toggles X display synchronous mode",
996                               FALSE,
997                               G_PARAM_READWRITE));
998
999     /**
1000      * GstVaapiSink:rotation:
1001      *
1002      * The VA display rotation mode, expressed as a #GstVaapiRotation.
1003      */
1004     g_object_class_install_property
1005         (object_class,
1006          PROP_ROTATION,
1007          g_param_spec_enum(GST_VAAPI_DISPLAY_PROP_ROTATION,
1008                            "rotation",
1009                            "The display rotation mode",
1010                            GST_VAAPI_TYPE_ROTATION,
1011                            DEFAULT_ROTATION,
1012                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1013 }
1014
1015 static void
1016 gst_vaapisink_init(GstVaapiSink *sink)
1017 {
1018     sink->caps           = NULL;
1019     sink->display        = NULL;
1020     sink->window         = NULL;
1021     sink->window_width   = 0;
1022     sink->window_height  = 0;
1023     sink->texture        = NULL;
1024     sink->video_buffer   = NULL;
1025     sink->video_width    = 0;
1026     sink->video_height   = 0;
1027     sink->video_par_n    = 1;
1028     sink->video_par_d    = 1;
1029     sink->foreign_window = FALSE;
1030     sink->fullscreen     = FALSE;
1031     sink->synchronous    = FALSE;
1032     sink->display_type   = DEFAULT_DISPLAY_TYPE;
1033     sink->rotation       = DEFAULT_ROTATION;
1034     sink->rotation_req   = DEFAULT_ROTATION;
1035     sink->use_reflection = FALSE;
1036     sink->use_overlay    = FALSE;
1037     sink->use_rotation   = FALSE;
1038 }