vulkan: move fullscreenquad object to library
[platform/upstream/gstreamer.git] / ext / vulkan / vkviewconvert.c
1 /*
2  * GStreamer
3  * Copyright (C) 2019 Matthew Waters <matthew@centricular.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:element-vulkanimageidentity
23  * @title: vulkanimgeidentity
24  *
25  * vulkanimageidentity produces a vulkan image that is a copy of the input image.
26  */
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif
31
32 #include <string.h>
33
34 #include "vkviewconvert.h"
35
36 #include "shaders/identity.vert.h"
37 #include "shaders/view_convert.frag.h"
38 #include "gstvulkan-plugins-enumtypes.h"
39
40 GST_DEBUG_CATEGORY (gst_debug_vulkan_view_convert);
41 #define GST_CAT_DEFAULT gst_debug_vulkan_view_convert
42
43 /* *INDENT-OFF* */
44 /* These match the order and number of DOWNMIX_ANAGLYPH_* modes */
45 static float downmix_matrices[][2][12] = {
46   {                             /* Green-Magenta Dubois */
47     {-0.062f, 0.284f, -0.015f, 0.0, -0.158f, 0.668f, -0.027f, 0.0, -0.039f, 0.143f, 0.021f, 0.0},
48     {0.529f, -0.016f, 0.009f, 0.0, 0.705f, -0.015f, 0.075f, 0.0, 0.024f, -0.065f, 0.937f, 0.0}
49   },
50   {                             /* Red-Cyan Dubois */
51     /* Source of this matrix: http://www.site.uottawa.ca/~edubois/anaglyph/LeastSquaresHowToPhotoshop.pdf */
52     {0.437f, -0.062f, -0.048f, 0.0, 0.449f, -0.062f, -0.050f, 0.0, 0.164f, -0.024f, -0.017f},
53     {-0.011f, 0.377f, -0.026f, 0.0, -0.032f, 0.761f, -0.093f, 0.0, -0.007f, 0.009f, 1.234f}
54   },
55   {                             /* Amber-blue Dubois */
56     {1.062f, -0.026f, -0.038f, 0.0, -0.205f, 0.908f, -0.173f, 0.0, 0.299f, 0.068f, 0.022f},
57     {-0.016f, 0.006f, 0.094f, 0.0, -0.123f, 0.062f, 0.185f, 0.0, -0.017f, -0.017f, 0.911f}
58   }
59 };
60 /* *INDENT-ON* */
61
62 struct ViewUpdate
63 {
64   int in_reorder_idx[4];
65   int out_reorder_idx[4];
66   float tex_offset[2][2];
67   float tex_scale[2][2];
68   int tex_size[2];
69   int output_type;
70   int _padding;
71   float downmix[2][12];
72 };
73
74 static void
75 get_rgb_format_swizzle_order (GstVideoFormat format,
76     gint swizzle[GST_VIDEO_MAX_COMPONENTS])
77 {
78   const GstVideoFormatInfo *finfo = gst_video_format_get_info (format);
79   int c_i = 0, i;
80
81   g_return_if_fail (finfo->flags & GST_VIDEO_FORMAT_FLAG_RGB
82       || format == GST_VIDEO_FORMAT_AYUV);
83
84   for (i = 0; i < finfo->n_components; i++) {
85     swizzle[c_i++] = finfo->poffset[i];
86   }
87
88   /* special case spaced RGB formats as the space does not contain a poffset
89    * value and we need all four components to be valid in order to swizzle
90    * correctly */
91   if (format == GST_VIDEO_FORMAT_xRGB || format == GST_VIDEO_FORMAT_xBGR) {
92     swizzle[c_i++] = 0;
93   } else if (format == GST_VIDEO_FORMAT_RGBx || format == GST_VIDEO_FORMAT_BGRx) {
94     swizzle[c_i++] = 3;
95   } else {
96     for (i = finfo->n_components; i < GST_VIDEO_MAX_COMPONENTS; i++) {
97       swizzle[c_i++] = -1;
98     }
99   }
100 }
101
102 static void
103 get_vulkan_rgb_format_swizzle_order (VkFormat format, gint * swizzle,
104     guint swizzle_count, guint offset)
105 {
106   const GstVulkanFormatInfo *finfo = gst_vulkan_format_get_info (format);
107   int i;
108
109   g_return_if_fail (finfo->flags & GST_VULKAN_FORMAT_FLAG_RGB);
110   g_return_if_fail (finfo->n_components <= swizzle_count);
111
112   for (i = 0; i < finfo->n_components; i++) {
113     swizzle[i] = offset + finfo->poffset[i];
114   }
115   for (i = finfo->n_components; i < swizzle_count; i++) {
116     swizzle[i] = -1;
117   }
118 }
119
120 /* given a swizzle index, produce an index such that:
121  *
122  * swizzle[idx[i]] == identity[i] where:
123  * - swizzle is the original swizzle
124  * - idx is the result
125  * - identity = {0, 1, 2,...}
126  * - unset fields are marked by -1
127  */
128 static void
129 swizzle_identity_order (gint * swizzle, gint * idx)
130 {
131   int i;
132
133   for (i = 0; i < GST_VIDEO_MAX_COMPONENTS; i++) {
134     idx[i] = -1;
135   }
136
137   for (i = 0; i < GST_VIDEO_MAX_COMPONENTS; i++) {
138     if (swizzle[i] >= 0 && swizzle[i] < 4 && idx[swizzle[i]] == -1) {
139       idx[swizzle[i]] = i;
140     }
141   }
142 }
143
144 static void
145 video_format_to_reorder (GstVideoFormat v_format, gint * reorder,
146     gboolean input)
147 {
148   switch (v_format) {
149     case GST_VIDEO_FORMAT_RGBA:
150     case GST_VIDEO_FORMAT_RGBx:
151     case GST_VIDEO_FORMAT_BGRA:
152     case GST_VIDEO_FORMAT_BGRx:
153     case GST_VIDEO_FORMAT_ARGB:
154     case GST_VIDEO_FORMAT_xRGB:
155     case GST_VIDEO_FORMAT_ABGR:
156     case GST_VIDEO_FORMAT_xBGR:
157     case GST_VIDEO_FORMAT_AYUV:
158       get_rgb_format_swizzle_order (v_format, reorder);
159       break;
160     case GST_VIDEO_FORMAT_UYVY:
161       reorder[0] = 1;
162       reorder[1] = 0;
163       reorder[2] = input ? 3 : 2;
164       reorder[3] = 0;
165       break;
166     case GST_VIDEO_FORMAT_YUY2:
167       reorder[0] = 0;
168       reorder[1] = 1;
169       reorder[2] = 0;
170       reorder[3] = input ? 3 : 2;
171       break;
172     case GST_VIDEO_FORMAT_NV12:
173       reorder[0] = 0;
174       reorder[1] = 1;
175       reorder[2] = 2;
176       reorder[3] = 0;
177       break;
178     default:
179       g_assert_not_reached ();
180       break;
181   }
182
183   GST_TRACE ("swizzle: %u, %u, %u, %u", reorder[0], reorder[1], reorder[2],
184       reorder[3]);
185 }
186
187 static guint
188 finfo_get_plane_n_components (const GstVideoFormatInfo * finfo, guint plane)
189 {
190   guint n_components = 0, i;
191
192   switch (finfo->format) {
193     case GST_VIDEO_FORMAT_RGBx:
194     case GST_VIDEO_FORMAT_xRGB:
195     case GST_VIDEO_FORMAT_BGRx:
196     case GST_VIDEO_FORMAT_xBGR:
197       /* fixup spaced RGB formats as we treat the space as a normal alpha
198        * component */
199       return plane == 0 ? 4 : 0;
200     default:
201       break;
202   }
203
204   for (i = 0; i < finfo->n_components; i++) {
205     if (finfo->plane[i] == plane)
206       n_components++;
207   }
208
209   return n_components;
210 }
211
212 static void
213 get_vulkan_format_swizzle_order (GstVideoFormat v_format,
214     VkFormat vk_format[GST_VIDEO_MAX_PLANES],
215     gint swizzle[GST_VIDEO_MAX_COMPONENTS])
216 {
217   const GstVideoFormatInfo *finfo;
218   int i, prev_in_i = 0;
219
220   finfo = gst_video_format_get_info (v_format);
221   for (i = 0; i < finfo->n_planes; i++) {
222     guint plane_components = finfo_get_plane_n_components (finfo, i);
223     get_vulkan_rgb_format_swizzle_order (vk_format[i],
224         &swizzle[prev_in_i], plane_components, prev_in_i);
225     prev_in_i += plane_components;
226   }
227
228   if (v_format == GST_VIDEO_FORMAT_YUY2 || v_format == GST_VIDEO_FORMAT_UYVY) {
229     /* Fixup these packed YUV formats as we use a two component format for
230      * a 4-component pixel and access two samples in the shader */
231     g_assert (swizzle[0] == 0);
232     g_assert (swizzle[1] == 1);
233     swizzle[2] = 2;
234     swizzle[3] = 3;
235   }
236
237   GST_TRACE ("%s: %i, %i, %i, %i", finfo->name, swizzle[0], swizzle[1],
238       swizzle[2], swizzle[3]);
239 }
240
241 static void
242 calculate_reorder_indexes (GstVideoFormat in_format,
243     GstVulkanImageView * in_views[GST_VIDEO_MAX_COMPONENTS],
244     GstVideoFormat out_format,
245     GstVulkanImageView * out_views[GST_VIDEO_MAX_COMPONENTS],
246     int ret_in[GST_VIDEO_MAX_COMPONENTS], int ret_out[GST_VIDEO_MAX_COMPONENTS])
247 {
248   const GstVideoFormatInfo *in_finfo, *out_finfo;
249   VkFormat in_vk_formats[GST_VIDEO_MAX_COMPONENTS];
250   VkFormat out_vk_formats[GST_VIDEO_MAX_COMPONENTS];
251   int in_vk_order[GST_VIDEO_MAX_COMPONENTS],
252       in_reorder[GST_VIDEO_MAX_COMPONENTS];
253   int out_vk_order[GST_VIDEO_MAX_COMPONENTS],
254       out_reorder[GST_VIDEO_MAX_COMPONENTS];
255   int tmp[GST_VIDEO_MAX_PLANES];
256   int i;
257
258   in_finfo = gst_video_format_get_info (in_format);
259   out_finfo = gst_video_format_get_info (out_format);
260
261   for (i = 0; i < in_finfo->n_planes; i++)
262     in_vk_formats[i] = in_views[i]->image->create_info.format;
263   for (i = 0; i < out_finfo->n_planes; i++)
264     out_vk_formats[i] = out_views[i]->image->create_info.format;
265
266   get_vulkan_format_swizzle_order (in_format, in_vk_formats, in_vk_order);
267   video_format_to_reorder (in_format, in_reorder, TRUE);
268
269   video_format_to_reorder (out_format, out_reorder, FALSE);
270   get_vulkan_format_swizzle_order (out_format, out_vk_formats, out_vk_order);
271
272   for (i = 0; i < GST_VIDEO_MAX_COMPONENTS; i++)
273     tmp[i] = out_vk_order[out_reorder[i]];
274   /* find the identity order for RGBA->$format */
275   GST_TRACE ("pre-invert: %u, %u, %u, %u", tmp[0], tmp[1], tmp[2], tmp[3]);
276   if (out_format == GST_VIDEO_FORMAT_YUY2
277       || out_format == GST_VIDEO_FORMAT_UYVY) {
278     for (i = 0; i < GST_VIDEO_MAX_COMPONENTS; i++)
279       ret_out[i] = tmp[i];
280   } else {
281     swizzle_identity_order (tmp, ret_out);
282   }
283
284   for (i = 0; i < GST_VIDEO_MAX_COMPONENTS; i++)
285     ret_in[i] = in_reorder[in_vk_order[i]];
286   GST_TRACE ("in reorder: %u, %u, %u, %u", ret_in[0], ret_in[1], ret_in[2],
287       ret_in[3]);
288   GST_TRACE ("out reorder: %u, %u, %u, %u", ret_out[0], ret_out[1], ret_out[2],
289       ret_out[3]);
290 }
291
292 static void
293 update_descriptor_set (GstVulkanViewConvert * conv,
294     GstVulkanImageView ** in_views)
295 {
296   GstVulkanVideoFilter *vfilter = GST_VULKAN_VIDEO_FILTER (conv);
297   VkDescriptorImageInfo image_info[GST_VIDEO_MAX_PLANES];
298   VkWriteDescriptorSet writes[GST_VIDEO_MAX_PLANES];
299   GstVideoMultiviewMode in_mode;
300   GstVideoMultiviewFlags in_flags, out_flags;
301   VkImageView views[GST_VIDEO_MAX_PLANES];
302   guint i = 0;
303
304   in_mode = conv->input_mode_override;
305   in_flags = conv->input_flags_override;
306   if (in_mode == GST_VIDEO_MULTIVIEW_MODE_NONE)
307     in_flags = GST_VIDEO_INFO_MULTIVIEW_FLAGS (&vfilter->in_info);
308   out_flags = GST_VIDEO_INFO_MULTIVIEW_FLAGS (&vfilter->out_info);
309
310   for (i = 0; i < GST_VIDEO_INFO_N_PLANES (&vfilter->in_info); i++) {
311     if ((in_flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_VIEW_FIRST) ==
312         (out_flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_VIEW_FIRST)) {
313       views[2 * i] = in_views[i]->view;
314       views[2 * i + 1] = in_views[i]->view;
315     } else {
316       views[2 * i] = in_views[i]->view;
317       views[2 * i + 1] = in_views[i]->view;
318     }
319   }
320
321   for (i = 0; i < GST_VIDEO_INFO_N_PLANES (&vfilter->in_info) * 2; i++) {
322     /* *INDENT-OFF* */
323     image_info[i] = (VkDescriptorImageInfo) {
324         .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
325         .imageView = views[i],
326         .sampler = (VkSampler) conv->quad->sampler->handle
327     };
328
329     g_assert (i < GST_VIDEO_MAX_PLANES);
330
331     writes[i] = (VkWriteDescriptorSet) {
332         .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
333         .pNext = NULL,
334         .dstSet = conv->quad->descriptor_set->set,
335         .dstBinding = i+1,
336         .dstArrayElement = 0,
337         .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
338         .descriptorCount = 1,
339         .pImageInfo = &image_info[i]
340     };
341     /* *INDENT-ON* */
342   }
343   g_assert (i <= G_N_ELEMENTS (writes));
344
345   vkUpdateDescriptorSets (vfilter->device->device, i, writes, 0, NULL);
346 }
347
348 static gboolean
349 create_uniform_buffer (GstVulkanViewConvert * conv)
350 {
351   GstVulkanVideoFilter *vfilter = GST_VULKAN_VIDEO_FILTER (conv);
352
353   conv->uniform =
354       gst_vulkan_buffer_memory_alloc (vfilter->device,
355       sizeof (struct ViewUpdate),
356       VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
357       VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
358       VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
359
360   return TRUE;
361 }
362
363 static gboolean
364 update_uniform (GstVulkanViewConvert * conv, GstVulkanImageView ** in_views,
365     GstVulkanImageView ** out_views)
366 {
367   GstVulkanVideoFilter *vfilter = GST_VULKAN_VIDEO_FILTER (conv);
368   GstVideoMultiviewMode in_mode, out_mode;
369   GstVideoMultiviewFlags in_flags, out_flags;
370   struct ViewUpdate data;
371   GstMapInfo map_info;
372   guint l_index, r_index;
373   gboolean mono_input = FALSE;
374
375   calculate_reorder_indexes (GST_VIDEO_INFO_FORMAT (&vfilter->in_info),
376       in_views, GST_VIDEO_INFO_FORMAT (&vfilter->out_info),
377       out_views, data.in_reorder_idx, data.out_reorder_idx);
378
379   data.tex_scale[0][0] = data.tex_scale[0][1] = 1.;
380   data.tex_scale[1][0] = data.tex_scale[1][1] = 1.;
381   data.tex_offset[0][0] = data.tex_offset[0][1] = 0.;
382   data.tex_offset[1][0] = data.tex_offset[1][1] = 0.;
383
384   in_mode = conv->input_mode_override;
385   in_flags = conv->input_flags_override;
386   if (in_mode == GST_VIDEO_MULTIVIEW_MODE_NONE) {
387     in_mode = GST_VIDEO_INFO_MULTIVIEW_MODE (&vfilter->in_info);
388     in_flags = GST_VIDEO_INFO_MULTIVIEW_FLAGS (&vfilter->in_info);
389   }
390
391   /* Configured output mode already takes any override
392    * into account */
393   out_mode = GST_VIDEO_INFO_MULTIVIEW_MODE (&vfilter->out_info);
394   out_flags = GST_VIDEO_INFO_MULTIVIEW_FLAGS (&vfilter->out_info);
395
396   if ((in_flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_VIEW_FIRST) ==
397       (out_flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_VIEW_FIRST)) {
398     l_index = 0;
399     r_index = 1;
400   } else {
401     GST_LOG_OBJECT (conv, "Switching left/right views");
402
403     l_index = 1;
404     r_index = 0;
405   }
406
407   if (in_mode < GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE) {        /* unknown/mono/left/right single image */
408   } else if (in_mode == GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE ||
409       in_mode == GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE_QUINCUNX) {
410     /* Side-by-side input */
411     data.tex_offset[r_index][0] += 0.5 * data.tex_scale[r_index][0];
412     data.tex_scale[0][0] *= 0.5f;       /* Half horizontal scale */
413     data.tex_scale[1][0] *= 0.5f;
414   } else if (in_mode == GST_VIDEO_MULTIVIEW_MODE_TOP_BOTTOM) {  /* top-bottom */
415     data.tex_offset[r_index][1] += 0.5 * data.tex_scale[r_index][1];
416     data.tex_scale[0][1] *= 0.5f;       /* Half vertical scale */
417     data.tex_scale[1][1] *= 0.5f;
418   }
419
420   /* Flipped is vertical, flopped is horizontal.
421    * Adjust and offset per-view scaling. This needs to be done
422    * after the input scaling already splits the views, before
423    * adding any output scaling. */
424   if ((in_flags & GST_VIDEO_MULTIVIEW_FLAGS_LEFT_FLIPPED) !=
425       (out_flags & GST_VIDEO_MULTIVIEW_FLAGS_LEFT_FLIPPED)) {
426     data.tex_offset[l_index][1] += data.tex_scale[l_index][1];
427     data.tex_scale[l_index][1] *= -1.0;
428   }
429   if ((in_flags & GST_VIDEO_MULTIVIEW_FLAGS_LEFT_FLOPPED) !=
430       (out_flags & GST_VIDEO_MULTIVIEW_FLAGS_LEFT_FLOPPED)) {
431     data.tex_offset[l_index][0] += data.tex_scale[l_index][0];
432     data.tex_scale[l_index][0] *= -1.0;
433   }
434   if ((in_flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLIPPED) !=
435       (out_flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLIPPED)) {
436     data.tex_offset[r_index][1] += data.tex_scale[r_index][1];
437     data.tex_scale[r_index][1] *= -1.0;
438   }
439   if ((in_flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLOPPED) !=
440       (out_flags & GST_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLOPPED)) {
441     data.tex_offset[r_index][0] += data.tex_scale[r_index][0];
442     data.tex_scale[r_index][0] *= -1.0;
443   }
444
445   if (out_mode == GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE ||
446       out_mode == GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE_QUINCUNX) {
447     /* Side-by-Side */
448     data.tex_offset[1][0] -= data.tex_scale[1][0];
449     data.tex_scale[0][0] *= 2.0f;
450     data.tex_scale[1][0] *= 2.0f;
451   } else if (out_mode == GST_VIDEO_MULTIVIEW_MODE_TOP_BOTTOM) {
452     data.tex_offset[1][1] -= data.tex_scale[1][1];
453     data.tex_scale[0][1] *= 2.0f;
454     data.tex_scale[1][1] *= 2.0f;
455   }
456
457   GST_DEBUG_OBJECT (conv,
458       "Scaling matrix [ %f, %f ] [ %f %f]. Offsets [ %f, %f ] [ %f, %f ]",
459       data.tex_scale[0][0], data.tex_scale[0][1],
460       data.tex_scale[1][0], data.tex_scale[1][1],
461       data.tex_offset[0][0], data.tex_offset[0][1], data.tex_offset[1][0],
462       data.tex_offset[1][1]);
463
464   if (in_mode == GST_VIDEO_MULTIVIEW_MODE_NONE ||
465       in_mode == GST_VIDEO_MULTIVIEW_MODE_MONO ||
466       in_mode == GST_VIDEO_MULTIVIEW_MODE_LEFT ||
467       in_mode == GST_VIDEO_MULTIVIEW_MODE_RIGHT)
468     mono_input = TRUE;
469
470   data.output_type = out_mode;
471   if (data.output_type == GST_VIDEO_MULTIVIEW_MODE_NONE ||
472       data.output_type == GST_VIDEO_MULTIVIEW_MODE_MONO) {
473     if (mono_input)
474       data.output_type = GST_VIDEO_MULTIVIEW_MODE_LEFT;
475     else
476       data.output_type = GST_VIDEO_MULTIVIEW_MODE_MONO;
477   } else if (data.output_type == GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE_QUINCUNX) {
478     data.output_type = GST_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE;
479   }
480
481   data.tex_size[0] = GST_VIDEO_INFO_WIDTH (&vfilter->out_info);
482   data.tex_size[1] = GST_VIDEO_INFO_HEIGHT (&vfilter->out_info);
483   memcpy (&data.downmix[0], &downmix_matrices[conv->downmix_mode][0],
484       sizeof (data.downmix[0]));
485   memcpy (&data.downmix[1], &downmix_matrices[conv->downmix_mode][1],
486       sizeof (data.downmix[1]));
487
488   if (!gst_memory_map (conv->uniform, &map_info, GST_MAP_WRITE)) {
489     return FALSE;
490   }
491   memcpy (map_info.data, &data, sizeof (data));
492   gst_memory_unmap (conv->uniform, &map_info);
493
494   return TRUE;
495 }
496
497 static GstMemory *
498 get_uniforms (GstVulkanViewConvert * conv,
499     GstVulkanImageView ** in_views, GstVulkanImageView ** out_views)
500 {
501   if (!conv->uniform) {
502     if (!create_uniform_buffer (conv))
503       return NULL;
504     if (!update_uniform (conv, in_views, out_views)) {
505       gst_memory_unref (conv->uniform);
506       conv->uniform = NULL;
507       return FALSE;
508     }
509   }
510
511   return gst_memory_ref (conv->uniform);
512 }
513
514 static void gst_vulkan_view_convert_set_property (GObject * object,
515     guint prop_id, const GValue * value, GParamSpec * pspec);
516 static void gst_vulkan_view_convert_get_property (GObject * object,
517     guint prop_id, GValue * value, GParamSpec * pspec);
518
519 static gboolean gst_vulkan_view_convert_start (GstBaseTransform * bt);
520 static gboolean gst_vulkan_view_convert_stop (GstBaseTransform * bt);
521
522 static GstCaps *gst_vulkan_view_convert_transform_caps (GstBaseTransform * bt,
523     GstPadDirection direction, GstCaps * caps, GstCaps * filter);
524 static GstCaps *gst_vulkan_view_convert_fixate_caps (GstBaseTransform * bt,
525     GstPadDirection direction, GstCaps * caps, GstCaps * othercaps);
526 static GstFlowReturn gst_vulkan_view_convert_transform (GstBaseTransform * bt,
527     GstBuffer * inbuf, GstBuffer * outbuf);
528 static gboolean gst_vulkan_view_convert_set_caps (GstBaseTransform * bt,
529     GstCaps * in_caps, GstCaps * out_caps);
530
531 static GstStaticPadTemplate gst_vulkan_sink_template =
532 GST_STATIC_PAD_TEMPLATE ("sink",
533     GST_PAD_SINK,
534     GST_PAD_ALWAYS,
535     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
536         (GST_CAPS_FEATURE_MEMORY_VULKAN_IMAGE,
537             "{ BGRA, RGBA }")));
538
539 static GstStaticPadTemplate gst_vulkan_src_template =
540 GST_STATIC_PAD_TEMPLATE ("src",
541     GST_PAD_SRC,
542     GST_PAD_ALWAYS,
543     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
544         (GST_CAPS_FEATURE_MEMORY_VULKAN_IMAGE,
545             "{ BGRA, RGBA }")));
546
547 enum
548 {
549   PROP_0,
550   PROP_INPUT_LAYOUT,
551   PROP_INPUT_FLAGS,
552   PROP_OUTPUT_LAYOUT,
553   PROP_OUTPUT_FLAGS,
554   PROP_OUTPUT_DOWNMIX_MODE
555 };
556
557 #define DEFAULT_DOWNMIX GST_VULKAN_STEREO_DOWNMIX_ANAGLYPH_GREEN_MAGENTA_DUBOIS
558
559 #define gst_vulkan_view_convert_parent_class parent_class
560 G_DEFINE_TYPE_WITH_CODE (GstVulkanViewConvert, gst_vulkan_view_convert,
561     GST_TYPE_VULKAN_VIDEO_FILTER,
562     GST_DEBUG_CATEGORY_INIT (gst_debug_vulkan_view_convert,
563         "vulkanviewconvert", 0, "Vulkan View Convert"));
564
565 static void
566 gst_vulkan_view_convert_class_init (GstVulkanViewConvertClass * klass)
567 {
568   GObjectClass *gobject_class;
569   GstElementClass *gstelement_class;
570   GstBaseTransformClass *gstbasetransform_class;
571
572   gobject_class = (GObjectClass *) klass;
573   gstelement_class = (GstElementClass *) klass;
574   gstbasetransform_class = (GstBaseTransformClass *) klass;
575
576   gobject_class->set_property = gst_vulkan_view_convert_set_property;
577   gobject_class->get_property = gst_vulkan_view_convert_get_property;
578
579   g_object_class_install_property (gobject_class, PROP_INPUT_LAYOUT,
580       g_param_spec_enum ("input-mode-override",
581           "Input Multiview Mode Override",
582           "Override any input information about multiview layout",
583           GST_TYPE_VIDEO_MULTIVIEW_MODE,
584           GST_VIDEO_MULTIVIEW_MODE_NONE,
585           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
586   g_object_class_install_property (gobject_class, PROP_INPUT_FLAGS,
587       g_param_spec_flags ("input-flags-override",
588           "Input Multiview Flags Override",
589           "Override any input information about multiview layout flags",
590           GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE,
591           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
592   g_object_class_install_property (gobject_class, PROP_OUTPUT_LAYOUT,
593       g_param_spec_enum ("output-mode-override",
594           "Output Multiview Mode Override",
595           "Override automatic output mode selection for multiview layout",
596           GST_TYPE_VIDEO_MULTIVIEW_MODE, GST_VIDEO_MULTIVIEW_MODE_NONE,
597           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
598   g_object_class_install_property (gobject_class, PROP_OUTPUT_FLAGS,
599       g_param_spec_flags ("output-flags-override",
600           "Output Multiview Flags Override",
601           "Override automatic negotiation for output multiview layout flags",
602           GST_TYPE_VIDEO_MULTIVIEW_FLAGS, GST_VIDEO_MULTIVIEW_FLAGS_NONE,
603           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
604   g_object_class_install_property (gobject_class, PROP_OUTPUT_DOWNMIX_MODE,
605       g_param_spec_enum ("downmix-mode", "Mode for mono downmixed output",
606           "Output anaglyph type to generate when downmixing to mono",
607           GST_TYPE_VULKAN_STEREO_DOWNMIX, DEFAULT_DOWNMIX,
608           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
609
610   gst_element_class_set_metadata (gstelement_class, "Vulkan Uploader",
611       "Filter/Video/Convert", "A Vulkan View Convert",
612       "Matthew Waters <matthew@centricular.com>");
613
614   gst_element_class_add_static_pad_template (gstelement_class,
615       &gst_vulkan_sink_template);
616   gst_element_class_add_static_pad_template (gstelement_class,
617       &gst_vulkan_src_template);
618
619   gstbasetransform_class->start =
620       GST_DEBUG_FUNCPTR (gst_vulkan_view_convert_start);
621   gstbasetransform_class->stop =
622       GST_DEBUG_FUNCPTR (gst_vulkan_view_convert_stop);
623   gstbasetransform_class->transform_caps =
624       gst_vulkan_view_convert_transform_caps;
625   gstbasetransform_class->fixate_caps = gst_vulkan_view_convert_fixate_caps;
626   gstbasetransform_class->set_caps = gst_vulkan_view_convert_set_caps;
627   gstbasetransform_class->transform = gst_vulkan_view_convert_transform;
628 }
629
630 static void
631 gst_vulkan_view_convert_init (GstVulkanViewConvert * conv)
632 {
633   conv->downmix_mode = DEFAULT_DOWNMIX;
634
635   conv->input_mode_override = GST_VIDEO_MULTIVIEW_MODE_NONE;
636   conv->input_flags_override = GST_VIDEO_MULTIVIEW_FLAGS_NONE;
637   conv->output_mode_override = GST_VIDEO_MULTIVIEW_MODE_NONE;
638   conv->output_flags_override = GST_VIDEO_MULTIVIEW_FLAGS_NONE;
639 }
640
641 static void
642 gst_vulkan_view_convert_set_property (GObject * object, guint prop_id,
643     const GValue * value, GParamSpec * pspec)
644 {
645   GstVulkanViewConvert *conv = GST_VULKAN_VIEW_CONVERT (object);
646
647   switch (prop_id) {
648     case PROP_INPUT_LAYOUT:
649       conv->input_mode_override = g_value_get_enum (value);
650       gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (conv));
651       break;
652     case PROP_INPUT_FLAGS:
653       conv->input_flags_override = g_value_get_flags (value);
654       gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (conv));
655       break;
656     case PROP_OUTPUT_LAYOUT:
657       conv->output_mode_override = g_value_get_enum (value);
658       gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (conv));
659       break;
660     case PROP_OUTPUT_FLAGS:
661       conv->output_flags_override = g_value_get_flags (value);
662       gst_base_transform_reconfigure_src (GST_BASE_TRANSFORM (conv));
663       break;
664     case PROP_OUTPUT_DOWNMIX_MODE:
665       conv->downmix_mode = g_value_get_enum (value);
666       break;
667     default:
668       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
669       break;
670   }
671 }
672
673 static void
674 gst_vulkan_view_convert_get_property (GObject * object, guint prop_id,
675     GValue * value, GParamSpec * pspec)
676 {
677   GstVulkanViewConvert *conv = GST_VULKAN_VIEW_CONVERT (object);
678
679   switch (prop_id) {
680     case PROP_INPUT_LAYOUT:
681       g_value_set_enum (value, conv->input_mode_override);
682       break;
683     case PROP_INPUT_FLAGS:
684       g_value_set_flags (value, conv->input_flags_override);
685       break;
686     case PROP_OUTPUT_LAYOUT:
687       g_value_set_enum (value, conv->output_mode_override);
688       break;
689     case PROP_OUTPUT_FLAGS:
690       g_value_set_flags (value, conv->output_flags_override);
691       break;
692     case PROP_OUTPUT_DOWNMIX_MODE:
693       g_value_set_enum (value, conv->downmix_mode);
694       break;
695     default:
696       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
697       break;
698   }
699 }
700
701 /* Function that can halve the value
702  * of ints, fractions, int/fraction ranges and lists of ints/fractions */
703 static gboolean
704 _halve_value (GValue * out, const GValue * in_value)
705 {
706   /* Fundamental fixed types first */
707   if (G_VALUE_HOLDS_INT (in_value)) {
708     g_value_init (out, G_TYPE_INT);
709     g_value_set_int (out, MAX (g_value_get_int (in_value) / 2, 1));
710   } else if (GST_VALUE_HOLDS_FRACTION (in_value)) {
711     gint num, den;
712     num = gst_value_get_fraction_numerator (in_value);
713     den = gst_value_get_fraction_denominator (in_value);
714     g_value_init (out, GST_TYPE_FRACTION);
715     /* Don't adjust 'infinite' fractions */
716     if ((num != 1 || den != 2147483647) && (num != 2147483647 || den != 1)) {
717       /* FIXME - could do better approximation when den > G_MAXINT/2? */
718       den = den > G_MAXINT / 2 ? G_MAXINT : den * 2;
719     }
720     gst_value_set_fraction (out, num, den);
721   } else if (GST_VALUE_HOLDS_INT_RANGE (in_value)) {
722     gint range_min = gst_value_get_int_range_min (in_value);
723     gint range_max = gst_value_get_int_range_max (in_value);
724     gint range_step = gst_value_get_int_range_step (in_value);
725     g_value_init (out, GST_TYPE_INT_RANGE);
726     if (range_min != 1)
727       range_min = MAX (1, range_min / 2);
728     if (range_max != G_MAXINT)
729       range_max = MAX (1, range_max / 2);
730     gst_value_set_int_range_step (out, range_min,
731         range_max, MAX (1, range_step / 2));
732   } else if (GST_VALUE_HOLDS_FRACTION_RANGE (in_value)) {
733     GValue min_out = G_VALUE_INIT;
734     GValue max_out = G_VALUE_INIT;
735     const GValue *range_min = gst_value_get_fraction_range_min (in_value);
736     const GValue *range_max = gst_value_get_fraction_range_max (in_value);
737     _halve_value (&min_out, range_min);
738     _halve_value (&max_out, range_max);
739     g_value_init (out, GST_TYPE_FRACTION_RANGE);
740     gst_value_set_fraction_range (out, &min_out, &max_out);
741     g_value_unset (&min_out);
742     g_value_unset (&max_out);
743   } else if (GST_VALUE_HOLDS_LIST (in_value)) {
744     gint i;
745     g_value_init (out, GST_TYPE_LIST);
746     for (i = 0; i < gst_value_list_get_size (in_value); i++) {
747       const GValue *entry;
748       GValue tmp = G_VALUE_INIT;
749
750       entry = gst_value_list_get_value (in_value, i);
751       /* Random list values might not be the right type */
752       if (!_halve_value (&tmp, entry))
753         goto fail;
754       gst_value_list_append_and_take_value (out, &tmp);
755     }
756   } else {
757     return FALSE;
758   }
759
760   return TRUE;
761 fail:
762   g_value_unset (out);
763   return FALSE;
764 }
765
766 static GstStructure *
767 _halve_structure_field (const GstStructure * in, const gchar * field_name)
768 {
769   GstStructure *out;
770   const GValue *in_value = gst_structure_get_value (in, field_name);
771   GValue tmp = G_VALUE_INIT;
772
773   if (G_UNLIKELY (in_value == NULL))
774     return gst_structure_copy (in);     /* Field doesn't exist, leave it as is */
775
776   if (!_halve_value (&tmp, in_value))
777     return NULL;
778
779   out = gst_structure_copy (in);
780   gst_structure_set_value (out, field_name, &tmp);
781   g_value_unset (&tmp);
782
783   return out;
784 }
785
786 /* Function that can double the value
787  * of ints, fractions, int/fraction ranges and lists of ints/fractions */
788 static gboolean
789 _double_value (GValue * out, const GValue * in_value)
790 {
791   /* Fundamental fixed types first */
792   if (G_VALUE_HOLDS_INT (in_value)) {
793     gint n = g_value_get_int (in_value);
794     g_value_init (out, G_TYPE_INT);
795     if (n <= G_MAXINT / 2)
796       g_value_set_int (out, n * 2);
797     else
798       g_value_set_int (out, G_MAXINT);
799   } else if (GST_VALUE_HOLDS_FRACTION (in_value)) {
800     gint num, den;
801     num = gst_value_get_fraction_numerator (in_value);
802     den = gst_value_get_fraction_denominator (in_value);
803     g_value_init (out, GST_TYPE_FRACTION);
804     /* Don't adjust 'infinite' fractions */
805     if ((num != 1 || den != 2147483647) && (num != 2147483647 || den != 1)) {
806       /* FIXME - could do better approximation when num > G_MAXINT/2? */
807       num = num > G_MAXINT / 2 ? G_MAXINT : num * 2;
808     }
809     gst_value_set_fraction (out, num, den);
810   } else if (GST_VALUE_HOLDS_INT_RANGE (in_value)) {
811     gint range_min = gst_value_get_int_range_min (in_value);
812     gint range_max = gst_value_get_int_range_max (in_value);
813     gint range_step = gst_value_get_int_range_step (in_value);
814     if (range_min != 1) {
815       range_min = MIN (G_MAXINT / 2, range_min);
816       range_min *= 2;
817     }
818     if (range_max != G_MAXINT) {
819       range_max = MIN (G_MAXINT / 2, range_max);
820       range_max *= 2;
821     }
822     range_step = MIN (G_MAXINT / 2, range_step);
823     g_value_init (out, GST_TYPE_INT_RANGE);
824     gst_value_set_int_range_step (out, range_min, range_max, range_step);
825   } else if (GST_VALUE_HOLDS_FRACTION_RANGE (in_value)) {
826     GValue min_out = G_VALUE_INIT;
827     GValue max_out = G_VALUE_INIT;
828     const GValue *range_min = gst_value_get_fraction_range_min (in_value);
829     const GValue *range_max = gst_value_get_fraction_range_max (in_value);
830     _double_value (&min_out, range_min);
831     _double_value (&max_out, range_max);
832     g_value_init (out, GST_TYPE_FRACTION_RANGE);
833     gst_value_set_fraction_range (out, &min_out, &max_out);
834     g_value_unset (&min_out);
835     g_value_unset (&max_out);
836   } else if (GST_VALUE_HOLDS_LIST (in_value)) {
837     gint i;
838     g_value_init (out, GST_TYPE_LIST);
839     for (i = 0; i < gst_value_list_get_size (in_value); i++) {
840       const GValue *entry;
841       GValue tmp = G_VALUE_INIT;
842
843       entry = gst_value_list_get_value (in_value, i);
844       /* Random list values might not be the right type */
845       if (!_double_value (&tmp, entry))
846         goto fail;
847       gst_value_list_append_and_take_value (out, &tmp);
848     }
849   } else {
850     return FALSE;
851   }
852
853   return TRUE;
854 fail:
855   g_value_unset (out);
856   return FALSE;
857 }
858
859 static GstStructure *
860 _double_structure_field (const GstStructure * in, const gchar * field_name)
861 {
862   GstStructure *out;
863   const GValue *in_value = gst_structure_get_value (in, field_name);
864   GValue tmp = G_VALUE_INIT;
865
866   if (G_UNLIKELY (in_value == NULL))
867     return gst_structure_copy (in);     /* Field doesn't exist, leave it as is */
868
869   if (!_double_value (&tmp, in_value))
870     return NULL;
871
872   out = gst_structure_copy (in);
873   gst_structure_set_value (out, field_name, &tmp);
874   g_value_unset (&tmp);
875
876   return out;
877 }
878
879 /* Return a copy of the caps with the requested field doubled in value/range */
880 static GstCaps *
881 _double_caps_field (const GstCaps * in, const gchar * field_name)
882 {
883   gint i;
884   GstCaps *out = gst_caps_new_empty ();
885
886   for (i = 0; i < gst_caps_get_size (in); i++) {
887     const GstStructure *cur = gst_caps_get_structure (in, i);
888     GstCapsFeatures *f = gst_caps_get_features (in, i);
889
890     GstStructure *res = _double_structure_field (cur, field_name);
891     out =
892         gst_caps_merge_structure_full (out, res,
893         f ? gst_caps_features_copy (f) : NULL);
894   }
895
896   return out;
897 }
898
899 /* Takes ownership of the input caps  */
900 static GstCaps *
901 _expand_par_for_half_aspect (GstCaps * in, gboolean vertical_half_aspect)
902 {
903   guint mview_flags, mview_flags_mask;
904   GstCaps *out;
905   GstStructure *tmp;
906
907   out = gst_caps_new_empty ();
908
909   while (gst_caps_get_size (in) > 0) {
910     GstStructure *s;
911     GstCapsFeatures *features;
912
913     features = gst_caps_get_features (in, 0);
914     if (features)
915       features = gst_caps_features_copy (features);
916
917     s = gst_caps_steal_structure (in, 0);
918
919     if (!gst_structure_get_flagset (s, "multiview-flags", &mview_flags,
920             &mview_flags_mask)) {
921       gst_caps_append_structure_full (out, s, features);
922       continue;
923     }
924     /* If the input doesn't care about the half-aspect flag, allow current PAR in either variant */
925     if ((mview_flags_mask & GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT) == 0) {
926       gst_caps_append_structure_full (out, s, features);
927       continue;
928     }
929     if (!gst_structure_has_field (s, "pixel-aspect-ratio")) {
930       /* No par field, dont-care the half-aspect flag */
931       gst_structure_set (s, "multiview-flags",
932           GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
933           mview_flags & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT,
934           mview_flags_mask & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT, NULL);
935       gst_caps_append_structure_full (out, s, features);
936       continue;
937     }
938
939     /* Halve or double PAR base on inputs input specified. */
940
941     /* Append a copy with the half-aspect flag as-is */
942     tmp = gst_structure_copy (s);
943     out = gst_caps_merge_structure_full (out, tmp,
944         features ? gst_caps_features_copy (features) : NULL);
945
946     /* and then a copy inverted */
947     if (mview_flags & GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT) {
948       /* Input is half-aspect. Double/halve the PAR, clear the flag */
949       if (vertical_half_aspect)
950         tmp = _halve_structure_field (s, "pixel-aspect-ratio");
951       else
952         tmp = _double_structure_field (s, "pixel-aspect-ratio");
953       /* Clear the flag */
954       gst_structure_set (tmp, "multiview-flags",
955           GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
956           mview_flags & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT,
957           mview_flags_mask | GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT, NULL);
958     } else {
959       if (vertical_half_aspect)
960         tmp = _double_structure_field (s, "pixel-aspect-ratio");
961       else
962         tmp = _halve_structure_field (s, "pixel-aspect-ratio");
963       /* Set the flag */
964       gst_structure_set (tmp, "multiview-flags",
965           GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
966           mview_flags | GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT,
967           mview_flags_mask | GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT, NULL);
968     }
969
970     out = gst_caps_merge_structure_full (out, tmp,
971         features ? gst_caps_features_copy (features) : NULL);
972
973     gst_structure_free (s);
974     if (features)
975       gst_caps_features_free (features);
976   }
977
978   gst_caps_unref (in);
979
980   return out;
981 }
982
983 /* If input supports top-bottom or row-interleaved, we may halve height to mono frames.
984  * If input supports left-right, checkerboard, quincunx or column-interleaved,
985  * we may halve width to mono frames.
986  * For output of top-bottom or row-interleaved, we may double the mono height
987  * For output of left-right, checkerboard, quincunx or column-interleaved,
988  * we may double the mono width.
989  * In all cases, if input has half-aspect and output does not, we may double the PAR
990  * And if input does *not* have half-aspect flag and output does not, we may halve the PAR
991  */
992 static GstCaps *
993 _expand_structure (GstVulkanViewConvert * viewconvert,
994     GstCaps * out_caps, GstStructure * structure, GstCapsFeatures * features)
995 {
996   GstCaps *expanded_caps, *tmp;
997   GstCaps *mono_caps;
998   const gchar *default_mview_mode_str = NULL;
999   guint mview_flags, mview_flags_mask;
1000   const GValue *in_modes;
1001   gint i;
1002
1003   /* Empty caps to accumulate into */
1004   expanded_caps = gst_caps_new_empty ();
1005
1006   /* First, set defaults if multiview flags are missing */
1007   default_mview_mode_str =
1008       gst_video_multiview_mode_to_caps_string (GST_VIDEO_MULTIVIEW_MODE_MONO);
1009
1010   mview_flags = GST_VIDEO_MULTIVIEW_FLAGS_NONE;
1011   mview_flags_mask = GST_FLAG_SET_MASK_EXACT;
1012
1013   if (!gst_structure_has_field (structure, "multiview-mode")) {
1014     gst_structure_set (structure,
1015         "multiview-mode", G_TYPE_STRING, default_mview_mode_str, NULL);
1016   }
1017   if (!gst_structure_has_field (structure, "multiview-flags")) {
1018     gst_structure_set (structure,
1019         "multiview-flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGSET, mview_flags,
1020         mview_flags_mask, NULL);
1021   } else {
1022     gst_structure_get_flagset (structure, "multiview-flags",
1023         &mview_flags, &mview_flags_mask);
1024   }
1025
1026   in_modes = gst_structure_get_value (structure, "multiview-mode");
1027   mono_caps = gst_caps_new_empty ();
1028   if (gst_value_intersect (NULL, in_modes,
1029           gst_video_multiview_get_mono_modes ())) {
1030     GstStructure *new_struct = gst_structure_copy (structure);
1031     gst_structure_set_value (new_struct, "multiview-mode",
1032         gst_video_multiview_get_mono_modes ());
1033     /* Half-aspect makes no sense for mono or unpacked, get rid of it */
1034     if (mview_flags & GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT) {
1035       gst_structure_set (new_struct, "multiview-flags",
1036           GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
1037           mview_flags & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT,
1038           mview_flags_mask & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT, NULL);
1039     }
1040     gst_caps_append_structure_full (mono_caps, new_struct,
1041         features ? gst_caps_features_copy (features) : NULL);
1042   }
1043   if (gst_value_intersect (NULL, in_modes,
1044           gst_video_multiview_get_unpacked_modes ())) {
1045     GstStructure *new_struct = gst_structure_copy (structure);
1046
1047     gst_structure_set_value (new_struct, "multiview-mode",
1048         gst_video_multiview_get_mono_modes ());
1049
1050     /* Half-aspect makes no sense for mono or unpacked, get rid of it */
1051     if (mview_flags & GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT) {
1052       gst_structure_set (new_struct, "multiview-flags",
1053           GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
1054           mview_flags & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT,
1055           mview_flags_mask & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT, NULL);
1056     }
1057     gst_caps_append_structure_full (mono_caps, new_struct,
1058         features ? gst_caps_features_copy (features) : NULL);
1059   }
1060
1061   if (gst_value_intersect (NULL, in_modes,
1062           gst_video_multiview_get_doubled_height_modes ())) {
1063     /* Append mono formats with height halved */
1064     GstStructure *new_struct = _halve_structure_field (structure, "height");
1065     gst_structure_set_value (new_struct, "multiview-mode",
1066         gst_video_multiview_get_mono_modes ());
1067     /* Normalise the half-aspect flag away */
1068     if (mview_flags & GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT) {
1069       GstStructure *s =
1070           _halve_structure_field (new_struct, "pixel-aspect-ratio");
1071       gst_structure_set (structure, "multiview-flags",
1072           GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
1073           mview_flags & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT,
1074           mview_flags_mask | GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT, NULL);
1075       gst_structure_free (new_struct);
1076       new_struct = s;
1077     }
1078     mono_caps = gst_caps_merge_structure_full (mono_caps, new_struct,
1079         features ? gst_caps_features_copy (features) : NULL);
1080   }
1081   if (gst_value_intersect (NULL, in_modes,
1082           gst_video_multiview_get_doubled_width_modes ())) {
1083     /* Append mono formats with width halved */
1084     GstStructure *new_struct = _halve_structure_field (structure, "width");
1085     gst_structure_set_value (new_struct, "multiview-mode",
1086         gst_video_multiview_get_mono_modes ());
1087     /* Normalise the half-aspect flag away */
1088     if (mview_flags & GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT) {
1089       GstStructure *s =
1090           _double_structure_field (new_struct, "pixel-aspect-ratio");
1091       gst_structure_set (structure, "multiview-flags",
1092           GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
1093           mview_flags & ~GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT,
1094           mview_flags_mask | GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT, NULL);
1095       gst_structure_free (new_struct);
1096       new_struct = s;
1097     }
1098     mono_caps = gst_caps_merge_structure_full (mono_caps, new_struct,
1099         features ? gst_caps_features_copy (features) : NULL);
1100   }
1101   if (gst_value_intersect (NULL, in_modes,
1102           gst_video_multiview_get_doubled_size_modes ())) {
1103     /* Append checkerboard/doubled size formats with width & height halved */
1104     GstStructure *new_struct_w = _halve_structure_field (structure, "width");
1105     GstStructure *new_struct_wh =
1106         _halve_structure_field (new_struct_w, "height");
1107     gst_structure_free (new_struct_w);
1108     gst_structure_set_value (new_struct_wh, "multiview-mode",
1109         gst_video_multiview_get_mono_modes ());
1110     mono_caps = gst_caps_merge_structure_full (mono_caps, new_struct_wh,
1111         features ? gst_caps_features_copy (features) : NULL);
1112   }
1113
1114   /* Everything is normalised now, unset the flags we can change */
1115   /* Remove the views field, as these are all 'mono' modes
1116    * Need to do this before we expand caps back out to frame packed modes */
1117   for (i = 0; i < gst_caps_get_size (mono_caps); i++) {
1118     GstStructure *s = gst_caps_get_structure (mono_caps, i);
1119     gst_structure_remove_fields (s, "views", NULL);
1120     if (gst_structure_get_flagset (s, "multiview-flags", &mview_flags,
1121             &mview_flags_mask)) {
1122       /* Preserve only the half-aspect and mixed-mono flags, for now.
1123        * The rest we can change */
1124       mview_flags_mask &=
1125           (GST_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT |
1126           GST_VIDEO_MULTIVIEW_FLAGS_MIXED_MONO);
1127       gst_structure_set (s, "multiview-flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
1128           mview_flags, mview_flags_mask, NULL);
1129     }
1130   }
1131
1132   GST_TRACE_OBJECT (viewconvert,
1133       "Collected single-view caps %" GST_PTR_FORMAT, mono_caps);
1134   /* Put unpacked and mono modes first. We don't care about flags. Clear them */
1135   tmp = gst_caps_copy (mono_caps);
1136   for (i = 0; i < gst_caps_get_size (tmp); i++) {
1137     GstStructure *s = gst_caps_get_structure (tmp, i);
1138     gst_structure_remove_fields (s, "views", NULL);
1139     if (gst_structure_get_flagset (s, "multiview-flags", &mview_flags,
1140             &mview_flags_mask)) {
1141       /* We can change any flags for mono modes - half-aspect and mixed-mono have no meaning */
1142       mview_flags_mask = 0;
1143       gst_structure_set (s, "multiview-flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
1144           mview_flags, mview_flags_mask, NULL);
1145     }
1146   }
1147   expanded_caps = gst_caps_merge (expanded_caps, tmp);
1148
1149   /* Unpacked output modes have 2 views, for now */
1150   tmp = gst_caps_copy (mono_caps);
1151   gst_caps_set_value (tmp, "multiview-mode",
1152       gst_video_multiview_get_unpacked_modes ());
1153   for (i = 0; i < gst_caps_get_size (tmp); i++) {
1154     GstStructure *s = gst_caps_get_structure (tmp, i);
1155     gst_structure_set (s, "views", G_TYPE_INT, 2, NULL);
1156     if (gst_structure_get_flagset (s, "multiview-flags", &mview_flags,
1157             &mview_flags_mask)) {
1158       /* We can change any flags for unpacked modes - half-aspect and mixed-mono have no meaning */
1159       mview_flags_mask = 0;
1160       gst_structure_set (s, "multiview-flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGSET,
1161           mview_flags, mview_flags_mask, NULL);
1162     }
1163   }
1164   expanded_caps = gst_caps_merge (expanded_caps, tmp);
1165
1166   /* Double height output modes */
1167   tmp = _double_caps_field (mono_caps, "height");
1168   gst_caps_set_value (tmp, "multiview-mode",
1169       gst_video_multiview_get_doubled_height_modes ());
1170   tmp = _expand_par_for_half_aspect (tmp, TRUE);
1171
1172   expanded_caps = gst_caps_merge (expanded_caps, tmp);
1173
1174   /* Double width output modes */
1175   tmp = _double_caps_field (mono_caps, "width");
1176   gst_caps_set_value (tmp, "multiview-mode",
1177       gst_video_multiview_get_doubled_width_modes ());
1178   tmp = _expand_par_for_half_aspect (tmp, FALSE);
1179
1180   expanded_caps = gst_caps_merge (expanded_caps, tmp);
1181
1182   /* Double size output modes */
1183   {
1184     GstCaps *tmp_w = _double_caps_field (mono_caps, "width");
1185     tmp = _double_caps_field (tmp_w, "height");
1186     gst_caps_unref (tmp_w);
1187     gst_caps_set_value (tmp, "multiview-mode",
1188         gst_video_multiview_get_doubled_size_modes ());
1189     expanded_caps = gst_caps_merge (expanded_caps, tmp);
1190   }
1191
1192   /* We're done with the mono caps now */
1193   gst_caps_unref (mono_caps);
1194
1195   GST_TRACE_OBJECT (viewconvert,
1196       "expanded transform caps now %" GST_PTR_FORMAT, expanded_caps);
1197
1198   if (gst_caps_is_empty (expanded_caps)) {
1199     gst_caps_unref (expanded_caps);
1200     return out_caps;
1201   }
1202   /* Really, we can rescale - so at this point we can append full-range
1203    * height/width/PAR as an unpreferred final option. */
1204 /*  tmp = gst_caps_copy (expanded_caps);
1205   gst_caps_set_simple (tmp, "width", GST_TYPE_INT_RANGE, 1, G_MAXINT,
1206       "height", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL);
1207 */
1208   out_caps = gst_caps_merge (out_caps, expanded_caps);
1209 /*  out_caps = gst_caps_merge (out_caps, tmp);*/
1210   return out_caps;
1211 }
1212
1213 static GstCaps *
1214 _intersect_with_mview_mode (GstCaps * caps,
1215     GstVideoMultiviewMode mode, GstVideoMultiviewFlags flags)
1216 {
1217   GstCaps *filter, *result;
1218   const gchar *caps_str;
1219
1220   caps_str = gst_video_multiview_mode_to_caps_string (mode);
1221
1222   filter = gst_caps_new_simple ("video/x-raw",
1223       "multiview-mode", G_TYPE_STRING,
1224       caps_str, "multiview-flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGSET, flags,
1225       GST_FLAG_SET_MASK_EXACT, NULL);
1226
1227   if (mode == GST_VIDEO_MULTIVIEW_MODE_SEPARATED ||
1228       mode == GST_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME)
1229     gst_caps_set_simple (filter, "views", G_TYPE_INT, 2, NULL);
1230   gst_caps_set_features (filter, 0, gst_caps_features_new_any ());
1231
1232   GST_DEBUG ("Intersecting target caps %" GST_PTR_FORMAT
1233       " with caps %" GST_PTR_FORMAT, caps, filter);
1234
1235   result = gst_caps_intersect_full (caps, filter, GST_CAPS_INTERSECT_FIRST);
1236   gst_caps_unref (filter);
1237   return result;
1238 }
1239
1240 static GstCaps *
1241 _intersect_with_mview_modes (GstCaps * caps, const GValue * modes)
1242 {
1243   GstCaps *filter, *result;
1244
1245   filter = gst_caps_new_empty_simple ("video/x-raw");
1246
1247   gst_caps_set_value (filter, "multiview-mode", modes);
1248   gst_caps_set_features (filter, 0, gst_caps_features_new_any ());
1249
1250   GST_DEBUG ("Intersecting target caps %" GST_PTR_FORMAT
1251       " with caps %" GST_PTR_FORMAT, caps, filter);
1252
1253   result = gst_caps_intersect_full (caps, filter, GST_CAPS_INTERSECT_FIRST);
1254   gst_caps_unref (filter);
1255   return result;
1256 }
1257
1258 static GstCaps *
1259 gst_vulkan_view_convert_transform_caps (GstBaseTransform * bt,
1260     GstPadDirection direction, GstCaps * caps, GstCaps * filter)
1261 {
1262   GstVulkanViewConvert *viewconvert = GST_VULKAN_VIEW_CONVERT (bt);
1263   GstCaps *base_caps =
1264       gst_static_pad_template_get_caps (&gst_vulkan_sink_template);
1265   GstCaps *out_caps, *tmp_caps;
1266   gint i;
1267
1268   GST_DEBUG_OBJECT (viewconvert, "Direction %s "
1269       "input caps %" GST_PTR_FORMAT " filter %" GST_PTR_FORMAT,
1270       direction == GST_PAD_SINK ? "sink" : "src", caps, filter);
1271
1272   /* We can only process VulkanImage caps, start from that */
1273   caps = gst_caps_intersect (caps, base_caps);
1274   gst_caps_unref (base_caps);
1275
1276   /* Change input/output to the formats we can convert to/from,
1277    * but keep the original caps at the start - we will always prefer
1278    * passthrough */
1279   if (direction == GST_PAD_SINK) {
1280     out_caps = gst_caps_copy (caps);
1281     if (viewconvert->input_mode_override != GST_VIDEO_MULTIVIEW_MODE_NONE) {
1282       GstVideoMultiviewMode mode = viewconvert->input_mode_override;
1283       GstVideoMultiviewFlags flags = viewconvert->input_flags_override;
1284
1285       const gchar *caps_str = gst_video_multiview_mode_to_caps_string (mode);
1286       /* Coerce the input caps before transforming, so the sizes come out right */
1287       gst_caps_set_simple (out_caps, "multiview-mode", G_TYPE_STRING,
1288           caps_str, "multiview-flags", GST_TYPE_VIDEO_MULTIVIEW_FLAGSET, flags,
1289           GST_FLAG_SET_MASK_EXACT, NULL);
1290     }
1291   } else {
1292     out_caps = gst_caps_new_empty ();
1293   }
1294
1295   for (i = 0; i < gst_caps_get_size (caps); i++) {
1296     GstStructure *structure = gst_caps_get_structure (caps, i);
1297     GstCapsFeatures *features = gst_caps_get_features (caps, i);
1298     out_caps = _expand_structure (viewconvert, out_caps, structure, features);
1299   }
1300
1301   if (gst_caps_is_empty (out_caps))
1302     goto out;
1303
1304   /* If we have an output mode override, limit things to that */
1305   if (direction == GST_PAD_SINK &&
1306       viewconvert->output_mode_override != GST_VIDEO_MULTIVIEW_MODE_NONE) {
1307
1308     tmp_caps = _intersect_with_mview_mode (out_caps,
1309         viewconvert->output_mode_override, viewconvert->output_flags_override);
1310
1311     gst_caps_unref (out_caps);
1312     out_caps = tmp_caps;
1313   } else if (viewconvert->input_mode_override != GST_VIDEO_MULTIVIEW_MODE_NONE) {
1314     /* Prepend a copy of our preferred input caps in case the peer
1315      * can handle them */
1316     tmp_caps = _intersect_with_mview_mode (out_caps,
1317         viewconvert->input_mode_override, viewconvert->input_flags_override);
1318     out_caps = gst_caps_merge (out_caps, tmp_caps);
1319   }
1320   if (direction == GST_PAD_SRC) {
1321     GstStructure *s;
1322     /* When generating input caps, we also need a copy of the mono caps
1323      * without multiview-mode or flags for backwards compat, at the end */
1324     tmp_caps = _intersect_with_mview_mode (caps,
1325         GST_VIDEO_MULTIVIEW_MODE_MONO, GST_VIDEO_MULTIVIEW_FLAGS_NONE);
1326     if (!gst_caps_is_empty (tmp_caps)) {
1327       s = gst_caps_get_structure (tmp_caps, 0);
1328       gst_structure_remove_fields (s, "multiview-mode", "multiview-flags",
1329           NULL);
1330       out_caps = gst_caps_merge (out_caps, tmp_caps);
1331     } else
1332       gst_caps_unref (tmp_caps);
1333   }
1334 out:
1335   gst_caps_unref (caps);
1336
1337   GST_DEBUG_OBJECT (viewconvert, "Have caps %" GST_PTR_FORMAT
1338       " filtering with caps %" GST_PTR_FORMAT, out_caps, filter);
1339
1340   if (filter) {
1341     GstCaps *tmp =
1342         gst_caps_intersect_full (filter, out_caps, GST_CAPS_INTERSECT_FIRST);
1343     gst_caps_unref (out_caps);
1344     out_caps = tmp;
1345   }
1346
1347   GST_DEBUG_OBJECT (viewconvert, "Returning caps %" GST_PTR_FORMAT, out_caps);
1348   return out_caps;
1349 }
1350
1351 static GstCaps *
1352 fixate_size (GstVulkanViewConvert * viewconvert,
1353     GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
1354 {
1355   GstStructure *ins, *outs;
1356   const GValue *from_par, *to_par;
1357   GValue fpar = { 0, }, tpar = {
1358   0,};
1359
1360   othercaps = gst_caps_make_writable (othercaps);
1361   othercaps = gst_caps_truncate (othercaps);
1362
1363   GST_DEBUG_OBJECT (viewconvert, "trying to fixate othercaps %" GST_PTR_FORMAT
1364       " based on caps %" GST_PTR_FORMAT, othercaps, caps);
1365
1366   ins = gst_caps_get_structure (caps, 0);
1367   outs = gst_caps_get_structure (othercaps, 0);
1368
1369   from_par = gst_structure_get_value (ins, "pixel-aspect-ratio");
1370   to_par = gst_structure_get_value (outs, "pixel-aspect-ratio");
1371
1372   /* If we're fixating from the sinkpad we always set the PAR and
1373    * assume that missing PAR on the sinkpad means 1/1 and
1374    * missing PAR on the srcpad means undefined
1375    */
1376   if (direction == GST_PAD_SINK) {
1377     if (!from_par) {
1378       g_value_init (&fpar, GST_TYPE_FRACTION);
1379       gst_value_set_fraction (&fpar, 1, 1);
1380       from_par = &fpar;
1381     }
1382     if (!to_par) {
1383       g_value_init (&tpar, GST_TYPE_FRACTION);
1384       gst_value_set_fraction (&tpar, 1, 1);
1385       to_par = &tpar;
1386     }
1387   } else {
1388     if (!to_par) {
1389       g_value_init (&tpar, GST_TYPE_FRACTION);
1390       gst_value_set_fraction (&tpar, 1, 1);
1391       to_par = &tpar;
1392
1393       gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
1394           NULL);
1395     }
1396     if (!from_par) {
1397       g_value_init (&fpar, GST_TYPE_FRACTION);
1398       gst_value_set_fraction (&fpar, 1, 1);
1399       from_par = &fpar;
1400     }
1401   }
1402
1403   /* we have both PAR but they might not be fixated */
1404   {
1405     gint from_w, from_h, from_par_n, from_par_d, to_par_n, to_par_d;
1406     gint w = 0, h = 0;
1407     gint from_dar_n, from_dar_d;
1408     gint num, den;
1409
1410     /* from_par should be fixed */
1411     g_return_val_if_fail (gst_value_is_fixed (from_par), othercaps);
1412
1413     from_par_n = gst_value_get_fraction_numerator (from_par);
1414     from_par_d = gst_value_get_fraction_denominator (from_par);
1415
1416     gst_structure_get_int (ins, "width", &from_w);
1417     gst_structure_get_int (ins, "height", &from_h);
1418
1419     gst_structure_get_int (outs, "width", &w);
1420     gst_structure_get_int (outs, "height", &h);
1421
1422     /* if both width and height are already fixed, we can't do anything
1423      * about it anymore */
1424     if (w && h) {
1425       GST_DEBUG_OBJECT (viewconvert,
1426           "dimensions already set to %dx%d, not fixating", w, h);
1427       if (!gst_value_is_fixed (to_par)) {
1428         GST_DEBUG_OBJECT (viewconvert, "fixating to_par to %dx%d", 1, 1);
1429         if (gst_structure_has_field (outs, "pixel-aspect-ratio"))
1430           gst_structure_fixate_field_nearest_fraction (outs,
1431               "pixel-aspect-ratio", 1, 1);
1432       }
1433       goto done;
1434     }
1435
1436     /* Calculate input DAR */
1437     if (!gst_util_fraction_multiply (from_w, from_h, from_par_n, from_par_d,
1438             &from_dar_n, &from_dar_d)) {
1439       GST_ELEMENT_ERROR (viewconvert, CORE, NEGOTIATION, (NULL),
1440           ("Error calculating the output scaled size - integer overflow"));
1441       goto done;
1442     }
1443
1444     GST_DEBUG_OBJECT (viewconvert, "Input DAR is %d/%d", from_dar_n,
1445         from_dar_d);
1446
1447     /* If either width or height are fixed there's not much we
1448      * can do either except choosing a height or width and PAR
1449      * that matches the DAR as good as possible
1450      */
1451     if (h) {
1452       gint num, den;
1453
1454       GST_DEBUG_OBJECT (viewconvert, "height is fixed (%d)", h);
1455
1456       if (!gst_value_is_fixed (to_par)) {
1457         /* (shortcut) copy-paste (??) of videoscale seems to aim for 1/1,
1458          * so let's make it so ...
1459          * especially if following code assumes fixed */
1460         GST_DEBUG_OBJECT (viewconvert, "fixating to_par to 1x1");
1461         gst_structure_fixate_field_nearest_fraction (outs,
1462             "pixel-aspect-ratio", 1, 1);
1463         to_par = gst_structure_get_value (outs, "pixel-aspect-ratio");
1464       }
1465
1466       /* PAR is fixed, choose the height that is nearest to the
1467        * height with the same DAR */
1468       to_par_n = gst_value_get_fraction_numerator (to_par);
1469       to_par_d = gst_value_get_fraction_denominator (to_par);
1470
1471       GST_DEBUG_OBJECT (viewconvert, "PAR is fixed %d/%d", to_par_n, to_par_d);
1472
1473       if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, to_par_d,
1474               to_par_n, &num, &den)) {
1475         GST_ELEMENT_ERROR (viewconvert, CORE, NEGOTIATION, (NULL),
1476             ("Error calculating the output scaled size - integer overflow"));
1477         goto done;
1478       }
1479
1480       w = (guint) gst_util_uint64_scale_int (h, num, den);
1481       gst_structure_fixate_field_nearest_int (outs, "width", w);
1482
1483       goto done;
1484     } else if (w) {
1485       gint num, den;
1486
1487       GST_DEBUG_OBJECT (viewconvert, "width is fixed (%d)", w);
1488
1489       if (!gst_value_is_fixed (to_par)) {
1490         /* (shortcut) copy-paste (??) of videoscale seems to aim for 1/1,
1491          * so let's make it so ...
1492          * especially if following code assumes fixed */
1493         GST_DEBUG_OBJECT (viewconvert, "fixating to_par to 1x1");
1494         gst_structure_fixate_field_nearest_fraction (outs,
1495             "pixel-aspect-ratio", 1, 1);
1496         to_par = gst_structure_get_value (outs, "pixel-aspect-ratio");
1497       }
1498
1499       /* PAR is fixed, choose the height that is nearest to the
1500        * height with the same DAR */
1501       to_par_n = gst_value_get_fraction_numerator (to_par);
1502       to_par_d = gst_value_get_fraction_denominator (to_par);
1503
1504       GST_DEBUG_OBJECT (viewconvert, "PAR is fixed %d/%d", to_par_n, to_par_d);
1505
1506       if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, to_par_d,
1507               to_par_n, &num, &den)) {
1508         GST_ELEMENT_ERROR (viewconvert, CORE, NEGOTIATION, (NULL),
1509             ("Error calculating the output scaled size - integer overflow"));
1510         goto done;
1511       }
1512
1513       h = (guint) gst_util_uint64_scale_int (w, den, num);
1514       gst_structure_fixate_field_nearest_int (outs, "height", h);
1515
1516       goto done;
1517     } else if (gst_value_is_fixed (to_par)) {
1518       GstStructure *tmp;
1519       gint set_h, set_w, f_h, f_w;
1520
1521       to_par_n = gst_value_get_fraction_numerator (to_par);
1522       to_par_d = gst_value_get_fraction_denominator (to_par);
1523
1524       /* Calculate scale factor for the PAR change */
1525       if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, to_par_n,
1526               to_par_d, &num, &den)) {
1527         GST_ELEMENT_ERROR (viewconvert, CORE, NEGOTIATION, (NULL),
1528             ("Error calculating the output scaled size - integer overflow"));
1529         goto done;
1530       }
1531
1532       /* Try to keep the input height */
1533       tmp = gst_structure_copy (outs);
1534       gst_structure_fixate_field_nearest_int (tmp, "height", from_h);
1535       gst_structure_get_int (tmp, "height", &set_h);
1536
1537       /* This might have failed but try to scale the width
1538        * to keep the DAR nonetheless */
1539       w = (guint) gst_util_uint64_scale_int (set_h, num, den);
1540       gst_structure_fixate_field_nearest_int (tmp, "width", w);
1541       gst_structure_get_int (tmp, "width", &set_w);
1542       gst_structure_free (tmp);
1543
1544       /* We kept the DAR and the height is nearest to the original height */
1545       if (set_w == w) {
1546         gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height",
1547             G_TYPE_INT, set_h, NULL);
1548         goto done;
1549       }
1550
1551       f_h = set_h;
1552       f_w = set_w;
1553
1554       /* If the former failed, try to keep the input width at least */
1555       tmp = gst_structure_copy (outs);
1556       gst_structure_fixate_field_nearest_int (tmp, "width", from_w);
1557       gst_structure_get_int (tmp, "width", &set_w);
1558
1559       /* This might have failed but try to scale the width
1560        * to keep the DAR nonetheless */
1561       h = (guint) gst_util_uint64_scale_int (set_w, den, num);
1562       gst_structure_fixate_field_nearest_int (tmp, "height", h);
1563       gst_structure_get_int (tmp, "height", &set_h);
1564       gst_structure_free (tmp);
1565
1566       /* We kept the DAR and the width is nearest to the original width */
1567       if (set_h == h) {
1568         gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height",
1569             G_TYPE_INT, set_h, NULL);
1570         goto done;
1571       }
1572
1573       /* If all this failed, keep the height that was nearest to the original
1574        * height and the nearest possible width. This changes the DAR but
1575        * there's not much else to do here.
1576        */
1577       gst_structure_set (outs, "width", G_TYPE_INT, f_w, "height", G_TYPE_INT,
1578           f_h, NULL);
1579       goto done;
1580     } else {
1581       GstStructure *tmp;
1582       gint set_h, set_w, set_par_n, set_par_d, tmp2;
1583
1584       /* width, height and PAR are not fixed */
1585
1586       /* First try to keep the height and width as good as possible
1587        * and scale PAR */
1588       tmp = gst_structure_copy (outs);
1589       gst_structure_fixate_field_nearest_int (tmp, "height", from_h);
1590       gst_structure_get_int (tmp, "height", &set_h);
1591       gst_structure_fixate_field_nearest_int (tmp, "width", from_w);
1592       gst_structure_get_int (tmp, "width", &set_w);
1593
1594       if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, set_h, set_w,
1595               &to_par_n, &to_par_d)) {
1596         GST_ELEMENT_ERROR (viewconvert, CORE, NEGOTIATION, (NULL),
1597             ("Error calculating the output scaled size - integer overflow"));
1598         gst_structure_free (tmp);
1599         goto done;
1600       }
1601
1602       if (!gst_structure_has_field (tmp, "pixel-aspect-ratio"))
1603         gst_structure_set_value (tmp, "pixel-aspect-ratio", to_par);
1604       gst_structure_fixate_field_nearest_fraction (tmp, "pixel-aspect-ratio",
1605           to_par_n, to_par_d);
1606       gst_structure_get_fraction (tmp, "pixel-aspect-ratio", &set_par_n,
1607           &set_par_d);
1608       gst_structure_free (tmp);
1609
1610       if (set_par_n == to_par_n && set_par_d == to_par_d) {
1611         gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height",
1612             G_TYPE_INT, set_h, NULL);
1613
1614         if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
1615             set_par_n != set_par_d)
1616           gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
1617               set_par_n, set_par_d, NULL);
1618         goto done;
1619       }
1620
1621       /* Otherwise try to scale width to keep the DAR with the set
1622        * PAR and height */
1623       if (!gst_util_fraction_multiply (from_dar_n, from_dar_d, set_par_d,
1624               set_par_n, &num, &den)) {
1625         GST_ELEMENT_ERROR (viewconvert, CORE, NEGOTIATION, (NULL),
1626             ("Error calculating the output scaled size - integer overflow"));
1627         goto done;
1628       }
1629
1630       w = (guint) gst_util_uint64_scale_int (set_h, num, den);
1631       tmp = gst_structure_copy (outs);
1632       gst_structure_fixate_field_nearest_int (tmp, "width", w);
1633       gst_structure_get_int (tmp, "width", &tmp2);
1634       gst_structure_free (tmp);
1635
1636       if (tmp2 == w) {
1637         gst_structure_set (outs, "width", G_TYPE_INT, tmp2, "height",
1638             G_TYPE_INT, set_h, NULL);
1639         if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
1640             set_par_n != set_par_d)
1641           gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
1642               set_par_n, set_par_d, NULL);
1643         goto done;
1644       }
1645
1646       /* ... or try the same with the height */
1647       h = (guint) gst_util_uint64_scale_int (set_w, den, num);
1648       tmp = gst_structure_copy (outs);
1649       gst_structure_fixate_field_nearest_int (tmp, "height", h);
1650       gst_structure_get_int (tmp, "height", &tmp2);
1651       gst_structure_free (tmp);
1652
1653       if (tmp2 == h) {
1654         gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height",
1655             G_TYPE_INT, tmp2, NULL);
1656         if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
1657             set_par_n != set_par_d)
1658           gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
1659               set_par_n, set_par_d, NULL);
1660         goto done;
1661       }
1662
1663       /* If all fails we can't keep the DAR and take the nearest values
1664        * for everything from the first try */
1665       gst_structure_set (outs, "width", G_TYPE_INT, set_w, "height",
1666           G_TYPE_INT, set_h, NULL);
1667       if (gst_structure_has_field (outs, "pixel-aspect-ratio") ||
1668           set_par_n != set_par_d)
1669         gst_structure_set (outs, "pixel-aspect-ratio", GST_TYPE_FRACTION,
1670             set_par_n, set_par_d, NULL);
1671     }
1672   }
1673
1674 done:
1675   othercaps = gst_caps_fixate (othercaps);
1676
1677   GST_DEBUG_OBJECT (viewconvert, "fixated othercaps to %" GST_PTR_FORMAT,
1678       othercaps);
1679
1680   if (from_par == &fpar)
1681     g_value_unset (&fpar);
1682   if (to_par == &tpar)
1683     g_value_unset (&tpar);
1684
1685   return othercaps;
1686 }
1687
1688 static GstCaps *
1689 gst_vulkan_view_convert_fixate_caps (GstBaseTransform * bt,
1690     GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
1691 {
1692   GstVulkanViewConvert *viewconvert = GST_VULKAN_VIEW_CONVERT (bt);
1693   GstVideoMultiviewMode mode = viewconvert->output_mode_override;
1694   GstVideoMultiviewFlags flags = viewconvert->output_flags_override;
1695   GstCaps *tmp;
1696
1697   othercaps = gst_caps_make_writable (othercaps);
1698   GST_LOG_OBJECT (viewconvert, "dir %s fixating %" GST_PTR_FORMAT
1699       " against caps %" GST_PTR_FORMAT,
1700       direction == GST_PAD_SINK ? "sink" : "src", othercaps, caps);
1701
1702   if (direction == GST_PAD_SINK) {
1703     if (mode != GST_VIDEO_MULTIVIEW_MODE_NONE) {
1704       /* We have a requested output mode and are fixating source caps, try and enforce it */
1705       GST_DEBUG_OBJECT (bt, "fixating multiview mode using the configured "
1706           "output override mode 0x%x and flags 0x%x", mode, flags);
1707       tmp = _intersect_with_mview_mode (othercaps, mode, flags);
1708       gst_caps_unref (othercaps);
1709       othercaps = tmp;
1710     } else {
1711       /* See if we can do passthrough */
1712       GstVideoInfo info;
1713
1714       if (gst_video_info_from_caps (&info, caps)) {
1715         GstVideoMultiviewMode mode = GST_VIDEO_INFO_MULTIVIEW_MODE (&info);
1716         GstVideoMultiviewFlags flags = GST_VIDEO_INFO_MULTIVIEW_FLAGS (&info);
1717
1718         if (viewconvert->input_mode_override != GST_VIDEO_MULTIVIEW_MODE_NONE) {
1719           mode = viewconvert->input_mode_override;
1720           flags = viewconvert->input_flags_override;
1721         }
1722
1723         tmp = _intersect_with_mview_mode (othercaps, mode, flags);
1724         if (gst_caps_is_empty (tmp)) {
1725           /* Nope, we can't pass our input caps downstream */
1726           gst_caps_unref (tmp);
1727         } else {
1728           GST_DEBUG_OBJECT (bt, "can configure a passthrough multiview mode "
1729               "using the input override mode 0x%x and flags 0x%x", mode, flags);
1730           gst_caps_unref (othercaps);
1731           othercaps = tmp;
1732           goto done;
1733         }
1734       }
1735
1736       /* Prefer an unpacked mode for output */
1737       tmp =
1738           _intersect_with_mview_modes (othercaps,
1739           gst_video_multiview_get_unpacked_modes ());
1740       if (!gst_caps_is_empty (tmp)) {
1741         GST_DEBUG_OBJECT (bt, "preferring an unpacked multiview mode");
1742         gst_caps_unref (othercaps);
1743         othercaps = tmp;
1744       } else {
1745         gst_caps_unref (tmp);
1746       }
1747     }
1748   } else if (viewconvert->input_mode_override != GST_VIDEO_MULTIVIEW_MODE_NONE) {
1749     /* See if we can coerce the caps into matching input mode/flags,
1750      * in case it doesn't care at all, but allow it not to too */
1751     mode = viewconvert->input_mode_override;
1752     flags = viewconvert->input_flags_override;
1753     tmp = _intersect_with_mview_mode (othercaps, mode, flags);
1754     if (gst_caps_is_empty (tmp)) {
1755       /* Nope, we can pass our input caps downstream */
1756       gst_caps_unref (tmp);
1757     } else {
1758       GST_DEBUG_OBJECT (bt, "can configure a passthrough multiview mode "
1759           "using the input override mode 0x%x and flags 0x%x", mode, flags);
1760       gst_caps_unref (othercaps);
1761       othercaps = tmp;
1762     }
1763   }
1764
1765 done:
1766   othercaps = fixate_size (viewconvert, direction, caps, othercaps);
1767   othercaps = gst_caps_fixate (othercaps);
1768
1769   GST_DEBUG_OBJECT (viewconvert, "dir %s fixated to %" GST_PTR_FORMAT
1770       " against caps %" GST_PTR_FORMAT,
1771       direction == GST_PAD_SINK ? "sink" : "src", othercaps, caps);
1772   return othercaps;
1773 }
1774
1775 static gboolean
1776 create_descriptor_set_layout (GstVulkanViewConvert * conv, GError ** error)
1777 {
1778   GstVulkanVideoFilter *vfilter = GST_VULKAN_VIDEO_FILTER (conv);
1779   VkDescriptorSetLayoutBinding bindings[GST_VIDEO_MAX_PLANES * 2 + 1] =
1780       { {0,}, };
1781   VkDescriptorSetLayoutCreateInfo layout_info;
1782   VkDescriptorSetLayout descriptor_set_layout;
1783   int descriptor_n = 0;
1784   VkResult err;
1785   int i;
1786
1787   /* *INDENT-OFF* */
1788   bindings[descriptor_n++] = (VkDescriptorSetLayoutBinding) {
1789       .binding = 0,
1790       .descriptorCount = 1,
1791       .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
1792       .pImmutableSamplers = NULL,
1793       .stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT
1794   };
1795   for (i = 0; i < GST_VIDEO_INFO_N_PLANES (&vfilter->in_info) * 2; i++) {
1796     bindings[descriptor_n++] = (VkDescriptorSetLayoutBinding) {
1797       .binding = i+1,
1798       .descriptorCount = 1,
1799       .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
1800       .pImmutableSamplers = NULL,
1801       .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT
1802     };
1803   };
1804
1805   layout_info = (VkDescriptorSetLayoutCreateInfo) {
1806       .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
1807       .pNext = NULL,
1808       .bindingCount = descriptor_n,
1809       .pBindings = bindings
1810   };
1811   /* *INDENT-ON* */
1812
1813   err =
1814       vkCreateDescriptorSetLayout (vfilter->device->device, &layout_info,
1815       NULL, &descriptor_set_layout);
1816   if (gst_vulkan_error_to_g_error (err, error,
1817           "vkCreateDescriptorSetLayout") < 0) {
1818     return FALSE;
1819   }
1820
1821   conv->quad->descriptor_set_layout =
1822       gst_vulkan_handle_new_wrapped (vfilter->device,
1823       GST_VULKAN_HANDLE_TYPE_DESCRIPTOR_SET_LAYOUT,
1824       (GstVulkanHandleTypedef) descriptor_set_layout,
1825       gst_vulkan_handle_free_descriptor_set_layout, NULL);
1826
1827   return TRUE;
1828 }
1829
1830 static gboolean
1831 gst_vulkan_view_convert_start (GstBaseTransform * bt)
1832 {
1833   GstVulkanVideoFilter *vfilter = GST_VULKAN_VIDEO_FILTER (bt);
1834   GstVulkanViewConvert *conv = GST_VULKAN_VIEW_CONVERT (bt);
1835   GstVulkanHandle *vert, *frag;
1836
1837   if (!GST_BASE_TRANSFORM_CLASS (parent_class)->start (bt))
1838     return FALSE;
1839
1840   conv->quad = gst_vulkan_full_screen_quad_new (vfilter->queue);
1841
1842   if (!(vert =
1843           gst_vulkan_create_shader (vfilter->device, identity_vert,
1844               identity_vert_size, NULL))) {
1845     return FALSE;
1846   }
1847   if (!(frag =
1848           gst_vulkan_create_shader (vfilter->device, view_convert_frag,
1849               view_convert_frag_size, NULL))) {
1850     gst_vulkan_handle_unref (vert);
1851     return FALSE;
1852   }
1853
1854   if (!gst_vulkan_full_screen_quad_set_shaders (conv->quad, vert, frag)) {
1855     gst_vulkan_handle_unref (vert);
1856     gst_vulkan_handle_unref (frag);
1857     return FALSE;
1858   }
1859   gst_vulkan_handle_unref (vert);
1860   gst_vulkan_handle_unref (frag);
1861
1862   return TRUE;
1863 }
1864
1865 static gboolean
1866 gst_vulkan_view_convert_set_caps (GstBaseTransform * bt, GstCaps * in_caps,
1867     GstCaps * out_caps)
1868 {
1869   GstVulkanVideoFilter *vfilter = GST_VULKAN_VIDEO_FILTER (bt);
1870   GstVulkanViewConvert *conv = GST_VULKAN_VIEW_CONVERT (bt);
1871
1872   if (!GST_BASE_TRANSFORM_CLASS (parent_class)->set_caps (bt, in_caps,
1873           out_caps))
1874     return FALSE;
1875
1876   if (!gst_vulkan_full_screen_quad_set_info (conv->quad, &vfilter->in_info,
1877           &vfilter->out_info))
1878     return FALSE;
1879
1880   return TRUE;
1881 }
1882
1883 static gboolean
1884 gst_vulkan_view_convert_stop (GstBaseTransform * bt)
1885 {
1886   GstVulkanViewConvert *conv = GST_VULKAN_VIEW_CONVERT (bt);
1887
1888   gst_clear_object (&conv->quad);
1889   if (conv->uniform)
1890     gst_memory_unref (conv->uniform);
1891   conv->uniform = NULL;
1892
1893   return GST_BASE_TRANSFORM_CLASS (parent_class)->stop (bt);
1894 }
1895
1896 static GstFlowReturn
1897 gst_vulkan_view_convert_transform (GstBaseTransform * bt, GstBuffer * inbuf,
1898     GstBuffer * outbuf)
1899 {
1900   GstVulkanVideoFilter *vfilter = GST_VULKAN_VIDEO_FILTER (bt);
1901   GstVulkanViewConvert *conv = GST_VULKAN_VIEW_CONVERT (bt);
1902   GstVulkanImageView *in_img_views[GST_VIDEO_MAX_PLANES] = { NULL, };
1903   GstVulkanImageView *out_img_views[GST_VIDEO_MAX_PLANES] = { NULL, };
1904   GstVulkanCommandBuffer *cmd_buf = NULL;
1905   GstVulkanFence *fence = NULL;
1906   GError *error = NULL;
1907   VkResult err;
1908   int i;
1909
1910   if (!gst_vulkan_full_screen_quad_set_input_buffer (conv->quad, inbuf, &error))
1911     goto error;
1912   if (!gst_vulkan_full_screen_quad_set_output_buffer (conv->quad, outbuf,
1913           &error))
1914     goto error;
1915
1916   fence = gst_vulkan_device_create_fence (vfilter->device, &error);
1917   if (!fence)
1918     goto error;
1919
1920   for (i = 0; i < GST_VIDEO_INFO_N_PLANES (&conv->quad->in_info); i++) {
1921     GstMemory *img_mem = gst_buffer_peek_memory (inbuf, i);
1922     if (!gst_is_vulkan_image_memory (img_mem)) {
1923       g_set_error_literal (&error, GST_VULKAN_ERROR, GST_VULKAN_FAILED,
1924           "Input memory must be a GstVulkanImageMemory");
1925       goto error;
1926     }
1927     in_img_views[i] =
1928         gst_vulkan_get_or_create_image_view ((GstVulkanImageMemory *) img_mem);
1929     gst_vulkan_trash_list_add (conv->quad->trash_list,
1930         gst_vulkan_trash_list_acquire (conv->quad->trash_list, fence,
1931             gst_vulkan_trash_mini_object_unref,
1932             (GstMiniObject *) in_img_views[i]));
1933   }
1934   for (i = 0; i < GST_VIDEO_INFO_N_PLANES (&conv->quad->out_info); i++) {
1935     GstMemory *mem = gst_buffer_peek_memory (outbuf, i);
1936     if (!gst_is_vulkan_image_memory (mem)) {
1937       g_set_error_literal (&error, GST_VULKAN_ERROR, GST_VULKAN_FAILED,
1938           "Output memory must be a GstVulkanImageMemory");
1939       goto error;
1940     }
1941     out_img_views[i] =
1942         gst_vulkan_get_or_create_image_view ((GstVulkanImageMemory *) mem);
1943     gst_vulkan_trash_list_add (conv->quad->trash_list,
1944         gst_vulkan_trash_list_acquire (conv->quad->trash_list, fence,
1945             gst_vulkan_trash_mini_object_unref,
1946             (GstMiniObject *) out_img_views[i]));
1947   }
1948
1949   {
1950     GstMemory *uniforms = get_uniforms (conv, in_img_views, out_img_views);
1951     if (!gst_vulkan_full_screen_quad_set_uniform_buffer (conv->quad, uniforms,
1952             &error))
1953       goto error;
1954     gst_memory_unref (uniforms);
1955   }
1956
1957   if (!conv->quad->descriptor_set_layout)
1958     if (!create_descriptor_set_layout (conv, &error))
1959       goto error;
1960
1961   if (!gst_vulkan_full_screen_quad_prepare_draw (conv->quad, fence, &error))
1962     goto error;
1963
1964   if (!(cmd_buf =
1965           gst_vulkan_command_pool_create (conv->quad->cmd_pool, &error)))
1966     goto error;
1967
1968   {
1969     VkCommandBufferBeginInfo cmd_buf_info = { 0, };
1970
1971     /* *INDENT-OFF* */
1972     cmd_buf_info = (VkCommandBufferBeginInfo) {
1973         .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
1974         .pNext = NULL,
1975         .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
1976         .pInheritanceInfo = NULL
1977     };
1978     /* *INDENT-ON* */
1979
1980     gst_vulkan_command_buffer_lock (cmd_buf);
1981     err = vkBeginCommandBuffer (cmd_buf->cmd, &cmd_buf_info);
1982     if (gst_vulkan_error_to_g_error (err, &error, "vkBeginCommandBuffer") < 0)
1983       goto error;
1984   }
1985
1986   update_descriptor_set (conv, in_img_views);
1987   if (!gst_vulkan_full_screen_quad_fill_command_buffer (conv->quad, cmd_buf,
1988           fence, &error))
1989     goto unlock_error;
1990
1991   err = vkEndCommandBuffer (cmd_buf->cmd);
1992   gst_vulkan_command_buffer_unlock (cmd_buf);
1993   if (gst_vulkan_error_to_g_error (err, &error, "vkEndCommandBuffer") < 0)
1994     goto error;
1995
1996   if (!gst_vulkan_full_screen_quad_submit (conv->quad, cmd_buf, fence, &error))
1997     goto error;
1998
1999   gst_vulkan_fence_unref (fence);
2000
2001   return GST_FLOW_OK;
2002
2003 unlock_error:
2004   if (cmd_buf) {
2005     gst_vulkan_command_buffer_unlock (cmd_buf);
2006     gst_vulkan_command_buffer_unref (cmd_buf);
2007   }
2008
2009 error:
2010   gst_clear_mini_object ((GstMiniObject **) & fence);
2011
2012   GST_ELEMENT_ERROR (bt, LIBRARY, FAILED, ("%s", error->message), (NULL));
2013   g_clear_error (&error);
2014   return GST_FLOW_ERROR;
2015 }