glutils: Export affine transformation functions for gtkglsink
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-base / gst-libs / gst / gl / gstglutils.c
1 /*
2  * GStreamer
3  * Copyright (C) 2013 Matthew Waters <ystreet00@gmail.com>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 /**
22  * SECTION:gstglutils
23  * @title: GstGLUtils
24  * @short_description: some miscellaneous utilities for OpenGL
25  * @see_also: #GstGLContext
26  */
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif
31
32 #include <stdio.h>
33
34 #include <gst/gst.h>
35 #include <glib/gprintf.h>
36
37 #include "gl.h"
38 #include "gstglutils.h"
39 #include "gstglutils_private.h"
40
41 #if GST_GL_HAVE_WINDOW_X11
42 #include <gst/gl/x11/gstgldisplay_x11.h>
43 #endif
44 #if GST_GL_HAVE_WINDOW_WAYLAND
45 #include <gst/gl/wayland/gstgldisplay_wayland.h>
46 #endif
47
48 #define USING_OPENGL(context) (gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL, 1, 0))
49 #define USING_OPENGL3(context) (gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL3, 3, 1))
50 #define USING_GLES(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES, 1, 0))
51 #define USING_GLES2(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES2, 2, 0))
52 #define USING_GLES3(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES2, 3, 0))
53
54 #ifndef GST_DISABLE_GST_DEBUG
55 GST_DEBUG_CATEGORY_STATIC (gst_gl_utils_debug);
56 static GstDebugCategory *
57 _init_gl_utils_debug_category (void)
58 {
59   static gsize _init = 0;
60
61   if (g_once_init_enter (&_init)) {
62     GST_DEBUG_CATEGORY_INIT (gst_gl_utils_debug, "glutils", 0,
63         "OpenGL Utilities");
64     g_once_init_leave (&_init, 1);
65   }
66
67   return gst_gl_utils_debug;
68 }
69
70 #define GST_CAT_DEFAULT _init_gl_utils_debug_category()
71 #endif
72
73 static gboolean
74 gst_gl_display_found (GstElement * element, GstGLDisplay * display)
75 {
76   if (display) {
77     GST_LOG_OBJECT (element, "already have a display (%p)", display);
78     return TRUE;
79   }
80
81   return FALSE;
82 }
83
84 GST_DEBUG_CATEGORY_STATIC (GST_CAT_CONTEXT);
85
86 static void
87 _init_context_debug (void)
88 {
89 #ifndef GST_DISABLE_GST_DEBUG
90   static gsize _init = 0;
91
92   if (g_once_init_enter (&_init)) {
93     GST_DEBUG_CATEGORY_GET (GST_CAT_CONTEXT, "GST_CONTEXT");
94     g_once_init_leave (&_init, 1);
95   }
96 #endif
97 }
98
99 static gboolean
100 pad_query (const GValue * item, GValue * value, gpointer user_data)
101 {
102   GstPad *pad = g_value_get_object (item);
103   GstQuery *query = user_data;
104   gboolean res;
105
106   _init_context_debug ();
107
108   res = gst_pad_peer_query (pad, query);
109
110   if (res) {
111     g_value_set_boolean (value, TRUE);
112     return FALSE;
113   }
114
115   GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, pad, "pad peer query failed");
116   return TRUE;
117 }
118
119 gboolean
120 gst_gl_run_query (GstElement * element, GstQuery * query,
121     GstPadDirection direction)
122 {
123   GstIterator *it;
124   GstIteratorFoldFunction func = pad_query;
125   GValue res = { 0 };
126
127   g_value_init (&res, G_TYPE_BOOLEAN);
128   g_value_set_boolean (&res, FALSE);
129
130   /* Ask neighbor */
131   if (direction == GST_PAD_SRC)
132     it = gst_element_iterate_src_pads (element);
133   else
134     it = gst_element_iterate_sink_pads (element);
135
136   while (gst_iterator_fold (it, func, &res, query) == GST_ITERATOR_RESYNC)
137     gst_iterator_resync (it);
138
139   gst_iterator_free (it);
140
141   return g_value_get_boolean (&res);
142 }
143
144 static void
145 _gst_context_query (GstElement * element, const gchar * display_type)
146 {
147   GstQuery *query;
148   GstContext *ctxt;
149
150   _init_context_debug ();
151
152   /*  2a) Query downstream with GST_QUERY_CONTEXT for the context and
153    *      check if downstream already has a context of the specific type
154    *  2b) Query upstream as above.
155    */
156   query = gst_query_new_context (display_type);
157   if (gst_gl_run_query (element, query, GST_PAD_SRC)) {
158     gst_query_parse_context (query, &ctxt);
159     GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, element,
160         "found context (%p) in downstream query", ctxt);
161     gst_element_set_context (element, ctxt);
162   } else if (gst_gl_run_query (element, query, GST_PAD_SINK)) {
163     gst_query_parse_context (query, &ctxt);
164     GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, element,
165         "found context (%p) in upstream query", ctxt);
166     gst_element_set_context (element, ctxt);
167   } else {
168     /* 3) Post a GST_MESSAGE_NEED_CONTEXT message on the bus with
169      *    the required context type and afterwards check if a
170      *    usable context was set now as in 1). The message could
171      *    be handled by the parent bins of the element and the
172      *    application.
173      */
174     GstMessage *msg;
175
176     GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, element,
177         "posting need context message");
178     msg = gst_message_new_need_context (GST_OBJECT_CAST (element),
179         display_type);
180     gst_element_post_message (element, msg);
181   }
182
183   /*
184    * Whomever responds to the need-context message performs a
185    * GstElement::set_context() with the required context in which the element
186    * is required to update the display_ptr or call gst_gl_handle_set_context().
187    */
188
189   gst_query_unref (query);
190 }
191
192 static void
193 gst_gl_display_context_query (GstElement * element, GstGLDisplay ** display_ptr)
194 {
195   _gst_context_query (element, GST_GL_DISPLAY_CONTEXT_TYPE);
196   if (*display_ptr)
197     return;
198
199 #if GST_GL_HAVE_WINDOW_X11
200   _gst_context_query (element, "gst.x11.display.handle");
201   if (*display_ptr)
202     return;
203 #endif
204
205 #if GST_GL_HAVE_WINDOW_WAYLAND
206   _gst_context_query (element, "GstWaylandDisplayHandleContextType");
207   if (*display_ptr)
208     return;
209 #endif
210 }
211
212 static void
213 gst_gl_context_query (GstElement * element)
214 {
215   _gst_context_query (element, "gst.gl.app_context");
216 }
217
218 /*  4) Create a context by itself and post a GST_MESSAGE_HAVE_CONTEXT
219  *     message.
220  */
221 void
222 gst_gl_element_propagate_display_context (GstElement * element,
223     GstGLDisplay * display)
224 {
225   GstContext *context;
226   GstMessage *msg;
227
228   if (!display) {
229     GST_ERROR_OBJECT (element, "Could not get GL display connection");
230     return;
231   }
232
233   _init_context_debug ();
234
235   context = gst_context_new (GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
236   gst_context_set_gl_display (context, display);
237
238   gst_element_set_context (element, context);
239
240   GST_CAT_INFO_OBJECT (GST_CAT_CONTEXT, element,
241       "posting have context (%p) message with display (%p)", context, display);
242   msg = gst_message_new_have_context (GST_OBJECT_CAST (element), context);
243   gst_element_post_message (GST_ELEMENT_CAST (element), msg);
244 }
245
246 /**
247  * gst_gl_ensure_element_data:
248  * @element: the #GstElement running the query
249  * @display_ptr: (inout): the resulting #GstGLDisplay
250  * @other_context_ptr: (inout): the resulting #GstGLContext
251  *
252  * Perform the steps necessary for retrieving a #GstGLDisplay and (optionally)
253  * an application provided #GstGLContext from the surrounding elements or from
254  * the application using the #GstContext mechanism.
255  *
256  * If the contents of @display_ptr or @other_context_ptr are not %NULL, then no
257  * #GstContext query is necessary for #GstGLDisplay or #GstGLContext retrieval
258  * or is performed.
259  *
260  * This performs #GstContext queries (if necessary) for a winsys display
261  * connection with %GST_GL_DISPLAY_CONTEXT_TYPE, "gst.x11.display.handle", and
262  * "GstWaylandDisplayHandleContextType" stopping after the first successful
263  * retrieval.
264  *
265  * This also performs a #GstContext query (if necessary) for an optional
266  * application provided #GstGLContext using the name "gst.gl.app_context".
267  * The returned #GstGLContext will be shared with a GStreamer created OpenGL context.
268  *
269  * Returns: whether a #GstGLDisplay exists in @display_ptr
270  */
271 gboolean
272 gst_gl_ensure_element_data (gpointer element, GstGLDisplay ** display_ptr,
273     GstGLContext ** other_context_ptr)
274 {
275   GstGLDisplay *display;
276
277   g_return_val_if_fail (element != NULL, FALSE);
278   g_return_val_if_fail (display_ptr != NULL, FALSE);
279   g_return_val_if_fail (other_context_ptr != NULL, FALSE);
280
281   /*  1) Check if the element already has a context of the specific
282    *     type.
283    */
284   display = *display_ptr;
285   if (gst_gl_display_found (element, display))
286     goto done;
287
288   gst_gl_display_context_query (element, display_ptr);
289
290   /* Neighbour found and it updated the display */
291   if (gst_gl_display_found (element, *display_ptr))
292     goto get_gl_context;
293
294   /* If no neighbor, or application not interested, use system default */
295   display = gst_gl_display_new ();
296
297   *display_ptr = display;
298
299   gst_gl_element_propagate_display_context (element, display);
300
301 get_gl_context:
302   if (*other_context_ptr)
303     goto done;
304
305   gst_gl_context_query (element);
306
307 done:
308   return *display_ptr != NULL;
309 }
310
311 /**
312  * gst_gl_handle_set_context:
313  * @element: a #GstElement
314  * @context: a #GstContext
315  * @display: (inout) (transfer full): location of a #GstGLDisplay
316  * @other_context: (inout) (transfer full): location of a #GstGLContext
317  *
318  * Helper function for implementing #GstElementClass.set_context() in
319  * OpenGL capable elements.
320  *
321  * Retrieve's the #GstGLDisplay or #GstGLContext in @context and places the
322  * result in @display or @other_context respectively.
323  *
324  * Returns: whether the @display or @other_context could be set successfully
325  */
326 gboolean
327 gst_gl_handle_set_context (GstElement * element, GstContext * context,
328     GstGLDisplay ** display, GstGLContext ** other_context)
329 {
330   GstGLDisplay *display_replacement = NULL;
331   GstGLContext *context_replacement = NULL;
332   const gchar *context_type;
333
334   g_return_val_if_fail (display != NULL, FALSE);
335   g_return_val_if_fail (other_context != NULL, FALSE);
336
337   if (!context)
338     return FALSE;
339
340   context_type = gst_context_get_context_type (context);
341
342   if (g_strcmp0 (context_type, GST_GL_DISPLAY_CONTEXT_TYPE) == 0) {
343     if (!gst_context_get_gl_display (context, &display_replacement)) {
344       GST_WARNING_OBJECT (element, "Failed to get display from context");
345       return FALSE;
346     }
347   }
348 #if GST_GL_HAVE_WINDOW_X11
349   else if (g_strcmp0 (context_type, "gst.x11.display.handle") == 0) {
350     const GstStructure *s;
351     Display *display;
352
353     s = gst_context_get_structure (context);
354     if (gst_structure_get (s, "display", G_TYPE_POINTER, &display, NULL))
355       display_replacement =
356           (GstGLDisplay *) gst_gl_display_x11_new_with_display (display);
357   }
358 #endif
359 #if GST_GL_HAVE_WINDOW_WAYLAND
360   else if (g_strcmp0 (context_type, "GstWaylandDisplayHandleContextType") == 0) {
361     const GstStructure *s;
362     struct wl_display *display;
363
364     s = gst_context_get_structure (context);
365     if (gst_structure_get (s, "display", G_TYPE_POINTER, &display, NULL))
366       display_replacement =
367           (GstGLDisplay *) gst_gl_display_wayland_new_with_display (display);
368   }
369 #endif
370   else if (g_strcmp0 (context_type, "gst.gl.app_context") == 0) {
371     const GstStructure *s = gst_context_get_structure (context);
372     GstGLDisplay *context_display;
373     GstGLDisplay *element_display;
374
375     if (gst_structure_get (s, "context", GST_TYPE_GL_CONTEXT,
376             &context_replacement, NULL)) {
377       context_display = gst_gl_context_get_display (context_replacement);
378       element_display = display_replacement ? display_replacement : *display;
379       if (element_display
380           && (gst_gl_display_get_handle_type (element_display) &
381               gst_gl_display_get_handle_type (context_display)) == 0) {
382         GST_ELEMENT_WARNING (element, LIBRARY, SETTINGS, ("%s",
383                 "Cannot set a GL context with a different display type"), ("%s",
384                 "Cannot set a GL context with a different display type"));
385         gst_object_unref (context_replacement);
386         context_replacement = NULL;
387       }
388       gst_object_unref (context_display);
389     }
390   }
391
392   if (display_replacement) {
393     GstGLDisplay *old = *display;
394     *display = display_replacement;
395
396     if (old)
397       gst_object_unref (old);
398   }
399
400   if (context_replacement) {
401     GstGLContext *old = *other_context;
402     *other_context = context_replacement;
403
404     if (old)
405       gst_object_unref (old);
406   }
407
408   return TRUE;
409 }
410
411 /**
412  * gst_gl_handle_context_query:
413  * @element: a #GstElement
414  * @query: a #GstQuery of type %GST_QUERY_CONTEXT
415  * @display: (transfer none) (nullable): a #GstGLDisplay
416  * @context: (transfer none) (nullable): a #GstGLContext
417  * @other_context: (transfer none) (nullable): application provided #GstGLContext
418  *
419  * Returns: Whether the @query was successfully responded to from the passed
420  *          @display, @context, and @other_context.
421  */
422 gboolean
423 gst_gl_handle_context_query (GstElement * element, GstQuery * query,
424     GstGLDisplay * display, GstGLContext * gl_context,
425     GstGLContext * other_context)
426 {
427   const gchar *context_type;
428   GstContext *context, *old_context;
429
430   g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
431   g_return_val_if_fail (GST_IS_QUERY (query), FALSE);
432   g_return_val_if_fail (display == NULL || GST_IS_GL_DISPLAY (display), FALSE);
433   g_return_val_if_fail (gl_context == NULL
434       || GST_IS_GL_CONTEXT (gl_context), FALSE);
435   g_return_val_if_fail (other_context == NULL
436       || GST_IS_GL_CONTEXT (other_context), FALSE);
437
438   GST_LOG_OBJECT (element, "handle context query %" GST_PTR_FORMAT, query);
439   gst_query_parse_context_type (query, &context_type);
440
441   if (display && g_strcmp0 (context_type, GST_GL_DISPLAY_CONTEXT_TYPE) == 0) {
442     gst_query_parse_context (query, &old_context);
443
444     if (old_context)
445       context = gst_context_copy (old_context);
446     else
447       context = gst_context_new (GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
448
449     gst_context_set_gl_display (context, display);
450     gst_query_set_context (query, context);
451     gst_context_unref (context);
452     GST_DEBUG_OBJECT (element, "successfully set %" GST_PTR_FORMAT
453         " on %" GST_PTR_FORMAT, display, query);
454
455     return TRUE;
456   }
457 #if GST_GL_HAVE_WINDOW_X11
458   else if (display && g_strcmp0 (context_type, "gst.x11.display.handle") == 0) {
459     GstStructure *s;
460
461     gst_query_parse_context (query, &old_context);
462
463     if (old_context)
464       context = gst_context_copy (old_context);
465     else
466       context = gst_context_new ("gst.x11.display.handle", TRUE);
467
468     if (gst_gl_display_get_handle_type (display) & GST_GL_DISPLAY_TYPE_X11) {
469       Display *x11_display = (Display *) gst_gl_display_get_handle (display);
470
471       if (x11_display) {
472         s = gst_context_writable_structure (context);
473         gst_structure_set (s, "display", G_TYPE_POINTER, x11_display, NULL);
474
475         gst_query_set_context (query, context);
476         gst_context_unref (context);
477
478         GST_DEBUG_OBJECT (element, "successfully set x11 display %p (from %"
479             GST_PTR_FORMAT ") on %" GST_PTR_FORMAT, x11_display, display,
480             query);
481
482         return TRUE;
483       }
484     }
485   }
486 #endif
487 #if GST_GL_HAVE_WINDOW_WAYLAND
488   else if (display
489       && g_strcmp0 (context_type, "GstWaylandDisplayHandleContextType") == 0) {
490     GstStructure *s;
491
492     gst_query_parse_context (query, &old_context);
493
494     if (old_context)
495       context = gst_context_copy (old_context);
496     else
497       context = gst_context_new ("GstWaylandDisplayHandleContextType", TRUE);
498
499     if (gst_gl_display_get_handle_type (display) & GST_GL_DISPLAY_TYPE_WAYLAND) {
500       struct wl_display *wayland_display =
501           (struct wl_display *) gst_gl_display_get_handle (display);
502
503       if (wayland_display) {
504         s = gst_context_writable_structure (context);
505         gst_structure_set (s, "display", G_TYPE_POINTER, wayland_display, NULL);
506
507         gst_query_set_context (query, context);
508         gst_context_unref (context);
509
510         GST_DEBUG_OBJECT (element, "successfully set wayland display %p (from %"
511             GST_PTR_FORMAT ") on %" GST_PTR_FORMAT, wayland_display, display,
512             query);
513
514         return TRUE;
515       }
516     }
517   }
518 #endif
519   else if (other_context && g_strcmp0 (context_type, "gst.gl.app_context") == 0) {
520     GstStructure *s;
521
522     gst_query_parse_context (query, &old_context);
523
524     if (old_context)
525       context = gst_context_copy (old_context);
526     else
527       context = gst_context_new ("gst.gl.app_context", TRUE);
528
529     s = gst_context_writable_structure (context);
530     gst_structure_set (s, "context", GST_TYPE_GL_CONTEXT, other_context, NULL);
531     gst_query_set_context (query, context);
532     gst_context_unref (context);
533
534     GST_DEBUG_OBJECT (element, "successfully set application GL context %"
535         GST_PTR_FORMAT " on %" GST_PTR_FORMAT, other_context, query);
536
537     return TRUE;
538   } else if (gl_context
539       && g_strcmp0 (context_type, "gst.gl.local_context") == 0) {
540     GstStructure *s;
541
542     gst_query_parse_context (query, &old_context);
543
544     if (old_context)
545       context = gst_context_copy (old_context);
546     else
547       context = gst_context_new ("gst.gl.local_context", TRUE);
548
549     s = gst_context_writable_structure (context);
550     gst_structure_set (s, "context", GST_TYPE_GL_CONTEXT, gl_context, NULL);
551     gst_query_set_context (query, context);
552     gst_context_unref (context);
553
554     GST_DEBUG_OBJECT (element, "successfully set GL context %"
555         GST_PTR_FORMAT " on %" GST_PTR_FORMAT, gl_context, query);
556
557     return TRUE;
558   }
559
560   return FALSE;
561 }
562
563 /**
564  * gst_gl_query_local_gl_context:
565  * @element: a #GstElement to query from
566  * @direction: the #GstPadDirection to query
567  * @context_ptr: (inout): location containing the current and/or resulting
568  *                      #GstGLContext
569  *
570  * Performs a GST_QUERY_CONTEXT query of type "gst.gl.local_context" on all
571  * #GstPads in @element of @direction for the local OpenGL context used by
572  * GStreamer elements.
573  *
574  * Returns: whether @context_ptr contains a #GstGLContext
575  */
576 gboolean
577 gst_gl_query_local_gl_context (GstElement * element, GstPadDirection direction,
578     GstGLContext ** context_ptr)
579 {
580   GstQuery *query;
581   GstContext *context;
582   const GstStructure *s;
583
584   g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
585   g_return_val_if_fail (context_ptr != NULL, FALSE);
586
587   if (*context_ptr)
588     return TRUE;
589
590   query = gst_query_new_context ("gst.gl.local_context");
591   if (gst_gl_run_query (GST_ELEMENT (element), query, direction)) {
592     gst_query_parse_context (query, &context);
593     if (context) {
594       s = gst_context_get_structure (context);
595       gst_structure_get (s, "context", GST_TYPE_GL_CONTEXT, context_ptr, NULL);
596     }
597   }
598
599   gst_query_unref (query);
600
601   return *context_ptr != NULL;
602 }
603
604 /**
605  * gst_gl_get_plane_data_size:
606  * @info: a #GstVideoInfo
607  * @align: a #GstVideoAlignment or %NULL
608  * @plane: plane number in @info to retrieve the data size of
609  *
610  * Retrieve the size in bytes of a video plane of data with a certain alignment
611  */
612 gsize
613 gst_gl_get_plane_data_size (const GstVideoInfo * info,
614     const GstVideoAlignment * align, guint plane)
615 {
616   gint comp[GST_VIDEO_MAX_COMPONENTS];
617   gint padded_height;
618   gsize plane_size;
619
620   gst_video_format_info_component (info->finfo, plane, comp);
621
622   padded_height = info->height;
623
624   if (align)
625     padded_height += align->padding_top + align->padding_bottom;
626
627   padded_height =
628       GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (info->finfo, comp[0], padded_height);
629
630   plane_size = GST_VIDEO_INFO_PLANE_STRIDE (info, plane) * padded_height;
631
632   return plane_size;
633 }
634
635 /**
636  * gst_gl_get_plane_start:
637  * @info: a #GstVideoInfo
638  * @valign: a #GstVideoAlignment or %NULL
639  * @plane: plane number in @info to retrieve the data size of
640  *
641  * Returns: difference between the supposed start of the plane from the @info
642  *          and where the data from the previous plane ends.
643  */
644 gsize
645 gst_gl_get_plane_start (const GstVideoInfo * info,
646     const GstVideoAlignment * valign, guint plane)
647 {
648   gsize plane_start;
649   gint i;
650
651   /* find the start of the plane data including padding */
652   plane_start = 0;
653   for (i = 0; i < plane; i++) {
654     plane_start += gst_gl_get_plane_data_size (info, valign, i);
655   }
656
657   /* offset between the plane data start and where the video frame starts */
658   return (GST_VIDEO_INFO_PLANE_OFFSET (info, plane)) - plane_start;
659 }
660
661 /**
662  * gst_gl_value_get_texture_target_mask:
663  * @value: an initialized #GValue of type G_TYPE_STRING
664  *
665  * See gst_gl_value_set_texture_target_from_mask() for what entails a mask
666  *
667  * Returns: the mask of #GstGLTextureTarget's in @value or
668  *     %GST_GL_TEXTURE_TARGET_NONE on failure
669  */
670 GstGLTextureTarget
671 gst_gl_value_get_texture_target_mask (const GValue * targets)
672 {
673   guint new_targets = 0;
674
675   g_return_val_if_fail (targets != NULL, GST_GL_TEXTURE_TARGET_NONE);
676
677   if (G_TYPE_CHECK_VALUE_TYPE (targets, G_TYPE_STRING)) {
678     GstGLTextureTarget target;
679     const gchar *str;
680
681     str = g_value_get_string (targets);
682     target = gst_gl_texture_target_from_string (str);
683
684     if (target)
685       new_targets |= 1 << target;
686   } else if (G_TYPE_CHECK_VALUE_TYPE (targets, GST_TYPE_LIST)) {
687     gint j, m;
688
689     m = gst_value_list_get_size (targets);
690     for (j = 0; j < m; j++) {
691       const GValue *val = gst_value_list_get_value (targets, j);
692       GstGLTextureTarget target;
693       const gchar *str;
694
695       str = g_value_get_string (val);
696       target = gst_gl_texture_target_from_string (str);
697       if (target)
698         new_targets |= 1 << target;
699     }
700   }
701
702   return new_targets;
703 }
704
705 /**
706  * gst_gl_value_set_texture_target:
707  * @value: an initialized #GValue of type G_TYPE_STRING
708  * @target: a #GstGLTextureTarget's
709  *
710  * Returns: whether the @target could be set on @value
711  */
712 gboolean
713 gst_gl_value_set_texture_target (GValue * value, GstGLTextureTarget target)
714 {
715   g_return_val_if_fail (value != NULL, FALSE);
716   g_return_val_if_fail (target != GST_GL_TEXTURE_TARGET_NONE, FALSE);
717
718   if (target == GST_GL_TEXTURE_TARGET_2D) {
719     g_value_set_static_string (value, GST_GL_TEXTURE_TARGET_2D_STR);
720   } else if (target == GST_GL_TEXTURE_TARGET_RECTANGLE) {
721     g_value_set_static_string (value, GST_GL_TEXTURE_TARGET_RECTANGLE_STR);
722   } else if (target == GST_GL_TEXTURE_TARGET_EXTERNAL_OES) {
723     g_value_set_static_string (value, GST_GL_TEXTURE_TARGET_EXTERNAL_OES_STR);
724   } else {
725     return FALSE;
726   }
727
728   return TRUE;
729 }
730
731 static guint64
732 _gst_gl_log2_int64 (guint64 value)
733 {
734   guint64 ret = 0;
735
736   while (value >>= 1)
737     ret++;
738
739   return ret;
740 }
741
742 /**
743  * gst_gl_value_set_texture_target_from_mask:
744  * @value: an uninitialized #GValue
745  * @target_mask: a bitwise mask of #GstGLTextureTarget's
746  *
747  * A mask is a bitwise OR of (1 << target) where target is a valid
748  * #GstGLTextureTarget
749  *
750  * Returns: whether the @target_mask could be set on @value
751  */
752 gboolean
753 gst_gl_value_set_texture_target_from_mask (GValue * value,
754     GstGLTextureTarget target_mask)
755 {
756   g_return_val_if_fail (value != NULL, FALSE);
757   g_return_val_if_fail (target_mask != GST_GL_TEXTURE_TARGET_NONE, FALSE);
758
759   if ((target_mask & (target_mask - 1)) == 0) {
760     /* only one texture target set */
761     g_value_init (value, G_TYPE_STRING);
762     return gst_gl_value_set_texture_target (value,
763         _gst_gl_log2_int64 (target_mask));
764   } else {
765     GValue item = G_VALUE_INIT;
766     gboolean ret = FALSE;
767
768     g_value_init (value, GST_TYPE_LIST);
769     g_value_init (&item, G_TYPE_STRING);
770     if (target_mask & (1 << GST_GL_TEXTURE_TARGET_2D)) {
771       gst_gl_value_set_texture_target (&item, GST_GL_TEXTURE_TARGET_2D);
772       gst_value_list_append_value (value, &item);
773       ret = TRUE;
774     }
775     if (target_mask & (1 << GST_GL_TEXTURE_TARGET_RECTANGLE)) {
776       gst_gl_value_set_texture_target (&item, GST_GL_TEXTURE_TARGET_RECTANGLE);
777       gst_value_list_append_value (value, &item);
778       ret = TRUE;
779     }
780     if (target_mask & (1 << GST_GL_TEXTURE_TARGET_EXTERNAL_OES)) {
781       gst_gl_value_set_texture_target (&item,
782           GST_GL_TEXTURE_TARGET_EXTERNAL_OES);
783       gst_value_list_append_value (value, &item);
784       ret = TRUE;
785     }
786
787     g_value_unset (&item);
788     return ret;
789   }
790 }
791
792 static const gfloat identity_matrix[] = {
793   1.0, 0.0, 0.0, 0.0,
794   0.0, 1.0, 0.0, 0.0,
795   0.0, 0.0, 1.0, 0.0,
796   0.0, 0.0, 0.0, 1.0,
797 };
798
799 static const gfloat from_ndc_matrix[] = {
800   0.5, 0.0, 0.0, 0.0,
801   0.0, 0.5, 0.0, 0.0,
802   0.0, 0.0, 0.5, 0.0,
803   0.5, 0.5, 0.5, 1.0,
804 };
805
806 static const gfloat to_ndc_matrix[] = {
807   2.0, 0.0, 0.0, 0.0,
808   0.0, 2.0, 0.0, 0.0,
809   0.0, 0.0, 2.0, 0.0,
810   -1.0, -1.0, -1.0, 1.0,
811 };
812
813 /**
814  * gst_gl_multiply_matrix4:
815  * @a: (array fixed-size=16): a 2-dimensional 4x4 array of #gfloat
816  * @b: (array fixed-size=16): another 2-dimensional 4x4 array of #gfloat
817  * @result: (out) (array fixed-size=16): the result of the multiplication
818  *
819  * Multiplies two 4x4 matrices, @a and @b, and stores the result, a
820  * 2-dimensional array of #gfloat, in @result.
821  *
822  * Since: 1.20
823  */
824 /* https://en.wikipedia.org/wiki/Matrix_multiplication */
825 void
826 gst_gl_multiply_matrix4 (const gfloat * a, const gfloat * b, gfloat * result)
827 {
828   int i, j, k;
829   gfloat tmp[16] = { 0.0f };
830
831   g_return_if_fail (a != NULL);
832   g_return_if_fail (b != NULL);
833   g_return_if_fail (result != NULL);
834
835   for (i = 0; i < 4; i++) {     /* column */
836     for (j = 0; j < 4; j++) {   /* row */
837       for (k = 0; k < 4; k++) {
838         tmp[j + (i * 4)] += a[k + (i * 4)] * b[j + (k * 4)];
839       }
840     }
841   }
842
843   for (i = 0; i < 16; i++)
844     result[i] = tmp[i];
845 }
846
847 /**
848  * gst_gl_get_affine_transformation_meta_as_ndc:
849  * @meta: (nullable): a #GstVideoAffineTransformationMeta
850  * @matrix: (out): result of the 4x4 matrix
851  *
852  * Retrieves the stored 4x4 affine transformation matrix stored in @meta in
853  * NDC coordinates. if @meta is NULL, an identity matrix is returned.
854  *
855  * NDC is a left-handed coordinate system
856  * - x - [-1, 1] - +ve X moves right
857  * - y - [-1, 1] - +ve Y moves up
858  * - z - [-1, 1] - +ve Z moves into
859  *
860  * Since: 1.20
861  */
862 void
863 gst_gl_get_affine_transformation_meta_as_ndc (GstVideoAffineTransformationMeta *
864     meta, gfloat * matrix)
865 {
866   g_return_if_fail (matrix != NULL);
867
868   if (!meta) {
869     int i;
870
871     for (i = 0; i < 16; i++) {
872       matrix[i] = identity_matrix[i];
873     }
874   } else {
875     float tmp[16];
876
877     /* change of basis multiplications */
878     gst_gl_multiply_matrix4 (from_ndc_matrix, meta->matrix, tmp);
879     gst_gl_multiply_matrix4 (tmp, to_ndc_matrix, matrix);
880   }
881 }
882
883 /**
884  * gst_gl_set_affine_transformation_meta_from_ndc:
885  * @meta: a #GstVideoAffineTransformationMeta
886  * @matrix: a 4x4 matrix
887  *
888  * Set the 4x4 affine transformation matrix stored in @meta from the
889  * NDC coordinates in @matrix.
890  *
891  * Since: 1.20
892  */
893 void gst_gl_set_affine_transformation_meta_from_ndc
894     (GstVideoAffineTransformationMeta * meta, const gfloat * matrix)
895 {
896   float tmp[16];
897
898   g_return_if_fail (meta != NULL);
899   g_return_if_fail (matrix != NULL);
900
901   /* change of basis multiplications */
902   gst_gl_multiply_matrix4 (to_ndc_matrix, matrix, tmp);
903   gst_gl_multiply_matrix4 (tmp, from_ndc_matrix, meta->matrix);
904 }