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