3 * Copyright (C) 2009 Julien Isorce <julien.isorce@mail.com>
4 * Copyright (C) 2014 Jan Schmidt <jan@centricular.com>
5 * Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
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 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
24 * SECTION:gstglviewconvert
25 * @title: GstGLViewConvert
26 * @short_description: convert between steroscopic/multiview video formats
27 * @see_also: #GstGLColorConvert, #GstGLContext
29 * Convert stereoscopic/multiview video using fragment shaders.
36 #include "gstglviewconvert.h"
38 #include <gst/video/gstvideoaffinetransformationmeta.h>
41 #include "gstglsl_private.h"
42 #include "gstglutils_private.h"
44 #define USING_OPENGL(context) (gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL, 1, 0))
45 #define USING_OPENGL3(context) (gst_gl_context_check_gl_version (context, GST_GL_API_OPENGL3, 3, 1))
46 #define USING_GLES(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES, 1, 0))
47 #define USING_GLES2(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES2, 2, 0))
48 #define USING_GLES3(context) (gst_gl_context_check_gl_version (context, GST_GL_API_GLES2, 3, 0))
50 static GstStaticCaps caps_template =
51 GST_STATIC_CAPS ("video/x-raw(" GST_CAPS_FEATURE_MEMORY_GL_MEMORY "), "
52 "format = (string) RGBA, "
53 "width = " GST_VIDEO_SIZE_RANGE ", "
54 "height = " GST_VIDEO_SIZE_RANGE ", "
55 "framerate = " GST_VIDEO_FPS_RANGE ", "
56 "texture-target = (string) { 2D, rectangle, external-oes } ");
58 #define GST_CAT_DEFAULT gst_gl_view_convert_debug
59 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
68 PROP_OUTPUT_DOWNMIX_MODE
71 #define DEFAULT_DOWNMIX GST_GL_STEREO_DOWNMIX_ANAGLYPH_GREEN_MAGENTA_DUBOIS
73 struct _GstGLViewConvertPrivate
77 GstVideoMultiviewMode input_mode;
78 GstVideoMultiviewFlags input_flags;
79 GstVideoMultiviewMode output_mode;
80 GstVideoMultiviewFlags output_flags;
82 GstBuffer *primary_in;
83 GstBuffer *auxilliary_in;
85 GstBuffer *primary_out;
86 GstBuffer *auxilliary_out;
88 GstGLMemory *in_tex[GST_VIDEO_MAX_PLANES];
89 GstGLMemory *out_tex[GST_VIDEO_MAX_PLANES];
100 GST_DEBUG_CATEGORY_INIT (gst_gl_view_convert_debug, "glviewconvert", 0, "glviewconvert object");
102 G_DEFINE_TYPE_WITH_CODE (GstGLViewConvert, gst_gl_view_convert,
103 GST_TYPE_OBJECT, G_ADD_PRIVATE (GstGLViewConvert) DEBUG_INIT);
105 static void gst_gl_view_convert_set_property (GObject * object,
106 guint prop_id, const GValue * value, GParamSpec * pspec);
107 static void gst_gl_view_convert_get_property (GObject * object,
108 guint prop_id, GValue * value, GParamSpec * pspec);
109 static void gst_gl_view_convert_finalize (GObject * object);
111 static void _do_view_convert (GstGLContext * context,
112 GstGLViewConvert * viewconvert);
115 /* These match the order and number of DOWNMIX_ANAGLYPH_* modes */
116 static GLfloat downmix_matrices[][2][9] = {
117 { /* Green-Magenta Dubois */
118 {-0.062f, 0.284f, -0.015f, -0.158f, 0.668f, -0.027f, -0.039f, 0.143f, 0.021f},
119 {0.529f, -0.016f, 0.009f, 0.705f, -0.015f, 0.075f, 0.024f, -0.065f, 0.937f}
121 { /* Red-Cyan Dubois */
122 /* Source of this matrix: http://www.site.uottawa.ca/~edubois/anaglyph/LeastSquaresHowToPhotoshop.pdf */
123 {0.437f, -0.062f, -0.048f, 0.449f, -0.062f, -0.050f, 0.164f, -0.024f, -0.017f},
124 {-0.011f, 0.377f, -0.026f, -0.032f, 0.761f, -0.093f, -0.007f, 0.009f, 1.234f}
126 { /* Amber-blue Dubois */
127 {1.062f, -0.026f, -0.038f, -0.205f, 0.908f, -0.173f, 0.299f, 0.068f, 0.022f},
128 {-0.016f, 0.006f, 0.094f, -0.123f, 0.062f, 0.185f, -0.017f, -0.017f, 0.911f}
132 static gfloat identity_matrix[] = {
133 1.0f, 0.0f, 0.0f, 0.0f,
134 0.0f, 1.0f, 0.0f, 0.0f,
135 0.0f, 0.0f, 1.0f, 0.0f,
136 0.0f, 0.0f, 0.0f, 1.0f,
140 #define glsl_OES_extension_string "#extension GL_OES_EGL_image_external : require \n"
143 static const gchar *fragment_header =
144 "uniform sampler2D tex_l;\n"
145 "uniform sampler2D tex_r;\n"
146 "uniform float width;\n"
147 "uniform float height;\n"
148 "uniform mat3 downmix[2];\n"
149 "uniform vec2 tex_scale[2];\n"
150 "uniform vec2 offsets[2];\n";
152 static const gchar *frag_input =
153 " vec2 l_tex = v_texcoord * tex_scale[0] + offsets[0];\n"
154 " vec2 r_tex = v_texcoord * tex_scale[1] + offsets[1];\n"
155 " l = texture2D(tex_l, l_tex).rgba;\n"
156 " r = texture2D(tex_r, r_tex).rgba;\n";
158 static const gchar *frag_output_downmix =
159 " vec3 lcol = l.rgb * l.a + vec3(1.0-l.a);\n"
160 " vec3 rcol = r.rgb * r.a + vec3(1.0-r.a);\n"
161 " if (l.a + r.a > 0.0) {\n"
162 " lcol = clamp (downmix[0] * lcol, 0.0, 1.0);\n"
163 " rcol = clamp (downmix[1] * rcol, 0.0, 1.0);\n"
164 " gl_FragColor = vec4 (lcol + rcol, 1.0);\n"
166 " gl_FragColor = vec4 (0.0);\n"
169 static const gchar *frag_output_left =
170 " gl_FragColor = l;\n";
172 static const gchar *frag_output_right =
173 " gl_FragColor = r;\n";
175 static const gchar *frag_output_side_by_side =
176 " if (v_texcoord.x < 0.5) {\n"
177 " gl_FragColor = l;\n"
179 " gl_FragColor = r;\n"
182 static const gchar *frag_output_top_bottom =
183 "if (v_texcoord.y < 0.5) {\n"
184 " gl_FragColor = l;\n"
186 " gl_FragColor = r;\n"
189 static const gchar *frag_output_column_interleaved =
190 "if (int(mod(l_tex.x * width, 2.0)) == 0) {\n"
191 " gl_FragColor = l;\n"
193 " gl_FragColor = r;\n"
196 static const gchar *frag_output_row_interleaved =
197 "if (int(mod(l_tex.y * height, 2.0)) == 0) {\n"
198 " gl_FragColor = l;\n"
200 " gl_FragColor = r;\n"
203 static const gchar *frag_output_checkerboard =
204 "if (int(mod(l_tex.x * width, 2.0)) == \n"
205 " int(mod(l_tex.y * height, 2.0))) {\n"
206 " gl_FragColor = l;\n"
208 " gl_FragColor = r;\n"
211 static const gchar *frag_output_separated =
212 "gl_FragData[0] = l;\n"
213 "gl_FragData[1] = r;\n";
216 static const GLfloat vertices[] = {
217 1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
218 -1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
219 -1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
220 1.0f, 1.0f, 0.0f, 1.0f, 1.0f
223 static const GLushort indices[] = { 0, 1, 2, 0, 2, 3 };
226 gst_gl_view_convert_class_init (GstGLViewConvertClass * klass)
228 GObjectClass *gobject_class = (GObjectClass *) klass;
230 gobject_class->set_property = gst_gl_view_convert_set_property;
231 gobject_class->get_property = gst_gl_view_convert_get_property;
232 gobject_class->finalize = gst_gl_view_convert_finalize;
234 g_object_class_install_property (gobject_class, PROP_INPUT_LAYOUT,
235 g_param_spec_enum ("input-mode-override",
236 "Input Multiview Mode Override",
237 "Override any input information about multiview layout",
238 GST_TYPE_VIDEO_MULTIVIEW_MODE,
239 GST_VIDEO_MULTIVIEW_MODE_NONE,
240 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
241 g_object_class_install_property (gobject_class, PROP_INPUT_FLAGS,
242 g_param_spec_flags ("input-flags-override",
243 "Input Multiview Flags Override",
244 "Override any input information about multiview layout flags",
245 GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE,
246 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
247 g_object_class_install_property (gobject_class, PROP_OUTPUT_LAYOUT,
248 g_param_spec_enum ("output-mode-override",
249 "Output Multiview Mode Override",
250 "Override automatic output mode selection for multiview layout",
251 GST_TYPE_VIDEO_MULTIVIEW_MODE, GST_VIDEO_MULTIVIEW_MODE_NONE,
252 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
253 g_object_class_install_property (gobject_class, PROP_OUTPUT_FLAGS,
254 g_param_spec_flags ("output-flags-override",
255 "Output Multiview Flags Override",
256 "Override automatic negotiation for output multiview layout flags",
257 GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE,
258 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
259 g_object_class_install_property (gobject_class, PROP_OUTPUT_DOWNMIX_MODE,
260 g_param_spec_enum ("downmix-mode", "Mode for mono downmixed output",
261 "Output anaglyph type to generate when downmixing to mono",
262 GST_TYPE_GL_STEREO_DOWNMIX, DEFAULT_DOWNMIX,
263 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
267 gst_gl_view_convert_init (GstGLViewConvert * convert)
269 convert->priv = gst_gl_view_convert_get_instance_private (convert);
271 convert->shader = NULL;
272 convert->downmix_mode = DEFAULT_DOWNMIX;
273 convert->priv->input_mode = GST_VIDEO_MULTIVIEW_MODE_NONE;
274 convert->priv->input_flags = GST_VIDEO_MULTIVIEW_FLAGS_NONE;
275 convert->priv->output_mode = GST_VIDEO_MULTIVIEW_MODE_NONE;
276 convert->priv->output_flags = GST_VIDEO_MULTIVIEW_FLAGS_NONE;
278 convert->input_mode_override = GST_VIDEO_MULTIVIEW_MODE_NONE;
279 convert->input_flags_override = GST_VIDEO_MULTIVIEW_FLAGS_NONE;
280 convert->output_mode_override = GST_VIDEO_MULTIVIEW_MODE_NONE;
281 convert->output_flags_override = GST_VIDEO_MULTIVIEW_FLAGS_NONE;
283 gst_video_info_init (&convert->in_info);
284 gst_video_info_init (&convert->out_info);
288 gst_gl_view_convert_finalize (GObject * object)
290 GstGLViewConvert *viewconvert;
292 viewconvert = GST_GL_VIEW_CONVERT (object);
294 gst_gl_view_convert_reset (viewconvert);
296 gst_buffer_replace (&viewconvert->priv->primary_in, NULL);
297 gst_buffer_replace (&viewconvert->priv->auxilliary_in, NULL);
298 gst_buffer_replace (&viewconvert->priv->primary_out, NULL);
299 gst_buffer_replace (&viewconvert->priv->auxilliary_out, NULL);
301 if (viewconvert->context) {
302 gst_object_unref (viewconvert->context);
303 viewconvert->context = NULL;
306 G_OBJECT_CLASS (gst_gl_view_convert_parent_class)->finalize (object);
310 * gst_gl_view_convert_new:
312 * Returns: (transfer full): a new #GstGLViewConvert
317 gst_gl_view_convert_new (void)
319 GstGLViewConvert *convert;
321 convert = g_object_new (GST_TYPE_GL_VIEW_CONVERT, NULL);
322 gst_object_ref_sink (convert);
328 _reset_gl (GstGLContext * context, GstGLViewConvert * viewconvert)
330 const GstGLFuncs *gl = context->gl_vtable;
332 if (viewconvert->priv->vao) {
333 gl->DeleteVertexArrays (1, &viewconvert->priv->vao);
334 viewconvert->priv->vao = 0;
337 if (viewconvert->priv->vertex_buffer) {
338 gl->DeleteBuffers (1, &viewconvert->priv->vertex_buffer);
339 viewconvert->priv->vertex_buffer = 0;
342 if (viewconvert->priv->vbo_indices) {
343 gl->DeleteBuffers (1, &viewconvert->priv->vbo_indices);
344 viewconvert->priv->vbo_indices = 0;
349 * gst_gl_view_convert_set_context:
350 * @viewconvert: a #GstGLViewConvert
351 * @context: the #GstGLContext to set
353 * Set @context on @viewconvert
358 gst_gl_view_convert_set_context (GstGLViewConvert * viewconvert,
359 GstGLContext * context)
361 GstGLContext *old_context = NULL;
363 g_return_if_fail (GST_IS_GL_VIEW_CONVERT (viewconvert));
365 GST_OBJECT_LOCK (viewconvert);
366 if (context != viewconvert->context) {
367 gst_gl_view_convert_reset (viewconvert);
368 if (viewconvert->context)
369 old_context = viewconvert->context;
370 viewconvert->context = context ? gst_object_ref (context) : NULL;
372 GST_OBJECT_UNLOCK (viewconvert);
374 gst_clear_object (&old_context);
378 _view_convert_set_format (GstGLViewConvert * viewconvert,
379 const GstVideoInfo * in_info, GstGLTextureTarget from_target,
380 const GstVideoInfo * out_info, GstGLTextureTarget to_target)
382 gboolean passthrough;
383 g_return_val_if_fail (GST_IS_GL_VIEW_CONVERT (viewconvert), FALSE);
385 if (gst_video_info_is_equal (in_info, &viewconvert->in_info) &&
386 gst_video_info_is_equal (out_info, &viewconvert->out_info) &&
387 viewconvert->from_texture_target == from_target &&
388 viewconvert->to_texture_target == to_target)
391 if (GST_VIDEO_INFO_FORMAT (in_info) != GST_VIDEO_FORMAT_RGBA ||
392 GST_VIDEO_INFO_FORMAT (out_info) != GST_VIDEO_FORMAT_RGBA) {
393 GST_ERROR_OBJECT (viewconvert,
394 "Multiview conversion can currently only be performed on RGBA textures");
398 passthrough = gst_video_info_is_equal (in_info, out_info) &&
399 from_target == to_target;
401 if (!passthrough && to_target != GST_GL_TEXTURE_TARGET_2D
402 && to_target != GST_GL_TEXTURE_TARGET_RECTANGLE)
405 /* FIXME: Compare what changed and decide if we need a full reset or not */
406 GST_OBJECT_LOCK (viewconvert);
407 gst_gl_view_convert_reset (viewconvert);
409 viewconvert->in_info = *in_info;
410 viewconvert->out_info = *out_info;
411 viewconvert->from_texture_target = from_target;
412 viewconvert->to_texture_target = to_target;
413 viewconvert->caps_passthrough = passthrough;
415 gst_buffer_replace (&viewconvert->priv->primary_in, NULL);
416 gst_buffer_replace (&viewconvert->priv->auxilliary_in, NULL);
417 gst_buffer_replace (&viewconvert->priv->primary_out, NULL);
418 gst_buffer_replace (&viewconvert->priv->auxilliary_out, NULL);
419 GST_OBJECT_UNLOCK (viewconvert);
425 * gst_gl_view_convert_set_caps:
426 * @viewconvert: a #GstGLViewConvert
427 * @in_caps: input #GstCaps
428 * @out_caps: output #GstCaps
430 * Initializes @viewconvert with the information required for conversion.
435 gst_gl_view_convert_set_caps (GstGLViewConvert * viewconvert,
436 GstCaps * in_caps, GstCaps * out_caps)
438 GstVideoInfo in_info, out_info;
439 GstCapsFeatures *in_features, *out_features;
440 GstGLTextureTarget from_target = GST_GL_TEXTURE_TARGET_2D;
441 GstGLTextureTarget to_target = GST_GL_TEXTURE_TARGET_2D;
443 g_return_val_if_fail (GST_IS_GL_VIEW_CONVERT (viewconvert), FALSE);
444 g_return_val_if_fail (GST_IS_CAPS (in_caps), FALSE);
445 g_return_val_if_fail (GST_IS_CAPS (out_caps), FALSE);
447 GST_INFO_OBJECT (viewconvert,
448 "Configuring multiview conversion from caps %" GST_PTR_FORMAT
449 " to %" GST_PTR_FORMAT, in_caps, out_caps);
451 in_features = gst_caps_get_features (in_caps, 0);
452 out_features = gst_caps_get_features (out_caps, 0);
454 if (!gst_caps_features_contains (in_features,
455 GST_CAPS_FEATURE_MEMORY_GL_MEMORY))
457 if (!gst_caps_features_contains (out_features,
458 GST_CAPS_FEATURE_MEMORY_GL_MEMORY))
461 if (!gst_video_info_from_caps (&in_info, in_caps))
463 if (!gst_video_info_from_caps (&out_info, out_caps))
467 GstStructure *in_s = gst_caps_get_structure (in_caps, 0);
468 GstStructure *out_s = gst_caps_get_structure (out_caps, 0);
470 if (gst_structure_has_field_typed (in_s, "texture-target", G_TYPE_STRING)) {
472 gst_gl_texture_target_from_string (gst_structure_get_string (in_s,
476 if (gst_structure_has_field_typed (out_s, "texture-target", G_TYPE_STRING)) {
478 gst_gl_texture_target_from_string (gst_structure_get_string (out_s,
482 if (to_target == GST_GL_TEXTURE_TARGET_NONE
483 || from_target == GST_GL_TEXTURE_TARGET_NONE)
488 return _view_convert_set_format (viewconvert, &in_info, from_target,
489 &out_info, to_target);
492 /* Function that can halve the value
493 * of ints, fractions, int/fraction ranges and lists of ints/fractions */
495 _halve_value (GValue * out, const GValue * in_value)
497 /* Fundamental fixed types first */
498 if (G_VALUE_HOLDS_INT (in_value)) {
499 g_value_init (out, G_TYPE_INT);
500 g_value_set_int (out, MAX (g_value_get_int (in_value) / 2, 1));
501 } else if (GST_VALUE_HOLDS_FRACTION (in_value)) {
503 num = gst_value_get_fraction_numerator (in_value);
504 den = gst_value_get_fraction_denominator (in_value);
505 g_value_init (out, GST_TYPE_FRACTION);
506 /* Don't adjust 'infinite' fractions */
507 if ((num != 1 || den != 2147483647) && (num != 2147483647 || den != 1)) {
508 /* FIXME - could do better approximation when den > G_MAXINT/2? */
509 den = den > G_MAXINT / 2 ? G_MAXINT : den * 2;
511 gst_value_set_fraction (out, num, den);
512 } else if (GST_VALUE_HOLDS_INT_RANGE (in_value)) {
513 gint range_min = gst_value_get_int_range_min (in_value);
514 gint range_max = gst_value_get_int_range_max (in_value);
515 gint range_step = gst_value_get_int_range_step (in_value);
516 g_value_init (out, GST_TYPE_INT_RANGE);
518 range_min = MAX (1, range_min / 2);
519 if (range_max != G_MAXINT)
520 range_max = MAX (1, range_max / 2);
521 gst_value_set_int_range_step (out, range_min,
522 range_max, MAX (1, range_step / 2));
523 } else if (GST_VALUE_HOLDS_FRACTION_RANGE (in_value)) {
524 GValue min_out = G_VALUE_INIT;
525 GValue max_out = G_VALUE_INIT;
526 const GValue *range_min = gst_value_get_fraction_range_min (in_value);
527 const GValue *range_max = gst_value_get_fraction_range_max (in_value);
528 _halve_value (&min_out, range_min);
529 _halve_value (&max_out, range_max);
530 g_value_init (out, GST_TYPE_FRACTION_RANGE);
531 gst_value_set_fraction_range (out, &min_out, &max_out);
532 g_value_unset (&min_out);
533 g_value_unset (&max_out);
534 } else if (GST_VALUE_HOLDS_LIST (in_value)) {
536 g_value_init (out, GST_TYPE_LIST);
537 for (i = 0; i < gst_value_list_get_size (in_value); i++) {
539 GValue tmp = G_VALUE_INIT;
541 entry = gst_value_list_get_value (in_value, i);
542 /* Random list values might not be the right type */
543 if (!_halve_value (&tmp, entry))
545 gst_value_list_append_and_take_value (out, &tmp);
557 static GstStructure *
558 _halve_structure_field (const GstStructure * in, const gchar * field_name)
561 const GValue *in_value = gst_structure_get_value (in, field_name);
562 GValue tmp = G_VALUE_INIT;
564 if (G_UNLIKELY (in_value == NULL))
565 return gst_structure_copy (in); /* Field doesn't exist, leave it as is */
567 if (!_halve_value (&tmp, in_value))
570 out = gst_structure_copy (in);
571 gst_structure_set_value (out, field_name, &tmp);
572 g_value_unset (&tmp);
577 /* Function that can double the value
578 * of ints, fractions, int/fraction ranges and lists of ints/fractions */
580 _double_value (GValue * out, const GValue * in_value)
582 /* Fundamental fixed types first */
583 if (G_VALUE_HOLDS_INT (in_value)) {
584 gint n = g_value_get_int (in_value);
585 g_value_init (out, G_TYPE_INT);
586 if (n <= G_MAXINT / 2)
587 g_value_set_int (out, n * 2);
589 g_value_set_int (out, G_MAXINT);
590 } else if (GST_VALUE_HOLDS_FRACTION (in_value)) {
592 num = gst_value_get_fraction_numerator (in_value);
593 den = gst_value_get_fraction_denominator (in_value);
594 g_value_init (out, GST_TYPE_FRACTION);
595 /* Don't adjust 'infinite' fractions */
596 if ((num != 1 || den != 2147483647) && (num != 2147483647 || den != 1)) {
597 /* FIXME - could do better approximation when num > G_MAXINT/2? */
598 num = num > G_MAXINT / 2 ? G_MAXINT : num * 2;
600 gst_value_set_fraction (out, num, den);
601 } else if (GST_VALUE_HOLDS_INT_RANGE (in_value)) {
602 gint range_min = gst_value_get_int_range_min (in_value);
603 gint range_max = gst_value_get_int_range_max (in_value);
604 gint range_step = gst_value_get_int_range_step (in_value);
605 if (range_min != 1) {
606 range_min = MIN (G_MAXINT / 2, range_min);
609 if (range_max != G_MAXINT) {
610 range_max = MIN (G_MAXINT / 2, range_max);
613 range_step = MIN (G_MAXINT / 2, range_step);
614 g_value_init (out, GST_TYPE_INT_RANGE);
615 gst_value_set_int_range_step (out, range_min, range_max, range_step);
616 } else if (GST_VALUE_HOLDS_FRACTION_RANGE (in_value)) {
617 GValue min_out = G_VALUE_INIT;
618 GValue max_out = G_VALUE_INIT;
619 const GValue *range_min = gst_value_get_fraction_range_min (in_value);
620 const GValue *range_max = gst_value_get_fraction_range_max (in_value);
621 _double_value (&min_out, range_min);
622 _double_value (&max_out, range_max);
623 g_value_init (out, GST_TYPE_FRACTION_RANGE);
624 gst_value_set_fraction_range (out, &min_out, &max_out);
625 g_value_unset (&min_out);
626 g_value_unset (&max_out);
627 } else if (GST_VALUE_HOLDS_LIST (in_value)) {
629 g_value_init (out, GST_TYPE_LIST);
630 for (i = 0; i < gst_value_list_get_size (in_value); i++) {
632 GValue tmp = G_VALUE_INIT;
634 entry = gst_value_list_get_value (in_value, i);
635 /* Random list values might not be the right type */
636 if (!_double_value (&tmp, entry))
638 gst_value_list_append_and_take_value (out, &tmp);
650 static GstStructure *
651 _double_structure_field (const GstStructure * in, const gchar * field_name)
654 const GValue *in_value = gst_structure_get_value (in, field_name);
655 GValue tmp = G_VALUE_INIT;
657 if (G_UNLIKELY (in_value == NULL))
658 return gst_structure_copy (in); /* Field doesn't exist, leave it as is */
660 if (!_double_value (&tmp, in_value))
663 out = gst_structure_copy (in);
664 gst_structure_set_value (out, field_name, &tmp);
665 g_value_unset (&tmp);
670 /* Return a copy of the caps with the requested field halved in value/range */
673 _halve_caps_field (const GstCaps * in, const gchar * field_name)
676 GstCaps *out = gst_caps_new_empty ();
678 for (i = 0; i < gst_caps_get_size (in); i++) {
679 const GstStructure *cur = gst_caps_get_structure (in, i);
680 GstCapsFeatures *f = gst_caps_get_features (in, i);
682 GstStructure *res = _halve_structure_field (cur, field_name);
684 gst_caps_merge_structure_full (out, res,
685 f ? gst_caps_features_copy (f) : NULL);
692 /* Return a copy of the caps with the requested field doubled in value/range */
694 _double_caps_field (const GstCaps * in, const gchar * field_name)
697 GstCaps *out = gst_caps_new_empty ();
699 for (i = 0; i < gst_caps_get_size (in); i++) {
700 const GstStructure *cur = gst_caps_get_structure (in, i);
701 GstCapsFeatures *f = gst_caps_get_features (in, i);
703 GstStructure *res = _double_structure_field (cur, field_name);
705 gst_caps_merge_structure_full (out, res,
706 f ? gst_caps_features_copy (f) : NULL);
712 /* Takes ownership of the input caps */
714 _expand_par_for_half_aspect (GstCaps * in, gboolean vertical_half_aspect)
717 guint mview_flags, mview_flags_mask;
721 out = gst_caps_new_empty ();
723 while (gst_caps_get_size (in) > 0) {
725 GstCapsFeatures *features;
727 features = gst_caps_get_features (in, 0);
729 features = gst_caps_features_copy (features);
731 s = gst_caps_steal_structure (in, 0);
733 if (!gst_structure_get_flagset (s, "multiview-flags", &mview_flags,
734 &mview_flags_mask)) {
735 gst_caps_append_structure_full (out, s, features);
738 /* If the input doesn't care about the half-aspect flag, allow current PAR in either variant */
739 if ((mview_flags_mask & GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT) == 0) {
740 gst_caps_append_structure_full (out, s, features);
743 if (!gst_structure_has_field (s, "pixel-aspect-ratio")) {
744 /* No par field, dont-care the half-aspect flag */
745 gst_structure_set (s, "multiview-flags",
746 GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
747 mview_flags & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT,
748 mview_flags_mask & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT, NULL);
749 gst_caps_append_structure_full (out, s, features);
753 /* Halve or double PAR base on inputs input specified. */
755 /* Append a copy with the half-aspect flag as-is */
756 tmp = gst_structure_copy (s);
757 out = gst_caps_merge_structure_full (out, tmp,
758 features ? gst_caps_features_copy (features) : NULL);
760 /* and then a copy inverted */
761 if (mview_flags & GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT) {
762 /* Input is half-aspect. Double/halve the PAR, clear the flag */
763 if (vertical_half_aspect)
764 tmp = _halve_structure_field (s, "pixel-aspect-ratio");
766 tmp = _double_structure_field (s, "pixel-aspect-ratio");
768 gst_structure_set (tmp, "multiview-flags",
769 GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
770 mview_flags & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT,
771 mview_flags_mask | GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT, NULL);
773 if (vertical_half_aspect)
774 tmp = _double_structure_field (s, "pixel-aspect-ratio");
776 tmp = _halve_structure_field (s, "pixel-aspect-ratio");
778 gst_structure_set (tmp, "multiview-flags",
779 GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
780 mview_flags | GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT,
781 mview_flags_mask | GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT, NULL);
784 out = gst_caps_merge_structure_full (out, tmp,
785 features ? gst_caps_features_copy (features) : NULL);
787 gst_structure_free (s);
789 gst_caps_features_free (features);
797 /* If input supports top-bottom or row-interleaved, we may halve height to mono frames.
798 * If input supports left-right, checkerboard, quincunx or column-interleaved,
799 * we may halve width to mono frames.
800 * For output of top-bottom or row-interleaved, we may double the mono height
801 * For output of left-right, checkerboard, quincunx or column-interleaved,
802 * we may double the mono width.
803 * In all cases, if input has half-aspect and output does not, we may double the PAR
804 * And if input does *not* have half-aspect flag and output does not, we may halve the PAR
807 _expand_structure (GstGLViewConvert * viewconvert,
808 GstCaps * out_caps, GstStructure * structure, GstCapsFeatures * features)
810 GstCaps *expanded_caps, *tmp;
812 const gchar *default_mview_mode_str = NULL;
813 guint mview_flags, mview_flags_mask;
814 const GValue *in_modes;
817 /* Empty caps to accumulate into */
818 expanded_caps = gst_caps_new_empty ();
820 /* First, set defaults if multiview flags are missing */
821 default_mview_mode_str =
822 gst_video_multiview_mode_to_caps_string (GST_VIDEO_MULTIVIEW_MODE_MONO);
824 mview_flags = GST_VIDEO_MULTIVIEW_FLAGS_NONE;
825 mview_flags_mask = GST_FLAG_SET_MASK_EXACT;
827 if (!gst_structure_has_field (structure, "multiview-mode")) {
828 gst_structure_set (structure,
829 "multiview-mode", G_TYPE_STRING, default_mview_mode_str, NULL);
831 if (!gst_structure_has_field (structure, "multiview-flags")) {
832 gst_structure_set (structure,
833 "multiview-flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGSET, mview_flags,
834 mview_flags_mask, NULL);
836 gst_structure_get_flagset (structure, "multiview-flags",
837 &mview_flags, &mview_flags_mask);
840 in_modes = gst_structure_get_value (structure, "multiview-mode");
841 mono_caps = gst_caps_new_empty ();
842 if (gst_value_intersect (NULL, in_modes,
843 gst_video_multiview_get_mono_modes ())) {
844 GstStructure *new_struct = gst_structure_copy (structure);
845 gst_structure_set_value (new_struct, "multiview-mode",
846 gst_video_multiview_get_mono_modes ());
847 /* Half-aspect makes no sense for mono or unpacked, get rid of it */
848 if (mview_flags & GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT) {
849 gst_structure_set (new_struct, "multiview-flags",
850 GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
851 mview_flags & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT,
852 mview_flags_mask & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT, NULL);
854 gst_caps_append_structure_full (mono_caps, new_struct,
855 features ? gst_caps_features_copy (features) : NULL);
857 if (gst_value_intersect (NULL, in_modes,
858 gst_video_multiview_get_unpacked_modes ())) {
859 GstStructure *new_struct = gst_structure_copy (structure);
861 gst_structure_set_value (new_struct, "multiview-mode",
862 gst_video_multiview_get_mono_modes ());
864 /* Half-aspect makes no sense for mono or unpacked, get rid of it */
865 if (mview_flags & GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT) {
866 gst_structure_set (new_struct, "multiview-flags",
867 GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
868 mview_flags & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT,
869 mview_flags_mask & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT, NULL);
871 gst_caps_append_structure_full (mono_caps, new_struct,
872 features ? gst_caps_features_copy (features) : NULL);
875 if (gst_value_intersect (NULL, in_modes,
876 gst_video_multiview_get_doubled_height_modes ())) {
877 /* Append mono formats with height halved */
878 GstStructure *new_struct = _halve_structure_field (structure, "height");
879 gst_structure_set_value (new_struct, "multiview-mode",
880 gst_video_multiview_get_mono_modes ());
881 /* Normalise the half-aspect flag away */
882 if (mview_flags & GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT) {
884 _halve_structure_field (new_struct, "pixel-aspect-ratio");
885 gst_structure_set (structure, "multiview-flags",
886 GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
887 mview_flags & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT,
888 mview_flags_mask | GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT, NULL);
889 gst_structure_free (new_struct);
892 mono_caps = gst_caps_merge_structure_full (mono_caps, new_struct,
893 features ? gst_caps_features_copy (features) : NULL);
895 if (gst_value_intersect (NULL, in_modes,
896 gst_video_multiview_get_doubled_width_modes ())) {
897 /* Append mono formats with width halved */
898 GstStructure *new_struct = _halve_structure_field (structure, "width");
899 gst_structure_set_value (new_struct, "multiview-mode",
900 gst_video_multiview_get_mono_modes ());
901 /* Normalise the half-aspect flag away */
902 if (mview_flags & GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT) {
904 _double_structure_field (new_struct, "pixel-aspect-ratio");
905 gst_structure_set (structure, "multiview-flags",
906 GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
907 mview_flags & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT,
908 mview_flags_mask | GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT, NULL);
909 gst_structure_free (new_struct);
912 mono_caps = gst_caps_merge_structure_full (mono_caps, new_struct,
913 features ? gst_caps_features_copy (features) : NULL);
915 if (gst_value_intersect (NULL, in_modes,
916 gst_video_multiview_get_doubled_size_modes ())) {
917 /* Append checkerboard/doubled size formats with width & height halved */
918 GstStructure *new_struct_w = _halve_structure_field (structure, "width");
919 GstStructure *new_struct_wh =
920 _halve_structure_field (new_struct_w, "height");
921 gst_structure_free (new_struct_w);
922 gst_structure_set_value (new_struct_wh, "multiview-mode",
923 gst_video_multiview_get_mono_modes ());
924 mono_caps = gst_caps_merge_structure_full (mono_caps, new_struct_wh,
925 features ? gst_caps_features_copy (features) : NULL);
928 /* Everything is normalised now, unset the flags we can change */
929 /* Remove the views field, as these are all 'mono' modes
930 * Need to do this before we expand caps back out to frame packed modes */
931 for (i = 0; i < gst_caps_get_size (mono_caps); i++) {
932 GstStructure *s = gst_caps_get_structure (mono_caps, i);
933 gst_structure_remove_fields (s, "views", NULL);
934 if (gst_structure_get_flagset (s, "multiview-flags", &mview_flags,
935 &mview_flags_mask)) {
936 /* Preserve only the half-aspect and mixed-mono flags, for now.
937 * The rest we can change */
939 (GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT |
940 GST_VIDEO_MULTIVIEW_FLAGS_MIXED_MONO);
941 gst_structure_set (s, "multiview-flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
942 mview_flags, mview_flags_mask, NULL);
946 GST_TRACE_OBJECT (viewconvert,
947 "Collected single-view caps %" GST_PTR_FORMAT, mono_caps);
948 /* Put unpacked and mono modes first. We don't care about flags. Clear them */
949 tmp = gst_caps_copy (mono_caps);
950 for (i = 0; i < gst_caps_get_size (tmp); i++) {
951 GstStructure *s = gst_caps_get_structure (tmp, i);
952 gst_structure_remove_fields (s, "views", NULL);
953 if (gst_structure_get_flagset (s, "multiview-flags", &mview_flags,
954 &mview_flags_mask)) {
955 /* We can change any flags for mono modes - half-aspect and mixed-mono have no meaning */
956 mview_flags_mask = 0;
957 gst_structure_set (s, "multiview-flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
958 mview_flags, mview_flags_mask, NULL);
961 expanded_caps = gst_caps_merge (expanded_caps, tmp);
963 /* Unpacked output modes have 2 views, for now */
964 tmp = gst_caps_copy (mono_caps);
965 gst_caps_set_value (tmp, "multiview-mode",
966 gst_video_multiview_get_unpacked_modes ());
967 for (i = 0; i < gst_caps_get_size (tmp); i++) {
968 GstStructure *s = gst_caps_get_structure (tmp, i);
969 gst_structure_set (s, "views", G_TYPE_INT, 2, NULL);
970 if (gst_structure_get_flagset (s, "multiview-flags", &mview_flags,
971 &mview_flags_mask)) {
972 /* We can change any flags for unpacked modes - half-aspect and mixed-mono have no meaning */
973 mview_flags_mask = 0;
974 gst_structure_set (s, "multiview-flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
975 mview_flags, mview_flags_mask, NULL);
978 expanded_caps = gst_caps_merge (expanded_caps, tmp);
980 /* Double height output modes */
981 tmp = _double_caps_field (mono_caps, "height");
982 gst_caps_set_value (tmp, "multiview-mode",
983 gst_video_multiview_get_doubled_height_modes ());
984 tmp = _expand_par_for_half_aspect (tmp, TRUE);
986 expanded_caps = gst_caps_merge (expanded_caps, tmp);
988 /* Double width output modes */
989 tmp = _double_caps_field (mono_caps, "width");
990 gst_caps_set_value (tmp, "multiview-mode",
991 gst_video_multiview_get_doubled_width_modes ());
992 tmp = _expand_par_for_half_aspect (tmp, FALSE);
994 expanded_caps = gst_caps_merge (expanded_caps, tmp);
996 /* Double size output modes */
998 GstCaps *tmp_w = _double_caps_field (mono_caps, "width");
999 tmp = _double_caps_field (tmp_w, "height");
1000 gst_caps_unref (tmp_w);
1001 gst_caps_set_value (tmp, "multiview-mode",
1002 gst_video_multiview_get_doubled_size_modes ());
1003 expanded_caps = gst_caps_merge (expanded_caps, tmp);
1006 /* We're done with the mono caps now */
1007 gst_caps_unref (mono_caps);
1009 GST_TRACE_OBJECT (viewconvert,
1010 "expanded transform caps now %" GST_PTR_FORMAT, expanded_caps);
1012 if (gst_caps_is_empty (expanded_caps)) {
1013 gst_caps_unref (expanded_caps);
1016 /* Really, we can rescale - so at this point we can append full-range
1017 * height/width/PAR as an unpreferred final option. */
1018 tmp = gst_caps_copy (expanded_caps);
1019 gst_caps_set_simple (tmp, "width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
1020 "height", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL);
1022 out_caps = gst_caps_merge (out_caps, expanded_caps);
1023 out_caps = gst_caps_merge (out_caps, tmp);
1028 _intersect_with_mview_mode (GstCaps * caps,
1029 GstVideoMultiviewMode mode, GstVideoMultiviewFlags flags)
1031 GstCaps *filter, *result;
1032 const gchar *caps_str;
1034 caps_str = gst_video_multiview_mode_to_caps_string (mode);
1036 filter = gst_caps_new_simple ("video/x-raw",
1037 "multiview-mode", G_TYPE_STRING,
1038 caps_str, "multiview-flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGSET, flags,
1039 GST_FLAG_SET_MASK_EXACT, NULL);
1041 if (mode == GST_VIDEO_MULTIVIEW_MODE_SEPARATED ||
1042 mode == GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME)
1043 gst_caps_set_simple (filter, "views", G_TYPE_INT, 2, NULL);
1045 gst_caps_set_features (filter, 0, gst_caps_features_new_any ());
1047 GST_DEBUG ("Intersecting target caps %" GST_PTR_FORMAT
1048 " with caps %" GST_PTR_FORMAT, caps, filter);
1050 result = gst_caps_intersect_full (caps, filter, GST_CAPS_INTERSECT_FIRST);
1051 gst_caps_unref (filter);
1056 _intersect_with_mview_modes (GstCaps * caps, const GValue * modes)
1058 GstCaps *filter, *result;
1060 filter = gst_caps_new_empty_simple ("video/x-raw");
1062 gst_caps_set_value (filter, "multiview-mode", modes);
1063 gst_caps_set_features (filter, 0, gst_caps_features_new_any ());
1065 GST_DEBUG ("Intersecting target caps %" GST_PTR_FORMAT
1066 " with caps %" GST_PTR_FORMAT, caps, filter);
1068 result = gst_caps_intersect_full (caps, filter, GST_CAPS_INTERSECT_FIRST);
1069 gst_caps_unref (filter);
1074 * gst_gl_view_convert_transform_caps:
1075 * @viewconvert: a #GstGLViewConvert
1076 * @direction: a #GstPadDirection
1077 * @caps: (transfer none): the #GstCaps to transform
1078 * @filter: (transfer none): a set of filter #GstCaps
1080 * Provides an implementation of #GstBaseTransformClass.transform_caps()
1082 * Returns: (transfer full): the converted #GstCaps
1087 gst_gl_view_convert_transform_caps (GstGLViewConvert * viewconvert,
1088 GstPadDirection direction, GstCaps * caps, GstCaps * filter)
1091 GstCaps *base_caps = gst_static_caps_get (&caps_template);
1092 GstCaps *out_caps, *tmp_caps;
1094 g_return_val_if_fail (GST_IS_GL_VIEW_CONVERT (viewconvert), NULL);
1096 GST_DEBUG_OBJECT (viewconvert, "Direction %s "
1097 "input caps %" GST_PTR_FORMAT " filter %" GST_PTR_FORMAT,
1098 direction == GST_PAD_SINK ? "sink" : "src", caps, filter);
1100 /* We can only process GLmemory RGBA caps, start from that */
1101 caps = gst_caps_intersect (caps, base_caps);
1102 gst_caps_unref (base_caps);
1104 /* Change input/output to the formats we can convert to/from,
1105 * but keep the original caps at the start - we will always prefer
1107 if (direction == GST_PAD_SINK) {
1108 out_caps = gst_caps_copy (caps);
1109 if (viewconvert->input_mode_override != GST_VIDEO_MULTIVIEW_MODE_NONE) {
1110 GstVideoMultiviewMode mode = viewconvert->input_mode_override;
1111 GstVideoMultiviewFlags flags = viewconvert->input_flags_override;
1113 const gchar *caps_str = gst_video_multiview_mode_to_caps_string (mode);
1114 /* Coerce the input caps before transforming, so the sizes come out right */
1115 gst_caps_set_simple (out_caps, "multiview-mode", G_TYPE_STRING,
1116 caps_str, "multiview-flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGSET, flags,
1117 GST_FLAG_SET_MASK_EXACT, NULL);
1120 out_caps = gst_caps_new_empty ();
1123 for (i = 0; i < gst_caps_get_size (caps); i++) {
1124 GstStructure *structure = gst_caps_get_structure (caps, i);
1125 GstCapsFeatures *features = gst_caps_get_features (caps, i);
1126 out_caps = _expand_structure (viewconvert, out_caps, structure, features);
1129 if (gst_caps_is_empty (out_caps))
1132 /* If we have an output mode override, limit things to that */
1133 if (direction == GST_PAD_SINK &&
1134 viewconvert->output_mode_override != GST_VIDEO_MULTIVIEW_MODE_NONE) {
1136 tmp_caps = _intersect_with_mview_mode (out_caps,
1137 viewconvert->output_mode_override, viewconvert->output_flags_override);
1139 gst_caps_unref (out_caps);
1140 out_caps = tmp_caps;
1141 } else if (viewconvert->input_mode_override != GST_VIDEO_MULTIVIEW_MODE_NONE) {
1142 /* Prepend a copy of our preferred input caps in case the peer
1143 * can handle them */
1144 tmp_caps = _intersect_with_mview_mode (out_caps,
1145 viewconvert->input_mode_override, viewconvert->input_flags_override);
1146 out_caps = gst_caps_merge (out_caps, tmp_caps);
1148 if (direction == GST_PAD_SRC) {
1150 /* When generating input caps, we also need a copy of the mono caps
1151 * without multiview-mode or flags for backwards compat, at the end */
1152 tmp_caps = _intersect_with_mview_mode (caps,
1153 GST_VIDEO_MULTIVIEW_MODE_MONO, GST_VIDEO_MULTIVIEW_FLAGS_NONE);
1154 if (!gst_caps_is_empty (tmp_caps)) {
1155 s = gst_caps_get_structure (tmp_caps, 0);
1156 gst_structure_remove_fields (s, "multiview-mode", "multiview-flags",
1158 out_caps = gst_caps_merge (out_caps, tmp_caps);
1160 gst_caps_unref (tmp_caps);
1163 gst_caps_unref (caps);
1165 n = gst_caps_get_size (out_caps);
1166 for (i = 0; i < n; i++) {
1167 GstStructure *s = gst_caps_get_structure (out_caps, i);
1169 gst_structure_remove_fields (s, "texture-target", NULL);
1172 GST_DEBUG_OBJECT (viewconvert, "Returning caps %" GST_PTR_FORMAT, out_caps);
1177 _get_target_bitmask_from_g_value (const GValue * targets)
1179 guint new_targets = 0;
1181 if (targets == NULL) {
1182 new_targets = 1 << GST_GL_TEXTURE_TARGET_2D;
1183 } else if (G_TYPE_CHECK_VALUE_TYPE (targets, G_TYPE_STRING)) {
1184 GstGLTextureTarget target;
1187 str = g_value_get_string (targets);
1188 target = gst_gl_texture_target_from_string (str);
1191 new_targets |= 1 << target;
1192 } else if (G_TYPE_CHECK_VALUE_TYPE (targets, GST_TYPE_LIST)) {
1195 m = gst_value_list_get_size (targets);
1196 for (j = 0; j < m; j++) {
1197 const GValue *val = gst_value_list_get_value (targets, j);
1198 GstGLTextureTarget target;
1201 str = g_value_get_string (val);
1202 target = gst_gl_texture_target_from_string (str);
1204 new_targets |= 1 << target;
1212 _fixate_texture_target (GstGLViewConvert * viewconvert,
1213 GstPadDirection direction, GstCaps * caps, GstCaps * other)
1215 GValue item = G_VALUE_INIT;
1216 const GValue *targets, *other_targets;
1217 guint targets_mask = 0, other_targets_mask = 0, result_mask;
1218 GstStructure *s, *s_other;
1220 other = gst_caps_make_writable (other);
1221 s = gst_caps_get_structure (caps, 0);
1222 s_other = gst_caps_get_structure (other, 0);
1224 other_targets = gst_structure_get_value (s_other, "texture-target");
1225 targets = gst_structure_get_value (s, "texture-target");
1227 targets_mask = _get_target_bitmask_from_g_value (targets);
1228 other_targets_mask = _get_target_bitmask_from_g_value (other_targets);
1230 result_mask = targets_mask & other_targets_mask;
1231 if (result_mask == 0) {
1232 /* nothing we can do here */
1233 return gst_caps_fixate (other);
1236 if (direction == GST_PAD_SINK) {
1238 (1 << GST_GL_TEXTURE_TARGET_2D | 1 << GST_GL_TEXTURE_TARGET_RECTANGLE);
1240 /* if the src caps has 2D support we can 'convert' to anything */
1241 if (targets_mask & (1 << GST_GL_TEXTURE_TARGET_2D))
1244 result_mask = other_targets_mask;
1247 g_value_init (&item, G_TYPE_STRING);
1248 if (result_mask & (1 << GST_GL_TEXTURE_TARGET_2D)) {
1249 g_value_set_static_string (&item, GST_GL_TEXTURE_TARGET_2D_STR);
1250 } else if (result_mask & (1 << GST_GL_TEXTURE_TARGET_RECTANGLE)) {
1251 g_value_set_static_string (&item, GST_GL_TEXTURE_TARGET_RECTANGLE_STR);
1252 } else if (result_mask & (1 << GST_GL_TEXTURE_TARGET_EXTERNAL_OES)) {
1253 g_value_set_static_string (&item, GST_GL_TEXTURE_TARGET_EXTERNAL_OES_STR);
1256 gst_structure_set_value (s_other, "texture-target", &item);
1258 g_value_unset (&item);
1260 return gst_caps_fixate (other);
1264 * gst_gl_view_convert_fixate_caps:
1265 * @viewconvert: a #GstGLViewConvert
1266 * @direction: a #GstPadDirection
1267 * @caps: (transfer none): the #GstCaps of @direction
1268 * @othercaps: (transfer full): the #GstCaps to fixate
1270 * Provides an implementation of #GstBaseTransformClass.fixate_caps()
1272 * Returns: (transfer full): the fixated #GstCaps
1277 gst_gl_view_convert_fixate_caps (GstGLViewConvert * viewconvert,
1278 GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
1280 GstVideoMultiviewMode mode = viewconvert->output_mode_override;
1281 GstVideoMultiviewFlags flags = viewconvert->output_flags_override;
1284 g_return_val_if_fail (GST_IS_GL_VIEW_CONVERT (viewconvert), NULL);
1286 othercaps = gst_caps_make_writable (othercaps);
1287 GST_LOG_OBJECT (viewconvert, "dir %s fixating %" GST_PTR_FORMAT
1288 " against caps %" GST_PTR_FORMAT,
1289 direction == GST_PAD_SINK ? "sink" : "src", othercaps, caps);
1291 if (direction == GST_PAD_SINK) {
1292 if (mode != GST_VIDEO_MULTIVIEW_MODE_NONE) {
1293 /* We have a requested output mode and are fixating source caps, try and enforce it */
1294 tmp = _intersect_with_mview_mode (othercaps, mode, flags);
1295 gst_caps_unref (othercaps);
1298 /* See if we can do passthrough */
1301 if (gst_video_info_from_caps (&info, caps)) {
1302 GstVideoMultiviewMode mode = GST_VIDEO_INFO_MULTIVIEW_MODE (&info);
1303 GstVideoMultiviewFlags flags = GST_VIDEO_INFO_MULTIVIEW_FLAGS (&info);
1305 if (viewconvert->input_mode_override != GST_VIDEO_MULTIVIEW_MODE_NONE) {
1306 mode = viewconvert->input_mode_override;
1307 flags = viewconvert->input_flags_override;
1310 tmp = _intersect_with_mview_mode (othercaps, mode, flags);
1311 if (gst_caps_is_empty (tmp)) {
1312 /* Nope, we can't pass our input caps downstream */
1313 gst_caps_unref (tmp);
1315 gst_caps_unref (othercaps);
1321 /* Prefer an unpacked mode for output */
1323 _intersect_with_mview_modes (othercaps,
1324 gst_video_multiview_get_unpacked_modes ());
1325 if (!gst_caps_is_empty (tmp)) {
1326 gst_caps_unref (othercaps);
1329 gst_caps_unref (tmp);
1332 } else if (viewconvert->input_mode_override != GST_VIDEO_MULTIVIEW_MODE_NONE) {
1333 /* See if we can coerce the caps into matching input mode/flags,
1334 * in case it doesn't care at all, but allow it not to too */
1335 mode = viewconvert->input_mode_override;
1336 flags = viewconvert->input_flags_override;
1337 tmp = _intersect_with_mview_mode (othercaps, mode, flags);
1338 if (gst_caps_is_empty (tmp)) {
1339 /* Nope, we can pass our input caps downstream */
1340 gst_caps_unref (tmp);
1342 gst_caps_unref (othercaps);
1347 othercaps = _fixate_texture_target (viewconvert, direction, caps, othercaps);
1350 GST_DEBUG_OBJECT (viewconvert, "dir %s fixated to %" GST_PTR_FORMAT
1351 " against caps %" GST_PTR_FORMAT,
1352 direction == GST_PAD_SINK ? "sink" : "src", othercaps, caps);
1357 * gst_gl_view_convert_reset:
1358 * @viewconvert: a #GstGLViewConvert
1360 * Reset @viewconvert to the default state. Further operation will require
1361 * setting the caps with gst_gl_view_convert_set_caps().
1366 gst_gl_view_convert_reset (GstGLViewConvert * viewconvert)
1368 g_return_if_fail (GST_IS_GL_VIEW_CONVERT (viewconvert));
1370 gst_clear_object (&viewconvert->shader);
1371 gst_clear_object (&viewconvert->fbo);
1373 if (viewconvert->context) {
1374 gst_gl_context_thread_add (viewconvert->context,
1375 (GstGLContextThreadFunc) _reset_gl, viewconvert);
1378 viewconvert->initted = FALSE;
1379 viewconvert->reconfigure = FALSE;
1383 gst_gl_view_convert_set_property (GObject * object, guint prop_id,
1384 const GValue * value, GParamSpec * pspec)
1386 GstGLViewConvert *convert = GST_GL_VIEW_CONVERT (object);
1388 case PROP_INPUT_LAYOUT:
1389 convert->input_mode_override = g_value_get_enum (value);
1391 case PROP_INPUT_FLAGS:
1392 convert->input_flags_override = g_value_get_flags (value);
1394 case PROP_OUTPUT_LAYOUT:
1395 convert->output_mode_override = g_value_get_enum (value);
1397 case PROP_OUTPUT_FLAGS:
1398 convert->output_flags_override = g_value_get_flags (value);
1400 case PROP_OUTPUT_DOWNMIX_MODE:
1401 convert->downmix_mode = g_value_get_enum (value);
1404 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1407 GST_OBJECT_LOCK (convert);
1408 convert->reconfigure = TRUE;
1409 GST_OBJECT_UNLOCK (convert);
1413 gst_gl_view_convert_get_property (GObject * object, guint prop_id,
1414 GValue * value, GParamSpec * pspec)
1416 GstGLViewConvert *convert = GST_GL_VIEW_CONVERT (object);
1418 case PROP_INPUT_LAYOUT:
1419 g_value_set_enum (value, convert->input_mode_override);
1421 case PROP_INPUT_FLAGS:
1422 g_value_set_flags (value, convert->input_flags_override);
1424 case PROP_OUTPUT_LAYOUT:
1425 g_value_set_enum (value, convert->output_mode_override);
1427 case PROP_OUTPUT_FLAGS:
1428 g_value_set_flags (value, convert->output_flags_override);
1430 case PROP_OUTPUT_DOWNMIX_MODE:
1431 g_value_set_enum (value, convert->downmix_mode);
1434 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1440 * gst_gl_view_convert_perform:
1441 * @viewconvert: a #GstGLViewConvert
1442 * @inbuf: (transfer none): the #GstGLMemory filled #GstBuffer to convert
1444 * Converts the data contained by @inbuf using the formats specified by the
1445 * #GstCaps passed to gst_gl_view_convert_set_caps()
1447 * Returns: (transfer full) (nullable): a converted #GstBuffer or %NULL
1452 gst_gl_view_convert_perform (GstGLViewConvert * viewconvert, GstBuffer * inbuf)
1456 if (gst_gl_view_convert_submit_input_buffer (viewconvert,
1457 GST_BUFFER_IS_DISCONT (inbuf), gst_buffer_ref (inbuf)) != GST_FLOW_OK)
1459 if (gst_gl_view_convert_get_output (viewconvert, &out) != GST_FLOW_OK)
1465 /* called by _init_convert (in the gl thread) */
1467 _init_view_convert_fbo (GstGLViewConvert * viewconvert)
1469 guint out_width, out_height;
1471 out_width = GST_VIDEO_INFO_WIDTH (&viewconvert->out_info);
1472 out_height = GST_VIDEO_INFO_HEIGHT (&viewconvert->out_info);
1475 gst_gl_framebuffer_new_with_default_depth (viewconvert->context,
1476 out_width, out_height);
1478 return viewconvert->fbo != NULL;
1481 /* free after use */
1483 _get_shader_string (GstGLViewConvert * viewconvert, GstGLShader * shader,
1484 GstVideoMultiviewMode in_mode, GstVideoMultiviewMode out_mode,
1485 GstGLSLVersion version, GstGLSLProfile profile)
1487 const gchar *input_str, *output_str;
1488 gboolean mono_input = FALSE;
1490 GString *str = g_string_new (NULL);
1491 guint n_outputs = 1;
1494 case GST_VIDEO_MULTIVIEW_MODE_NONE:
1495 case GST_VIDEO_MULTIVIEW_MODE_MONO:
1496 case GST_VIDEO_MULTIVIEW_MODE_LEFT:
1497 case GST_VIDEO_MULTIVIEW_MODE_RIGHT:
1501 input_str = frag_input;
1506 case GST_VIDEO_MULTIVIEW_MODE_LEFT:
1507 output_str = frag_output_left;
1509 case GST_VIDEO_MULTIVIEW_MODE_RIGHT:
1510 output_str = frag_output_right;
1512 case GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE_QUINCUNX:
1513 /* FIXME: implement properly with sub-sampling */
1514 case GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE:
1515 output_str = frag_output_side_by_side;
1517 case GST_VIDEO_MULTIVIEW_MODE_TOP_BOTTOM:
1518 output_str = frag_output_top_bottom;
1520 case GST_VIDEO_MULTIVIEW_MODE_COLUMN_INTERLEAVED:
1521 output_str = frag_output_column_interleaved;
1523 case GST_VIDEO_MULTIVIEW_MODE_ROW_INTERLEAVED:
1524 output_str = frag_output_row_interleaved;
1526 case GST_VIDEO_MULTIVIEW_MODE_SEPARATED:
1527 case GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME:
1528 output_str = frag_output_separated;
1531 case GST_VIDEO_MULTIVIEW_MODE_CHECKERBOARD:
1532 output_str = frag_output_checkerboard;
1534 case GST_VIDEO_MULTIVIEW_MODE_NONE:
1535 case GST_VIDEO_MULTIVIEW_MODE_MONO:
1538 output_str = frag_output_left;
1540 output_str = frag_output_downmix;
1544 if (viewconvert->from_texture_target == GST_GL_TEXTURE_TARGET_EXTERNAL_OES)
1545 g_string_append (str, glsl_OES_extension_string);
1547 g_string_append (str,
1548 gst_gl_shader_string_get_highest_precision (viewconvert->context, version,
1550 g_string_append (str, fragment_header);
1552 /* GL 3.3+ and GL ES 3.x */
1553 if ((profile == GST_GLSL_PROFILE_CORE && version >= GST_GLSL_VERSION_330)
1554 || (profile == GST_GLSL_PROFILE_ES && version >= GST_GLSL_VERSION_300)) {
1555 if (n_outputs > 1) {
1558 for (i = 0; i < n_outputs; i++) {
1559 g_string_append_printf (str,
1560 "layout(location = %d) out vec4 fragColor_%d;\n", i, i);
1563 g_string_append (str, "layout (location = 0) out vec4 fragColor;\n");
1565 } else if (profile == GST_GLSL_PROFILE_CORE
1566 && version >= GST_GLSL_VERSION_150) {
1567 /* no layout specifiers, use glBindFragDataLocation instead */
1568 if (n_outputs > 1) {
1571 for (i = 0; i < n_outputs; i++) {
1572 gchar *var_name = g_strdup_printf ("fragColor_%d", i);
1573 g_string_append_printf (str, "out vec4 %s;\n", var_name);
1574 gst_gl_shader_bind_frag_data_location (shader, i, var_name);
1578 g_string_append (str, "out vec4 fragColor;\n");
1579 gst_gl_shader_bind_frag_data_location (shader, 0, "fragColor");
1584 const gchar *varying = NULL;
1586 if ((profile == GST_GLSL_PROFILE_ES && version >= GST_GLSL_VERSION_300)
1587 || (profile == GST_GLSL_PROFILE_CORE
1588 && version >= GST_GLSL_VERSION_150)) {
1591 varying = "varying";
1593 g_string_append_printf (str,
1594 "\n%s vec2 v_texcoord;\nvoid main() {\nvec4 l, r;\n", varying);
1597 g_string_append (str, input_str);
1598 g_string_append (str, output_str);
1599 g_string_append (str, "\n}");
1600 tmp = g_string_free (str, FALSE);
1603 _gst_glsl_mangle_shader (tmp, GL_FRAGMENT_SHADER,
1604 GST_GL_TEXTURE_TARGET_2D, viewconvert->from_texture_target,
1605 viewconvert->context, &version, &profile);
1611 _bind_buffer (GstGLViewConvert * viewconvert)
1613 const GstGLFuncs *gl = viewconvert->context->gl_vtable;
1614 gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, viewconvert->priv->vbo_indices);
1615 gl->BindBuffer (GL_ARRAY_BUFFER, viewconvert->priv->vertex_buffer);
1616 /* Load the vertex position */
1617 gl->VertexAttribPointer (viewconvert->priv->attr_position, 3, GL_FLOAT,
1618 GL_FALSE, 5 * sizeof (GLfloat), (void *) 0);
1619 /* Load the texture coordinate */
1620 gl->VertexAttribPointer (viewconvert->priv->attr_texture, 2, GL_FLOAT,
1621 GL_FALSE, 5 * sizeof (GLfloat), (void *) (3 * sizeof (GLfloat)));
1622 gl->EnableVertexAttribArray (viewconvert->priv->attr_position);
1623 gl->EnableVertexAttribArray (viewconvert->priv->attr_texture);
1627 _unbind_buffer (GstGLViewConvert * viewconvert)
1629 const GstGLFuncs *gl = viewconvert->context->gl_vtable;
1630 gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
1631 gl->BindBuffer (GL_ARRAY_BUFFER, 0);
1632 gl->DisableVertexAttribArray (viewconvert->priv->attr_position);
1633 gl->DisableVertexAttribArray (viewconvert->priv->attr_texture);
1636 /* Called in the gl thread */
1638 _init_view_convert (GstGLViewConvert * viewconvert)
1640 GstGLViewConvertPrivate *priv = viewconvert->priv;
1641 GstVideoMultiviewMode in_mode = priv->input_mode;
1642 GstVideoMultiviewMode out_mode = priv->output_mode;
1643 GstVideoMultiviewFlags in_flags = priv->input_flags;
1644 GstVideoMultiviewFlags out_flags = priv->output_flags;
1645 gfloat tex_scale[2][2] = {
1649 gfloat offsets[2][2] = {
1653 gchar *fragment_source_str;
1655 gint l_index, r_index;
1657 gl = viewconvert->context->gl_vtable;
1658 if (viewconvert->reconfigure)
1659 gst_gl_view_convert_reset (viewconvert);
1660 if (viewconvert->initted)
1663 GST_LOG_OBJECT (viewconvert,
1664 "Initializing multiview conversion from %s mode %d flags 0x%x w %u h %u to "
1665 "%s mode %d flags 0x%x w %u h %u",
1666 gst_video_format_to_string (GST_VIDEO_INFO_FORMAT
1667 (&viewconvert->in_info)), in_mode, in_flags,
1668 viewconvert->in_info.width, viewconvert->in_info.height,
1669 gst_video_format_to_string (GST_VIDEO_INFO_FORMAT
1670 (&viewconvert->out_info)), out_mode, out_flags,
1671 viewconvert->out_info.width, viewconvert->out_info.height);
1673 if (!gl->CreateProgramObject && !gl->CreateProgram) {
1674 GST_ERROR_OBJECT (viewconvert, "Cannot perform multiview conversion "
1675 "without OpenGL shaders");
1679 if (out_mode == GST_VIDEO_MULTIVIEW_MODE_SEPARATED
1680 || out_mode == GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME) {
1681 if (!gl->DrawBuffers) {
1682 GST_ERROR_OBJECT (viewconvert,
1683 "Separate texture output mode requested however the current "
1684 "OpenGL API does not support drawing to multiple buffers");
1689 if ((in_flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_VIEW_FIRST) ==
1690 (out_flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_VIEW_FIRST)) {
1694 GST_LOG_OBJECT (viewconvert, "Switching left/right views");
1695 /* Swap the views */
1700 if (in_mode < GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE) { /* unknown/mono/left/right single image */
1701 } else if (in_mode == GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE ||
1702 in_mode == GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE_QUINCUNX) {
1703 /* Side-by-side input */
1704 offsets[r_index][0] += 0.5 * tex_scale[r_index][0];
1705 tex_scale[0][0] *= 0.5f; /* Half horizontal scale */
1706 tex_scale[1][0] *= 0.5f;
1707 } else if (in_mode == GST_VIDEO_MULTIVIEW_MODE_TOP_BOTTOM) { /* top-bottom */
1708 offsets[r_index][1] += 0.5 * tex_scale[r_index][1];
1709 tex_scale[0][1] *= 0.5f; /* Half vertical scale */
1710 tex_scale[1][1] *= 0.5f;
1713 /* Flipped is vertical, flopped is horizontal.
1714 * Adjust and offset per-view scaling. This needs to be done
1715 * after the input scaling already splits the views, before
1716 * adding any output scaling. */
1717 if ((in_flags & GST_VIDEO_MULTIVIEW_FLAGS_LEFT_FLIPPED) !=
1718 (out_flags & GST_VIDEO_MULTIVIEW_FLAGS_LEFT_FLIPPED)) {
1719 offsets[l_index][1] += tex_scale[l_index][1];
1720 tex_scale[l_index][1] *= -1.0;
1722 if ((in_flags & GST_VIDEO_MULTIVIEW_FLAGS_LEFT_FLOPPED) !=
1723 (out_flags & GST_VIDEO_MULTIVIEW_FLAGS_LEFT_FLOPPED)) {
1724 offsets[l_index][0] += tex_scale[l_index][0];
1725 tex_scale[l_index][0] *= -1.0;
1727 if ((in_flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLIPPED) !=
1728 (out_flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLIPPED)) {
1729 offsets[r_index][1] += tex_scale[r_index][1];
1730 tex_scale[r_index][1] *= -1.0;
1732 if ((in_flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLOPPED) !=
1733 (out_flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLOPPED)) {
1734 offsets[r_index][0] += tex_scale[r_index][0];
1735 tex_scale[r_index][0] *= -1.0;
1738 if (out_mode == GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE ||
1739 out_mode == GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE_QUINCUNX) {
1741 offsets[1][0] -= tex_scale[1][0];
1742 tex_scale[0][0] *= 2.0f;
1743 tex_scale[1][0] *= 2.0f;
1744 } else if (out_mode == GST_VIDEO_MULTIVIEW_MODE_TOP_BOTTOM) {
1745 offsets[1][1] -= tex_scale[1][1];
1746 tex_scale[0][1] *= 2.0f;
1747 tex_scale[1][1] *= 2.0f;
1750 GST_DEBUG_OBJECT (viewconvert,
1751 "Scaling matrix [ %f, %f ] [ %f %f]. Offsets [ %f, %f ] [ %f, %f ]",
1752 tex_scale[0][0], tex_scale[0][1],
1753 tex_scale[1][0], tex_scale[1][1],
1754 offsets[0][0], offsets[0][1], offsets[1][0], offsets[1][1]);
1756 viewconvert->shader = gst_gl_shader_new (viewconvert->context);
1758 GstGLSLVersion version;
1759 GstGLSLProfile profile;
1760 GstGLSLStage *vert, *frag;
1761 gchar *tmp, *tmp1, *version_str;
1762 const gchar *strings[2];
1763 GError *error = NULL;
1766 _gst_glsl_mangle_shader
1767 (gst_gl_shader_string_vertex_mat4_vertex_transform, GL_VERTEX_SHADER,
1768 GST_GL_TEXTURE_TARGET_2D, viewconvert->from_texture_target,
1769 viewconvert->context, &version, &profile);
1771 tmp1 = gst_glsl_version_profile_to_string (version, profile);
1772 version_str = g_strdup_printf ("#version %s\n", tmp1);
1774 strings[0] = version_str;
1778 gst_glsl_stage_new_with_strings (viewconvert->context,
1779 GL_VERTEX_SHADER, version, profile, 2, strings);
1782 if (!gst_gl_shader_compile_attach_stage (viewconvert->shader, vert, &error)) {
1783 GST_ERROR_OBJECT (viewconvert, "Failed to compile vertex stage %s",
1785 gst_object_unref (viewconvert->shader);
1786 viewconvert->shader = NULL;
1787 g_free (version_str);
1791 fragment_source_str = _get_shader_string (viewconvert, viewconvert->shader,
1792 in_mode, out_mode, version, profile);
1793 strings[1] = fragment_source_str;
1796 gst_glsl_stage_new_with_strings (viewconvert->context,
1797 GL_FRAGMENT_SHADER, version, profile, 2, strings);
1798 g_free (version_str);
1800 if (!gst_gl_shader_compile_attach_stage (viewconvert->shader, frag, &error)) {
1801 GST_ERROR_OBJECT (viewconvert, "Failed to compile fragment stage %s",
1803 g_free (fragment_source_str);
1804 gst_object_unref (viewconvert->shader);
1805 viewconvert->shader = NULL;
1808 g_free (fragment_source_str);
1810 if (!gst_gl_shader_link (viewconvert->shader, &error)) {
1811 GST_ERROR_OBJECT (viewconvert, "Failed to link conversion shader %s",
1813 gst_object_unref (viewconvert->shader);
1814 viewconvert->shader = NULL;
1819 viewconvert->priv->attr_position =
1820 gst_gl_shader_get_attribute_location (viewconvert->shader, "a_position");
1821 viewconvert->priv->attr_texture =
1822 gst_gl_shader_get_attribute_location (viewconvert->shader, "a_texcoord");
1823 gst_gl_shader_use (viewconvert->shader);
1824 gst_gl_shader_set_uniform_2fv (viewconvert->shader, "tex_scale",
1826 gst_gl_shader_set_uniform_2fv (viewconvert->shader, "offsets", 2, offsets[0]);
1827 gst_gl_shader_set_uniform_1f (viewconvert->shader, "width",
1828 GST_VIDEO_INFO_WIDTH (&viewconvert->out_info));
1829 gst_gl_shader_set_uniform_1f (viewconvert->shader, "height",
1830 GST_VIDEO_INFO_HEIGHT (&viewconvert->out_info));
1831 gst_gl_shader_set_uniform_matrix_3fv (viewconvert->shader, "downmix",
1832 2, FALSE, &downmix_matrices[viewconvert->downmix_mode][0][0]);
1833 gst_gl_shader_set_uniform_matrix_4fv (viewconvert->shader, "u_transformation",
1834 1, FALSE, identity_matrix);
1835 if (in_mode == GST_VIDEO_MULTIVIEW_MODE_SEPARATED ||
1836 in_mode == GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME) {
1837 gst_gl_shader_set_uniform_1i (viewconvert->shader, "tex_l", l_index);
1838 gst_gl_shader_set_uniform_1i (viewconvert->shader, "tex_r", r_index);
1840 gst_gl_shader_set_uniform_1i (viewconvert->shader, "tex_l", 0);
1841 gst_gl_shader_set_uniform_1i (viewconvert->shader, "tex_r", 0);
1843 gst_gl_context_clear_shader (viewconvert->context);
1844 if (!_init_view_convert_fbo (viewconvert)) {
1848 if (!viewconvert->priv->vertex_buffer) {
1849 if (gl->GenVertexArrays) {
1850 gl->GenVertexArrays (1, &viewconvert->priv->vao);
1851 gl->BindVertexArray (viewconvert->priv->vao);
1854 gl->GenBuffers (1, &viewconvert->priv->vertex_buffer);
1855 gl->BindBuffer (GL_ARRAY_BUFFER, viewconvert->priv->vertex_buffer);
1856 gl->BufferData (GL_ARRAY_BUFFER, 4 * 5 * sizeof (GLfloat), vertices,
1858 gl->GenBuffers (1, &viewconvert->priv->vbo_indices);
1859 gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, viewconvert->priv->vbo_indices);
1860 gl->BufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof (indices), indices,
1862 if (gl->GenVertexArrays) {
1863 _bind_buffer (viewconvert);
1864 gl->BindVertexArray (0);
1867 gl->BindBuffer (GL_ARRAY_BUFFER, 0);
1868 gl->BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
1871 viewconvert->initted = TRUE;
1878 _do_view_convert_draw (GstGLContext * context, GstGLViewConvert * viewconvert)
1880 GstGLViewConvertPrivate *priv = viewconvert->priv;
1882 guint out_width, out_height;
1884 GLenum multipleRT[] = {
1885 GL_COLOR_ATTACHMENT0,
1886 GL_COLOR_ATTACHMENT1,
1887 GL_COLOR_ATTACHMENT2
1889 GstVideoMultiviewMode in_mode = priv->input_mode;
1890 GstVideoMultiviewMode out_mode = priv->output_mode;
1891 guint from_gl_target =
1892 gst_gl_texture_target_to_gl (viewconvert->from_texture_target);
1894 gl = context->gl_vtable;
1896 gst_gl_framebuffer_bind (viewconvert->fbo);
1898 if (out_mode == GST_VIDEO_MULTIVIEW_MODE_SEPARATED ||
1899 out_mode == GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME) {
1900 out_views = viewconvert->out_info.views;
1905 /* attach the texture to the FBO to renderer to */
1906 for (i = 0; i < out_views; i++) {
1907 GstGLBaseMemory *tex = (GstGLBaseMemory *) priv->out_tex[i];
1909 gst_gl_framebuffer_attach (viewconvert->fbo, GL_COLOR_ATTACHMENT0 + i, tex);
1912 if (gl->DrawBuffers)
1913 gl->DrawBuffers (out_views, multipleRT);
1914 else if (gl->DrawBuffer)
1915 gl->DrawBuffer (GL_COLOR_ATTACHMENT0);
1917 gst_gl_framebuffer_get_effective_dimensions (viewconvert->fbo, &out_width,
1919 gl->Viewport (0, 0, out_width, out_height);
1921 gst_gl_shader_use (viewconvert->shader);
1923 /* FIXME: the auxiliary buffer could have a different transform matrix */
1925 GstVideoAffineTransformationMeta *af_meta;
1929 gst_buffer_get_video_affine_transformation_meta (priv->primary_in);
1930 gst_gl_get_affine_transformation_meta_as_ndc (af_meta, matrix);
1931 gst_gl_shader_set_uniform_matrix_4fv (viewconvert->shader,
1932 "u_transformation", 1, FALSE, matrix);
1935 if (gl->BindVertexArray)
1936 gl->BindVertexArray (priv->vao);
1937 _bind_buffer (viewconvert);
1939 if (in_mode == GST_VIDEO_MULTIVIEW_MODE_SEPARATED ||
1940 in_mode == GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME) {
1941 if (priv->in_tex[1] == NULL) {
1942 GST_ERROR_OBJECT (viewconvert,
1943 "No 2nd view available during conversion!");
1946 gl->ActiveTexture (GL_TEXTURE1);
1947 gl->BindTexture (from_gl_target, priv->in_tex[1]->tex_id);
1950 gl->ActiveTexture (GL_TEXTURE0);
1951 gl->BindTexture (from_gl_target, priv->in_tex[0]->tex_id);
1953 gl->ClearColor (0.0, 0.0, 0.0, 1.0);
1954 gl->Clear (GL_COLOR_BUFFER_BIT);
1956 gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);
1958 if (gl->BindVertexArray)
1959 gl->BindVertexArray (0);
1961 _unbind_buffer (viewconvert);
1963 gl->DrawBuffer (GL_COLOR_ATTACHMENT0);
1964 /* we are done with the shader */
1965 gst_gl_context_clear_shader (context);
1966 gst_gl_context_clear_framebuffer (context);
1972 _gen_buffer (GstGLViewConvert * viewconvert, GstBuffer ** target)
1974 GstGLVideoAllocationParams *params;
1975 GstGLMemoryAllocator *mem_allocator;
1976 GstAllocator *allocator;
1979 *target = gst_buffer_new ();
1982 GST_ALLOCATOR (gst_gl_memory_allocator_get_default
1983 (viewconvert->context));
1984 mem_allocator = GST_GL_MEMORY_ALLOCATOR (allocator);
1985 params = gst_gl_video_allocation_params_new (viewconvert->context, NULL,
1986 &viewconvert->out_info, 0, NULL, viewconvert->to_texture_target, 0);
1988 if (!gst_gl_memory_setup_buffer (mem_allocator, *target, params, NULL, NULL,
1990 gst_gl_allocation_params_free ((GstGLAllocationParams *) params);
1991 gst_object_unref (allocator);
1994 gst_object_unref (allocator);
1996 meta = gst_buffer_add_video_meta_full (*target, 0,
1997 GST_VIDEO_INFO_FORMAT (&viewconvert->out_info),
1998 GST_VIDEO_INFO_WIDTH (&viewconvert->out_info),
1999 GST_VIDEO_INFO_HEIGHT (&viewconvert->out_info),
2000 GST_VIDEO_INFO_N_PLANES (&viewconvert->out_info),
2001 viewconvert->out_info.offset, viewconvert->out_info.stride);
2004 gst_video_meta_set_alignment (meta, *params->valign);
2006 gst_gl_allocation_params_free ((GstGLAllocationParams *) params);
2012 _do_view_convert (GstGLContext * context, GstGLViewConvert * viewconvert)
2014 GstGLViewConvertPrivate *priv = viewconvert->priv;
2015 guint in_width, in_height, out_width, out_height;
2016 GstMapInfo out_info[GST_VIDEO_MAX_PLANES], in_info[GST_VIDEO_MAX_PLANES];
2017 GstGLMemory *dest_tex[GST_VIDEO_MAX_PLANES];
2018 gboolean res = TRUE;
2020 gint in_views, out_views;
2021 GstVideoMultiviewMode in_mode;
2022 GstVideoMultiviewMode out_mode;
2023 GstGLSyncMeta *sync_meta;
2025 out_width = GST_VIDEO_INFO_WIDTH (&viewconvert->out_info);
2026 out_height = GST_VIDEO_INFO_HEIGHT (&viewconvert->out_info);
2027 in_width = GST_VIDEO_INFO_WIDTH (&viewconvert->in_info);
2028 in_height = GST_VIDEO_INFO_HEIGHT (&viewconvert->in_info);
2030 g_return_if_fail (priv->primary_out == NULL);
2031 g_return_if_fail (priv->auxilliary_out == NULL);
2033 in_mode = priv->input_mode;
2034 out_mode = priv->output_mode;
2036 if (in_mode == GST_VIDEO_MULTIVIEW_MODE_SEPARATED ||
2037 in_mode == GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME)
2038 in_views = viewconvert->in_info.views;
2042 if (out_mode == GST_VIDEO_MULTIVIEW_MODE_SEPARATED ||
2043 out_mode == GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME)
2044 out_views = viewconvert->out_info.views;
2048 if (!_init_view_convert (viewconvert)) {
2049 priv->result = FALSE;
2053 if (!_gen_buffer (viewconvert, &priv->primary_out)) {
2054 GST_ERROR_OBJECT (viewconvert,
2055 "Failed to setup memory for primary output buffer");
2056 priv->result = FALSE;
2060 if (out_mode == GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME) {
2061 if (!_gen_buffer (viewconvert, &priv->auxilliary_out)) {
2062 GST_ERROR_OBJECT (viewconvert,
2063 "Failed to setup memory for second view output buffer");
2064 priv->result = FALSE;
2069 for (i = 0; i < in_views; i++) {
2070 if (in_mode == GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME && i > 0) {
2072 (GstGLMemory *) gst_buffer_peek_memory (priv->auxilliary_in, 0);
2075 (GstGLMemory *) gst_buffer_peek_memory (priv->primary_in, i);
2077 if (!gst_is_gl_memory ((GstMemory *) priv->in_tex[i])) {
2078 GST_ERROR_OBJECT (viewconvert, "input must be GstGLMemory");
2082 if (!gst_memory_map ((GstMemory *) priv->in_tex[i],
2083 &in_info[i], GST_MAP_READ | GST_MAP_GL)) {
2084 GST_ERROR_OBJECT (viewconvert, "failed to map input memory %p",
2091 for (j = 0; j < out_views; j++) {
2092 GstGLMemory *out_tex;
2093 guint width, height;
2094 GstVideoInfo temp_info;
2096 if (j > 0 && out_mode == GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME) {
2097 dest_tex[j] = out_tex =
2098 (GstGLMemory *) gst_buffer_peek_memory (priv->auxilliary_out, 0);
2100 dest_tex[j] = out_tex =
2101 (GstGLMemory *) gst_buffer_peek_memory (priv->primary_out, j);
2104 if (!gst_is_gl_memory ((GstMemory *) out_tex)) {
2105 GST_ERROR_OBJECT (viewconvert, "output must be GstGLMemory");
2110 width = gst_gl_memory_get_texture_width (out_tex);
2111 height = gst_gl_memory_get_texture_height (out_tex);
2112 gst_video_info_set_format (&temp_info, GST_VIDEO_FORMAT_RGBA, width,
2114 if (out_tex->tex_format == GST_GL_LUMINANCE
2115 || out_tex->tex_format == GST_GL_LUMINANCE_ALPHA
2116 || out_width != width || out_height != height) {
2117 /* Luminance formats are not color renderable */
2118 /* rendering to a framebuffer only renders the intersection of all
2119 * the attachments i.e. the smallest attachment size */
2120 if (!priv->out_tex[j]) {
2121 GstGLVideoAllocationParams *params;
2122 GstGLBaseMemoryAllocator *base_mem_allocator;
2123 GstAllocator *allocator;
2124 GstVideoInfo temp_info;
2126 gst_video_info_set_format (&temp_info, GST_VIDEO_FORMAT_RGBA, out_width,
2130 GST_ALLOCATOR (gst_gl_memory_allocator_get_default (context));
2131 base_mem_allocator = GST_GL_BASE_MEMORY_ALLOCATOR (allocator);
2132 params = gst_gl_video_allocation_params_new (context, NULL, &temp_info,
2133 0, NULL, viewconvert->to_texture_target, GST_GL_RGBA);
2136 (GstGLMemory *) gst_gl_base_memory_alloc (base_mem_allocator,
2137 (GstGLAllocationParams *) params);
2139 gst_gl_allocation_params_free ((GstGLAllocationParams *) params);
2140 gst_object_unref (allocator);
2143 priv->out_tex[j] = out_tex;
2146 if (!gst_memory_map ((GstMemory *) priv->out_tex[j],
2147 &out_info[j], GST_MAP_WRITE | GST_MAP_GL)) {
2148 GST_ERROR_OBJECT (viewconvert, "failed to map output memory %p",
2154 priv->n_out_tex = out_views;
2156 if (priv->primary_in) {
2157 if ((sync_meta = gst_buffer_get_gl_sync_meta (priv->primary_in))) {
2158 gst_gl_sync_meta_wait (sync_meta, context);
2162 if (priv->auxilliary_in) {
2163 if ((sync_meta = gst_buffer_get_gl_sync_meta (priv->auxilliary_in))) {
2164 gst_gl_sync_meta_wait (sync_meta, context);
2168 GST_LOG_OBJECT (viewconvert, "multiview splitting to textures:%p,%p,%p,%p "
2169 "dimensions:%ux%u, from textures:%p,%p,%p,%p dimensions:%ux%u",
2170 priv->out_tex[0], priv->out_tex[1],
2171 priv->out_tex[2], priv->out_tex[3],
2172 out_width, out_height, priv->in_tex[0],
2173 priv->in_tex[1], priv->in_tex[2], priv->in_tex[3], in_width, in_height);
2175 if (!_do_view_convert_draw (context, viewconvert))
2178 for (j--; j >= 0; j--) {
2179 GstGLMemory *out_tex;
2180 guint width, height;
2182 out_tex = dest_tex[j];
2184 width = gst_gl_memory_get_texture_width (out_tex);
2185 height = gst_gl_memory_get_texture_height (out_tex);
2187 gst_memory_unmap ((GstMemory *) priv->out_tex[j], &out_info[j]);
2188 if (out_tex != priv->out_tex[j]) {
2189 GstMapInfo to_info, from_info;
2190 if (!gst_memory_map ((GstMemory *) priv->out_tex[j],
2191 &from_info, GST_MAP_READ | GST_MAP_GL)) {
2192 GST_ERROR_OBJECT (viewconvert, "Failed to map intermediate memory");
2196 if (!gst_memory_map ((GstMemory *) out_tex, &to_info,
2197 GST_MAP_WRITE | GST_MAP_GL)) {
2198 GST_ERROR_OBJECT (viewconvert, "Failed to map intermediate memory");
2202 gst_gl_memory_copy_into (priv->out_tex[j], out_tex->tex_id,
2203 viewconvert->to_texture_target, out_tex->tex_format, width, height);
2204 gst_memory_unmap ((GstMemory *) out_tex, &to_info);
2207 priv->out_tex[j] = NULL;
2210 for (i--; i >= 0; i--) {
2211 gst_memory_unmap ((GstMemory *) priv->in_tex[i], &in_info[i]);
2215 gst_buffer_replace (&priv->primary_out, NULL);
2216 gst_buffer_replace (&priv->auxilliary_out, NULL);
2219 if (priv->primary_out) {
2220 if ((sync_meta = gst_buffer_add_gl_sync_meta (context, priv->primary_out)))
2221 gst_gl_sync_meta_set_sync_point (sync_meta, context);
2224 if (priv->auxilliary_out) {
2226 gst_buffer_add_gl_sync_meta (context, priv->auxilliary_out)))
2227 gst_gl_sync_meta_set_sync_point (sync_meta, context);
2235 * gst_gl_view_convert_submit_input_buffer:
2236 * @viewconvert: a #GstGLViewConvert
2237 * @is_discont: true if we have a discontinuity
2238 * @input: (transfer full): a #GstBuffer
2240 * Submit @input to be processed by @viewconvert
2242 * Returns: a #GstFlowReturn
2247 gst_gl_view_convert_submit_input_buffer (GstGLViewConvert * viewconvert,
2248 gboolean is_discont, GstBuffer * input)
2250 GstFlowReturn ret = GST_FLOW_OK;
2251 GstVideoMultiviewMode mode;
2255 gst_buffer_replace (&viewconvert->priv->primary_in, NULL);
2256 gst_buffer_replace (&viewconvert->priv->auxilliary_in, NULL);
2259 mode = viewconvert->input_mode_override;
2260 if (mode == GST_VIDEO_MULTIVIEW_MODE_NONE)
2261 mode = GST_VIDEO_INFO_MULTIVIEW_MODE (&viewconvert->in_info);
2263 target = &viewconvert->priv->primary_in;
2265 /* For frame-by-frame mode, we need to collect the 2nd eye into
2266 * our auxiliary buffer */
2267 if (mode == GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME) {
2268 if (!GST_BUFFER_FLAG_IS_SET (input, GST_VIDEO_BUFFER_FLAG_FIRST_IN_BUNDLE))
2269 target = &viewconvert->priv->auxilliary_in;
2273 gst_buffer_unref (*target);
2280 * gst_gl_view_convert_get_output:
2281 * @viewconvert: a #GstGLViewConvert
2282 * @outbuf_ptr: (out): a #GstBuffer
2284 * Retrieve the processed output buffer placing the output in @outbuf_ptr.
2286 * Returns: a #GstFlowReturn
2291 gst_gl_view_convert_get_output (GstGLViewConvert * viewconvert,
2292 GstBuffer ** outbuf_ptr)
2294 GstGLViewConvertPrivate *priv = viewconvert->priv;
2295 GstBuffer *outbuf = NULL;
2296 GstFlowReturn ret = GST_FLOW_OK;
2297 GstVideoMultiviewMode in_mode, out_mode;
2298 GstVideoMultiviewFlags in_flags, out_flags;
2300 g_return_val_if_fail (GST_IS_GL_VIEW_CONVERT (viewconvert), GST_FLOW_ERROR);
2301 g_return_val_if_fail (GST_IS_GL_CONTEXT (viewconvert->context),
2304 GST_OBJECT_LOCK (viewconvert);
2306 /* See if a buffer is available already */
2307 if (priv->primary_out) {
2308 outbuf = viewconvert->priv->primary_out;
2309 priv->primary_out = NULL;
2312 if (viewconvert->priv->auxilliary_out) {
2313 outbuf = priv->auxilliary_out;
2314 priv->auxilliary_out = NULL;
2318 /* Check prereqs before processing a new input buffer */
2319 if (priv->primary_in == NULL)
2322 in_mode = viewconvert->input_mode_override;
2323 in_flags = viewconvert->input_flags_override;
2324 if (in_mode == GST_VIDEO_MULTIVIEW_MODE_NONE) {
2325 in_mode = GST_VIDEO_INFO_MULTIVIEW_MODE (&viewconvert->in_info);
2326 in_flags = GST_VIDEO_INFO_MULTIVIEW_FLAGS (&viewconvert->in_info);
2329 /* Configured output mode already takes any override
2331 out_mode = GST_VIDEO_INFO_MULTIVIEW_MODE (&viewconvert->out_info);
2332 out_flags = GST_VIDEO_INFO_MULTIVIEW_FLAGS (&viewconvert->out_info);
2334 if (in_mode == GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME) {
2335 /* For frame-by-frame, we need 2 input buffers */
2336 if (priv->auxilliary_in == NULL) {
2337 GST_LOG_OBJECT (viewconvert,
2338 "Can't generate output yet - frame-by-frame mode");
2343 /* Store the current conversion in the priv vars */
2344 priv->input_mode = in_mode;
2345 priv->input_flags = in_flags;
2346 priv->output_mode = out_mode;
2347 priv->output_flags = out_flags;
2349 if (priv->input_mode == priv->output_mode &&
2350 priv->input_flags == priv->output_flags &&
2351 viewconvert->in_info.width == viewconvert->out_info.width &&
2352 viewconvert->in_info.height == viewconvert->out_info.height &&
2353 viewconvert->from_texture_target == viewconvert->to_texture_target) {
2354 /* passthrough - just pass input buffers */
2355 outbuf = gst_buffer_ref (priv->primary_in);
2356 if (in_mode == GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME)
2357 priv->auxilliary_out = gst_buffer_ref (priv->auxilliary_in);
2358 goto done_clear_input;
2361 /* We can't output to OES textures, they're only supported for passthrough */
2362 if (viewconvert->to_texture_target == GST_GL_TEXTURE_TARGET_EXTERNAL_OES) {
2363 ret = GST_FLOW_ERROR;
2364 goto done_clear_input;
2367 /* Generate new output buffer(s) */
2368 gst_gl_context_thread_add (viewconvert->context,
2369 (GstGLContextThreadFunc) _do_view_convert, viewconvert);
2371 if (!priv->result) {
2372 if (priv->primary_out)
2373 gst_object_unref (priv->primary_out);
2374 if (priv->auxilliary_out)
2375 gst_object_unref (priv->auxilliary_out);
2376 priv->primary_out = NULL;
2377 priv->auxilliary_out = NULL;
2378 ret = GST_FLOW_ERROR;
2379 goto done_clear_input;
2382 outbuf = priv->primary_out;
2384 GstVideoOverlayCompositionMeta *composition_meta;
2386 gst_buffer_copy_into (outbuf, priv->primary_in,
2387 GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS, 0, -1);
2388 GST_BUFFER_FLAG_SET (outbuf,
2389 GST_VIDEO_BUFFER_FLAG_FIRST_IN_BUNDLE |
2390 GST_VIDEO_BUFFER_FLAG_MULTIPLE_VIEW);
2393 gst_buffer_get_video_overlay_composition_meta (priv->primary_in);
2394 if (composition_meta) {
2395 GST_DEBUG ("found video overlay composition meta, applying on output.");
2396 gst_buffer_add_video_overlay_composition_meta
2397 (outbuf, composition_meta->overlay);
2401 if (priv->auxilliary_out) {
2402 GstVideoOverlayCompositionMeta *composition_meta;
2404 gst_buffer_copy_into (priv->auxilliary_out,
2405 priv->primary_out, GST_BUFFER_COPY_FLAGS, 0, -1);
2406 GST_BUFFER_FLAG_UNSET (priv->auxilliary_out,
2407 GST_VIDEO_BUFFER_FLAG_FIRST_IN_BUNDLE);
2410 gst_buffer_get_video_overlay_composition_meta (priv->primary_out);
2411 if (composition_meta) {
2412 GST_DEBUG ("found video overlay composition meta, applying on output.");
2413 gst_buffer_add_video_overlay_composition_meta
2414 (priv->auxilliary_out, composition_meta->overlay);
2417 priv->primary_out = NULL;
2420 /* Invalidate input buffers now they've been used */
2421 gst_buffer_replace (&priv->primary_in, NULL);
2422 gst_buffer_replace (&priv->auxilliary_in, NULL);
2425 GST_OBJECT_UNLOCK (viewconvert);
2426 *outbuf_ptr = outbuf;
2430 #ifndef GST_REMOVE_DEPRECATED
2431 #ifdef GST_DISABLE_DEPRECATED
2432 GST_GL_API GType gst_gl_stereo_downmix_mode_get_type (void);
2436 gst_gl_stereo_downmix_mode_get_type (void)
2438 return gst_gl_stereo_downmix_get_type ();