/* * GStreamer * Copyright (C) 2019 Matthew Waters * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * SECTION:element-vulkanimageidentity * @title: vulkanimgeidentity * * vulkanimageidentity produces a vulkan image that is a copy of the input image. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "vkcolorconvert.h" #include "vkshader.h" #include "shaders/identity.vert.h" #include "shaders/swizzle.frag.h" #include "shaders/swizzle_and_clobber_alpha.frag.h" #include "shaders/yuy2_to_rgb.frag.h" #include "shaders/ayuv_to_rgb.frag.h" #include "shaders/nv12_to_rgb.frag.h" #include "shaders/rgb_to_ayuv.frag.h" #include "shaders/rgb_to_yuy2.frag.h" #include "shaders/rgb_to_nv12.frag.h" GST_DEBUG_CATEGORY (gst_debug_vulkan_color_convert); #define GST_CAT_DEFAULT gst_debug_vulkan_color_convert #define N_SHADER_INFO (8*16) static shader_info shader_infos[N_SHADER_INFO]; #define PUSH_CONSTANT_RANGE_NULL_INIT (VkPushConstantRange) { \ .stageFlags = 0, \ .offset = 0, \ .size = 0, \ } static void get_rgb_format_swizzle_order (GstVideoFormat format, gint swizzle[GST_VIDEO_MAX_COMPONENTS]) { const GstVideoFormatInfo *finfo = gst_video_format_get_info (format); int c_i = 0, i; g_return_if_fail (finfo->flags & GST_VIDEO_FORMAT_FLAG_RGB || format == GST_VIDEO_FORMAT_AYUV); for (i = 0; i < finfo->n_components; i++) { swizzle[c_i++] = finfo->poffset[i]; } /* special case spaced RGB formats as the space does not contain a poffset * value and we need all four components to be valid in order to swizzle * correctly */ if (format == GST_VIDEO_FORMAT_xRGB || format == GST_VIDEO_FORMAT_xBGR) { swizzle[c_i++] = 0; } else if (format == GST_VIDEO_FORMAT_RGBx || format == GST_VIDEO_FORMAT_BGRx) { swizzle[c_i++] = 3; } else { for (i = finfo->n_components; i < GST_VIDEO_MAX_COMPONENTS; i++) { swizzle[c_i++] = -1; } } } static void get_vulkan_rgb_format_swizzle_order (VkFormat format, gint * swizzle, guint swizzle_count, guint offset) { const GstVulkanFormatInfo *finfo = gst_vulkan_format_get_info (format); int i; g_return_if_fail (finfo->flags & GST_VULKAN_FORMAT_FLAG_RGB); g_return_if_fail (finfo->n_components <= swizzle_count); for (i = 0; i < finfo->n_components; i++) { swizzle[i] = offset + finfo->poffset[i]; } for (i = finfo->n_components; i < swizzle_count; i++) { swizzle[i] = -1; } } /* given a swizzle index, produce an index such that: * * swizzle[idx[i]] == identity[i] where: * - swizzle is the original swizzle * - idx is the result * - identity = {0, 1, 2,...} * - unset fields are marked by -1 */ static void swizzle_identity_order (gint * swizzle, gint * idx) { int i; for (i = 0; i < GST_VIDEO_MAX_COMPONENTS; i++) { idx[i] = -1; } for (i = 0; i < GST_VIDEO_MAX_COMPONENTS; i++) { if (swizzle[i] >= 0 && swizzle[i] < 4 && idx[swizzle[i]] == -1) { idx[swizzle[i]] = i; } } } typedef struct { double dm[4][4]; } Matrix4; static void matrix_debug (const Matrix4 * s) { GST_DEBUG ("[%f %f %f %f]", s->dm[0][0], s->dm[0][1], s->dm[0][2], s->dm[0][3]); GST_DEBUG ("[%f %f %f %f]", s->dm[1][0], s->dm[1][1], s->dm[1][2], s->dm[1][3]); GST_DEBUG ("[%f %f %f %f]", s->dm[2][0], s->dm[2][1], s->dm[2][2], s->dm[2][3]); GST_DEBUG ("[%f %f %f %f]", s->dm[3][0], s->dm[3][1], s->dm[3][2], s->dm[3][3]); } static void matrix_to_float (const Matrix4 * m, float *ret) { int i, j; for (i = 0; i < 4; i++) { for (j = 0; j < 4; j++) { ret[j * 4 + i] = m->dm[i][j]; } } } static void matrix_set_identity (Matrix4 * m) { int i, j; for (i = 0; i < 4; i++) { for (j = 0; j < 4; j++) { m->dm[i][j] = (i == j); } } } static void matrix_copy (Matrix4 * d, const Matrix4 * s) { gint i, j; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) d->dm[i][j] = s->dm[i][j]; } /* Perform 4x4 matrix multiplication: * - @dst@ = @a@ * @b@ * - @dst@ may be a pointer to @a@ andor @b@ */ static void matrix_multiply (Matrix4 * dst, Matrix4 * a, Matrix4 * b) { Matrix4 tmp; int i, j, k; for (i = 0; i < 4; i++) { for (j = 0; j < 4; j++) { double x = 0; for (k = 0; k < 4; k++) { x += a->dm[i][k] * b->dm[k][j]; } tmp.dm[i][j] = x; } } matrix_copy (dst, &tmp); } #if 0 static void matrix_invert (Matrix4 * d, Matrix4 * s) { Matrix4 tmp; int i, j; double det; matrix_set_identity (&tmp); for (j = 0; j < 3; j++) { for (i = 0; i < 3; i++) { tmp.dm[j][i] = s->dm[(i + 1) % 3][(j + 1) % 3] * s->dm[(i + 2) % 3][(j + 2) % 3] - s->dm[(i + 1) % 3][(j + 2) % 3] * s->dm[(i + 2) % 3][(j + 1) % 3]; } } det = tmp.dm[0][0] * s->dm[0][0] + tmp.dm[0][1] * s->dm[1][0] + tmp.dm[0][2] * s->dm[2][0]; for (j = 0; j < 3; j++) { for (i = 0; i < 3; i++) { tmp.dm[i][j] /= det; } } matrix_copy (d, &tmp); } #endif static void matrix_offset_components (Matrix4 * m, double a1, double a2, double a3) { Matrix4 a; matrix_set_identity (&a); a.dm[0][3] = a1; a.dm[1][3] = a2; a.dm[2][3] = a3; matrix_debug (&a); matrix_multiply (m, &a, m); } static void matrix_scale_components (Matrix4 * m, double a1, double a2, double a3) { Matrix4 a; matrix_set_identity (&a); a.dm[0][0] = a1; a.dm[1][1] = a2; a.dm[2][2] = a3; matrix_multiply (m, &a, m); } static void matrix_YCbCr_to_RGB (Matrix4 * m, double Kr, double Kb) { double Kg = 1.0 - Kr - Kb; Matrix4 k = { { {1., 0., 2 * (1 - Kr), 0.}, {1., -2 * Kb * (1 - Kb) / Kg, -2 * Kr * (1 - Kr) / Kg, 0.}, {1., 2 * (1 - Kb), 0., 0.}, {0., 0., 0., 1.}, } }; matrix_multiply (m, &k, m); } typedef struct { GstVideoInfo in_info; GstVideoInfo out_info; Matrix4 to_RGB_matrix; Matrix4 to_YUV_matrix; Matrix4 convert_matrix; } ConvertInfo; static void convert_to_RGB (ConvertInfo * conv, Matrix4 * m) { GstVideoInfo *info = &conv->in_info; { const GstVideoFormatInfo *uinfo; gint offset[4], scale[4], depth[4]; int i; uinfo = gst_video_format_get_info (GST_VIDEO_INFO_FORMAT (info)); /* bring color components to [0..1.0] range */ gst_video_color_range_offsets (info->colorimetry.range, uinfo, offset, scale); for (i = 0; i < uinfo->n_components; i++) depth[i] = (1 << uinfo->depth[i]) - 1; matrix_offset_components (m, -offset[0] / (float) depth[0], -offset[1] / (float) depth[1], -offset[2] / (float) depth[2]); matrix_scale_components (m, depth[0] / ((float) scale[0]), depth[1] / ((float) scale[1]), depth[2] / ((float) scale[2])); GST_DEBUG ("to RGB scale/offset matrix"); matrix_debug (m); } if (GST_VIDEO_INFO_IS_YUV (info)) { gdouble Kr, Kb; if (gst_video_color_matrix_get_Kr_Kb (info->colorimetry.matrix, &Kr, &Kb)) matrix_YCbCr_to_RGB (m, Kr, Kb); GST_DEBUG ("to RGB matrix"); matrix_debug (m); } } static void matrix_RGB_to_YCbCr (Matrix4 * m, double Kr, double Kb) { double Kg = 1.0 - Kr - Kb; Matrix4 k; double x; k.dm[0][0] = Kr; k.dm[0][1] = Kg; k.dm[0][2] = Kb; k.dm[0][3] = 0; x = 1 / (2 * (1 - Kb)); k.dm[1][0] = -x * Kr; k.dm[1][1] = -x * Kg; k.dm[1][2] = x * (1 - Kb); k.dm[1][3] = 0; x = 1 / (2 * (1 - Kr)); k.dm[2][0] = x * (1 - Kr); k.dm[2][1] = -x * Kg; k.dm[2][2] = -x * Kb; k.dm[2][3] = 0; k.dm[3][0] = 0; k.dm[3][1] = 0; k.dm[3][2] = 0; k.dm[3][3] = 1; matrix_multiply (m, &k, m); } static void convert_to_YUV (ConvertInfo * conv, Matrix4 * m) { GstVideoInfo *info = &conv->out_info; if (GST_VIDEO_INFO_IS_YUV (info)) { gdouble Kr, Kb; if (gst_video_color_matrix_get_Kr_Kb (info->colorimetry.matrix, &Kr, &Kb)) matrix_RGB_to_YCbCr (m, Kr, Kb); GST_DEBUG ("to YUV matrix"); matrix_debug (m); } { const GstVideoFormatInfo *uinfo; gint offset[4], scale[4], depth[4]; int i; uinfo = gst_video_format_get_info (GST_VIDEO_INFO_FORMAT (info)); /* bring color components to nominal range */ gst_video_color_range_offsets (info->colorimetry.range, uinfo, offset, scale); for (i = 0; i < uinfo->n_components; i++) depth[i] = (1 << uinfo->depth[i]) - 1; matrix_scale_components (m, scale[0] / (float) depth[0], scale[1] / (float) depth[1], scale[2] / (float) depth[2]); matrix_offset_components (m, offset[0] / (float) depth[0], offset[1] / (float) depth[1], offset[2] / (float) depth[2]); GST_DEBUG ("to YUV scale/offset matrix"); matrix_debug (m); } } #if 0 static void matrix_RGB_to_XYZ (Matrix4 * dst, double Rx, double Ry, double Gx, double Gy, double Bx, double By, double Wx, double Wy) { Matrix4 m, im; double sx, sy, sz; double wx, wy, wz; matrix_set_identity (&m); m.dm[0][0] = Rx; m.dm[1][0] = Ry; m.dm[2][0] = (1.0 - Rx - Ry); m.dm[0][1] = Gx; m.dm[1][1] = Gy; m.dm[2][1] = (1.0 - Gx - Gy); m.dm[0][2] = Bx; m.dm[1][2] = By; m.dm[2][2] = (1.0 - Bx - By); matrix_invert (&im, &m); wx = Wx / Wy; wy = 1.0; wz = (1.0 - Wx - Wy) / Wy; sx = im.dm[0][0] * wx + im.dm[0][1] * wy + im.dm[0][2] * wz; sy = im.dm[1][0] * wx + im.dm[1][1] * wy + im.dm[1][2] * wz; sz = im.dm[2][0] * wx + im.dm[2][1] * wy + im.dm[2][2] * wz; m.dm[0][0] *= sx; m.dm[1][0] *= sx; m.dm[2][0] *= sx; m.dm[0][1] *= sy; m.dm[1][1] *= sy; m.dm[2][1] *= sy; m.dm[0][2] *= sz; m.dm[1][2] *= sz; m.dm[2][2] *= sz; matrix_copy (dst, &m); } static void convert_primaries (ConvertInfo * conv) { gboolean same_matrix, same_primaries; Matrix4 p1, p2; same_matrix = conv->in_info.colorimetry.matrix == conv->out_info.colorimetry.matrix; same_primaries = conv->in_info.colorimetry.primaries == conv->out_info.colorimetry.primaries; GST_DEBUG ("matrix %d -> %d (%d)", conv->in_info.colorimetry.matrix, conv->out_info.colorimetry.matrix, same_matrix); GST_DEBUG ("primaries %d -> %d (%d)", conv->in_info.colorimetry.primaries, conv->out_info.colorimetry.primaries, same_primaries); matrix_set_identity (&conv->convert_matrix); if (!same_primaries) { const GstVideoColorPrimariesInfo *pi; pi = gst_video_color_primaries_get_info (conv->in_info.colorimetry. primaries); matrix_RGB_to_XYZ (&p1, pi->Rx, pi->Ry, pi->Gx, pi->Gy, pi->Bx, pi->By, pi->Wx, pi->Wy); GST_DEBUG ("to XYZ matrix"); matrix_debug (&p1); GST_DEBUG ("current matrix"); matrix_multiply (&conv->convert_matrix, &conv->convert_matrix, &p1); matrix_debug (&conv->convert_matrix); pi = gst_video_color_primaries_get_info (conv->out_info.colorimetry. primaries); matrix_RGB_to_XYZ (&p2, pi->Rx, pi->Ry, pi->Gx, pi->Gy, pi->Bx, pi->By, pi->Wx, pi->Wy); matrix_invert (&p2, &p2); GST_DEBUG ("to RGB matrix"); matrix_debug (&p2); matrix_multiply (&conv->convert_matrix, &conv->convert_matrix, &p2); GST_DEBUG ("current matrix"); matrix_debug (&conv->convert_matrix); } } #endif static ConvertInfo * convert_info_new (GstVideoInfo * in_info, GstVideoInfo * out_info) { ConvertInfo *conv = g_new0 (ConvertInfo, 1); matrix_set_identity (&conv->to_RGB_matrix); matrix_set_identity (&conv->convert_matrix); matrix_set_identity (&conv->to_YUV_matrix); memcpy (&conv->in_info, in_info, sizeof (*in_info)); memcpy (&conv->out_info, out_info, sizeof (*out_info)); convert_to_RGB (conv, &conv->to_RGB_matrix); /* by default videoconvert does not convert primaries convert_primaries (conv); */ convert_to_YUV (conv, &conv->to_YUV_matrix); return conv; } static void update_descriptor_set (GstVulkanColorConvert * conv, VkImageView * views, guint n_views) { GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (conv); VkDescriptorBufferInfo buffer_info; VkDescriptorImageInfo image_info[GST_VIDEO_MAX_PLANES]; VkWriteDescriptorSet writes[5]; guint i = 0; for (; i < GST_VIDEO_INFO_N_PLANES (&render->in_info); i++) { /* *INDENT-OFF* */ image_info[i] = (VkDescriptorImageInfo) { .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, .imageView = views[i], .sampler = conv->sampler }; g_assert (i < n_views); g_assert (i < GST_VIDEO_MAX_PLANES); writes[i] = (VkWriteDescriptorSet) { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .pNext = NULL, .dstSet = conv->descriptor_set, .dstBinding = i, .dstArrayElement = 0, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1, .pImageInfo = &image_info[i] }; /* *INDENT-ON* */ } if (conv->uniform && conv->current_shader->uniform_size) { /* *INDENT-OFF* */ buffer_info = (VkDescriptorBufferInfo) { .buffer = ((GstVulkanBufferMemory *) conv->uniform)->buffer, .offset = 0, .range = conv->current_shader->uniform_size }; writes[i] = (VkWriteDescriptorSet) { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .pNext = NULL, .dstSet = conv->descriptor_set, .dstBinding = i, .dstArrayElement = 0, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1, .pBufferInfo = &buffer_info }; /* *INDENT-ON* */ i++; }; g_assert (i <= G_N_ELEMENTS (writes)); vkUpdateDescriptorSets (render->device->device, i, writes, 0, NULL); } static void video_format_to_reorder (GstVideoFormat v_format, gint * reorder, gboolean input) { switch (v_format) { case GST_VIDEO_FORMAT_RGBA: case GST_VIDEO_FORMAT_RGBx: case GST_VIDEO_FORMAT_BGRA: case GST_VIDEO_FORMAT_BGRx: case GST_VIDEO_FORMAT_ARGB: case GST_VIDEO_FORMAT_xRGB: case GST_VIDEO_FORMAT_ABGR: case GST_VIDEO_FORMAT_xBGR: case GST_VIDEO_FORMAT_AYUV: get_rgb_format_swizzle_order (v_format, reorder); break; case GST_VIDEO_FORMAT_UYVY: reorder[0] = 1; reorder[1] = 0; reorder[2] = input ? 3 : 2; reorder[3] = 0; break; case GST_VIDEO_FORMAT_YUY2: reorder[0] = 0; reorder[1] = 1; reorder[2] = 0; reorder[3] = input ? 3 : 2; break; case GST_VIDEO_FORMAT_NV12: reorder[0] = 0; reorder[1] = 1; reorder[2] = 2; reorder[3] = 0; break; default: g_assert_not_reached (); break; } GST_TRACE ("swizzle: %u, %u, %u, %u", reorder[0], reorder[1], reorder[2], reorder[3]); } static guint finfo_get_plane_n_components (const GstVideoFormatInfo * finfo, guint plane) { guint n_components = 0, i; switch (finfo->format) { case GST_VIDEO_FORMAT_RGBx: case GST_VIDEO_FORMAT_xRGB: case GST_VIDEO_FORMAT_BGRx: case GST_VIDEO_FORMAT_xBGR: /* fixup spaced RGB formats as we treat the space as a normal alpha * component */ return plane == 0 ? 4 : 0; default: break; } for (i = 0; i < finfo->n_components; i++) { if (finfo->plane[i] == plane) n_components++; } return n_components; } static void get_vulkan_format_swizzle_order (GstVideoFormat v_format, VkFormat vk_format[GST_VIDEO_MAX_PLANES], gint swizzle[GST_VIDEO_MAX_COMPONENTS]) { const GstVideoFormatInfo *finfo; int i, prev_in_i = 0; finfo = gst_video_format_get_info (v_format); for (i = 0; i < finfo->n_planes; i++) { guint plane_components = finfo_get_plane_n_components (finfo, i); get_vulkan_rgb_format_swizzle_order (vk_format[i], &swizzle[prev_in_i], plane_components, prev_in_i); prev_in_i += plane_components; } if (v_format == GST_VIDEO_FORMAT_YUY2 || v_format == GST_VIDEO_FORMAT_UYVY) { /* Fixup these packed YUV formats as we use a two component format for * a 4-component pixel and access two samples in the shader */ g_assert (swizzle[0] == 0); g_assert (swizzle[1] == 1); swizzle[2] = 2; swizzle[3] = 3; } GST_TRACE ("%s: %i, %i, %i, %i", finfo->name, swizzle[0], swizzle[1], swizzle[2], swizzle[3]); } static void calculate_reorder_indexes (GstVideoFormat in_format, GstVulkanImageMemory * in_mems[GST_VIDEO_MAX_COMPONENTS], GstVideoFormat out_format, GstVulkanImageMemory * out_mems[GST_VIDEO_MAX_COMPONENTS], int ret_in[GST_VIDEO_MAX_COMPONENTS], int ret_out[GST_VIDEO_MAX_COMPONENTS]) { const GstVideoFormatInfo *in_finfo, *out_finfo; VkFormat in_vk_formats[GST_VIDEO_MAX_COMPONENTS]; VkFormat out_vk_formats[GST_VIDEO_MAX_COMPONENTS]; int in_vk_order[GST_VIDEO_MAX_COMPONENTS], in_reorder[GST_VIDEO_MAX_COMPONENTS]; int out_vk_order[GST_VIDEO_MAX_COMPONENTS], out_reorder[GST_VIDEO_MAX_COMPONENTS]; int tmp[GST_VIDEO_MAX_PLANES]; int i; in_finfo = gst_video_format_get_info (in_format); out_finfo = gst_video_format_get_info (out_format); for (i = 0; i < in_finfo->n_planes; i++) in_vk_formats[i] = in_mems[i]->create_info.format; for (i = 0; i < out_finfo->n_planes; i++) out_vk_formats[i] = out_mems[i]->create_info.format; get_vulkan_format_swizzle_order (in_format, in_vk_formats, in_vk_order); video_format_to_reorder (in_format, in_reorder, TRUE); video_format_to_reorder (out_format, out_reorder, FALSE); get_vulkan_format_swizzle_order (out_format, out_vk_formats, out_vk_order); for (i = 0; i < GST_VIDEO_MAX_COMPONENTS; i++) tmp[i] = out_vk_order[out_reorder[i]]; /* find the identity order for RGBA->$format */ GST_TRACE ("pre-invert: %u, %u, %u, %u", tmp[0], tmp[1], tmp[2], tmp[3]); if (out_format == GST_VIDEO_FORMAT_YUY2 || out_format == GST_VIDEO_FORMAT_UYVY) { for (i = 0; i < GST_VIDEO_MAX_COMPONENTS; i++) ret_out[i] = tmp[i]; } else { swizzle_identity_order (tmp, ret_out); } for (i = 0; i < GST_VIDEO_MAX_COMPONENTS; i++) ret_in[i] = in_reorder[in_vk_order[i]]; GST_TRACE ("in reorder: %u, %u, %u, %u", ret_in[0], ret_in[1], ret_in[2], ret_in[3]); GST_TRACE ("out reorder: %u, %u, %u, %u", ret_out[0], ret_out[1], ret_out[2], ret_out[3]); } static gboolean swizzle_rgb_update_command_state (GstVulkanColorConvert * conv, VkCommandBuffer cmd, shader_info * sinfo, GstVulkanImageMemory ** in_mems, GstVulkanImageMemory ** out_mems) { GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (conv); gint reorder[8]; calculate_reorder_indexes (GST_VIDEO_INFO_FORMAT (&render->in_info), in_mems, GST_VIDEO_INFO_FORMAT (&render->out_info), out_mems, reorder, &reorder[4]); vkCmdPushConstants (cmd, render->pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof (reorder), (const void *) reorder); update_descriptor_set (conv, &in_mems[0]->view, 1); vkCmdBindDescriptorSets (cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, render->pipeline_layout, 0, 1, &conv->descriptor_set, 0, NULL); return TRUE; } struct ColorMatrices { float to_RGB[16]; float primaries[16]; float to_YUV[16]; }; struct YUVUpdateData { int in_reorder[4]; int out_reorder[4]; int tex_size[2]; /* each member is aligned on 4x previous component size boundaries */ int _padding[2]; struct ColorMatrices matrices; }; static gboolean yuv_to_rgb_update_command_state (GstVulkanColorConvert * conv, VkCommandBuffer cmd, shader_info * sinfo, GstVulkanImageMemory ** in_mems, GstVulkanImageMemory ** out_mems) { GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (conv); if (!GPOINTER_TO_INT (sinfo->user_data)) { struct YUVUpdateData data; ConvertInfo *conv_info; GstMapInfo map_info; VkImageView views[GST_VIDEO_MAX_PLANES]; int i; calculate_reorder_indexes (GST_VIDEO_INFO_FORMAT (&render->in_info), in_mems, GST_VIDEO_INFO_FORMAT (&render->out_info), out_mems, data.in_reorder, data.out_reorder); conv_info = convert_info_new (&render->in_info, &render->out_info); matrix_to_float (&conv_info->to_RGB_matrix, data.matrices.to_RGB); matrix_to_float (&conv_info->convert_matrix, data.matrices.primaries); matrix_to_float (&conv_info->to_YUV_matrix, data.matrices.to_YUV); /* FIXME: keep this around */ g_free (conv_info); data.tex_size[0] = GST_VIDEO_INFO_WIDTH (&render->in_info); data.tex_size[1] = GST_VIDEO_INFO_HEIGHT (&render->in_info); if (!gst_memory_map (conv->uniform, &map_info, GST_MAP_WRITE)) { return FALSE; } memcpy (map_info.data, &data, sizeof (data)); gst_memory_unmap (conv->uniform, &map_info); for (i = 0; i < GST_VIDEO_INFO_N_PLANES (&render->in_info); i++) views[i] = in_mems[i]->view; update_descriptor_set (conv, views, GST_VIDEO_INFO_N_PLANES (&render->in_info)); sinfo->user_data = GINT_TO_POINTER (1); } vkCmdBindDescriptorSets (cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, render->pipeline_layout, 0, 1, &conv->descriptor_set, 0, NULL); return TRUE; } static void clear_user_data_flag (shader_info * sinfo) { sinfo->user_data = NULL; } static gboolean gst_vulkan_color_convert_start (GstBaseTransform * bt); static gboolean gst_vulkan_color_convert_stop (GstBaseTransform * bt); static GstCaps *gst_vulkan_color_convert_transform_caps (GstBaseTransform * bt, GstPadDirection direction, GstCaps * caps, GstCaps * filter); static GstFlowReturn gst_vulkan_color_convert_transform (GstBaseTransform * bt, GstBuffer * inbuf, GstBuffer * outbuf); static GstFlowReturn gst_vulkan_color_convert_set_caps (GstBaseTransform * bt, GstCaps * in_caps, GstCaps * out_caps); static VkAttachmentReference * gst_vulkan_color_convert_render_pass_attachment_references (GstVulkanFullScreenRender * render, guint * n_attachments); static VkAttachmentDescription * gst_vulkan_color_convert_render_pass_attachment_descriptions (GstVulkanFullScreenRender * render, guint * n_descriptions); static VkDescriptorSetLayoutBinding * gst_vulkan_color_convert_descriptor_set_layout_bindings (GstVulkanFullScreenRender * render, guint * n_bindings); static void gst_vulkan_color_convert_shader_create_info (GstVulkanFullScreenRender * render); static VkPushConstantRange * gst_vulkan_color_convert_push_constant_ranges (GstVulkanFullScreenRender * render, guint * n_constants); static GstStaticPadTemplate gst_vulkan_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_VULKAN_IMAGE, "{ BGRA, RGBA, ABGR, ARGB, BGRx, RGBx, xBGR, xRGB, AYUV, YUY2, UYVY, NV12 }"))); static GstStaticPadTemplate gst_vulkan_src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_VULKAN_IMAGE, "{ BGRA, RGBA, ABGR, ARGB, BGRx, RGBx, xBGR, xRGB, AYUV, YUY2, UYVY, NV12 }"))); enum { PROP_0, }; enum { SIGNAL_0, LAST_SIGNAL }; /* static guint gst_vulkan_color_convert_signals[LAST_SIGNAL] = { 0 }; */ #define gst_vulkan_color_convert_parent_class parent_class G_DEFINE_TYPE_WITH_CODE (GstVulkanColorConvert, gst_vulkan_color_convert, GST_TYPE_VULKAN_FULL_SCREEN_RENDER, GST_DEBUG_CATEGORY_INIT (gst_debug_vulkan_color_convert, "vulkancolorconvert", 0, "Vulkan Color Convert")); struct yuv_info { GstVideoFormat format; gchar *from_frag; gsize from_frag_size; gchar *to_frag; gsize to_frag_size; }; static void fill_shader_info (void) { GstVideoFormat rgbs[] = { GST_VIDEO_FORMAT_RGBA, GST_VIDEO_FORMAT_ARGB, GST_VIDEO_FORMAT_BGRA, GST_VIDEO_FORMAT_ABGR, GST_VIDEO_FORMAT_RGBx, GST_VIDEO_FORMAT_xRGB, GST_VIDEO_FORMAT_BGRx, GST_VIDEO_FORMAT_xBGR }; struct yuv_info yuvs[] = { {GST_VIDEO_FORMAT_AYUV, ayuv_to_rgb_frag, ayuv_to_rgb_frag_size, rgb_to_ayuv_frag, rgb_to_ayuv_frag_size}, {GST_VIDEO_FORMAT_YUY2, yuy2_to_rgb_frag, yuy2_to_rgb_frag_size, rgb_to_yuy2_frag, rgb_to_yuy2_frag_size}, {GST_VIDEO_FORMAT_UYVY, yuy2_to_rgb_frag, yuy2_to_rgb_frag_size, rgb_to_yuy2_frag, rgb_to_yuy2_frag_size}, {GST_VIDEO_FORMAT_NV12, nv12_to_rgb_frag, nv12_to_rgb_frag_size, rgb_to_nv12_frag, rgb_to_nv12_frag_size}, }; guint info_i = 0; guint i, j; /* standard RGB with alpha conversion all components are copied */ /* *INDENT-OFF* */ for (i = 0; i < G_N_ELEMENTS (rgbs); i++) { const GstVideoFormatInfo *from_finfo = gst_video_format_get_info (rgbs[i]); for (j = 0; j < G_N_ELEMENTS (rgbs); j++) { const GstVideoFormatInfo *to_finfo = gst_video_format_get_info (rgbs[j]); gboolean clobber_alpha = FALSE; GST_TRACE ("Initializing info for %s -> %s", from_finfo->name, to_finfo->name); /* copying to an RGBx variant means we can store whatever we like in the 'x' * component we choose to copy the alpha component like a standard RGBA->RGBA * swizzle. * Copying from an rgbx to a rgba format means we need to reset the * alpha value */ clobber_alpha = !GST_VIDEO_FORMAT_INFO_HAS_ALPHA (from_finfo) && GST_VIDEO_FORMAT_INFO_HAS_ALPHA (to_finfo); shader_infos[info_i++] = (shader_info) { .from = rgbs[i], .to = rgbs[j], .cmd_state_update = swizzle_rgb_update_command_state, .frag_code = clobber_alpha ? swizzle_and_clobber_alpha_frag : swizzle_frag, .frag_size = clobber_alpha ? swizzle_and_clobber_alpha_frag_size : swizzle_frag_size, .push_constant_ranges = { { .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, .offset = 0, .size = 8 * sizeof(gint32), }, PUSH_CONSTANT_RANGE_NULL_INIT, }, .notify = (GDestroyNotify) clear_user_data_flag, .user_data = NULL, }; } for (j = 0; j < G_N_ELEMENTS (yuvs); j++) { const GstVideoFormatInfo *to_finfo = gst_video_format_get_info (yuvs[j].format); GST_TRACE ("Initializing info for %s -> %s", from_finfo->name, to_finfo->name); shader_infos[info_i++] = (shader_info) { .from = rgbs[i], .to = yuvs[j].format, .cmd_state_update = yuv_to_rgb_update_command_state, .frag_code = yuvs[j].to_frag, .frag_size = yuvs[j].to_frag_size, .push_constant_ranges = { PUSH_CONSTANT_RANGE_NULL_INIT }, .uniform_size = sizeof(struct YUVUpdateData), .notify = (GDestroyNotify) clear_user_data_flag, .user_data = NULL, }; GST_TRACE ("Initializing info for %s -> %s", to_finfo->name, from_finfo->name); shader_infos[info_i++] = (shader_info) { .from = yuvs[j].format, .to = rgbs[i], .cmd_state_update = yuv_to_rgb_update_command_state, .frag_code = yuvs[j].from_frag, .frag_size = yuvs[j].from_frag_size, .push_constant_ranges = { PUSH_CONSTANT_RANGE_NULL_INIT }, .uniform_size = sizeof(struct YUVUpdateData), .notify = (GDestroyNotify) clear_user_data_flag, .user_data = NULL, }; } } /* *INDENT-ON* */ GST_TRACE ("initialized %u formats", info_i); g_assert (info_i == N_SHADER_INFO); } static void gst_vulkan_color_convert_class_init (GstVulkanColorConvertClass * klass) { GstElementClass *gstelement_class; GstBaseTransformClass *gstbasetransform_class; GstVulkanFullScreenRenderClass *fullscreenrender_class; gstelement_class = (GstElementClass *) klass; gstbasetransform_class = (GstBaseTransformClass *) klass; fullscreenrender_class = (GstVulkanFullScreenRenderClass *) klass; gst_element_class_set_metadata (gstelement_class, "Vulkan Uploader", "Filter/Video/Convert", "A Vulkan Color Convert", "Matthew Waters "); gst_element_class_add_static_pad_template (gstelement_class, &gst_vulkan_sink_template); gst_element_class_add_static_pad_template (gstelement_class, &gst_vulkan_src_template); gstbasetransform_class->start = GST_DEBUG_FUNCPTR (gst_vulkan_color_convert_start); gstbasetransform_class->stop = GST_DEBUG_FUNCPTR (gst_vulkan_color_convert_stop); gstbasetransform_class->transform_caps = gst_vulkan_color_convert_transform_caps; gstbasetransform_class->set_caps = gst_vulkan_color_convert_set_caps; gstbasetransform_class->transform = gst_vulkan_color_convert_transform; fullscreenrender_class->render_pass_attachment_references = gst_vulkan_color_convert_render_pass_attachment_references; fullscreenrender_class->render_pass_attachment_descriptions = gst_vulkan_color_convert_render_pass_attachment_descriptions; fullscreenrender_class->descriptor_set_layout_bindings = gst_vulkan_color_convert_descriptor_set_layout_bindings; fullscreenrender_class->shader_create_info = gst_vulkan_color_convert_shader_create_info; fullscreenrender_class->push_constant_ranges = gst_vulkan_color_convert_push_constant_ranges; fill_shader_info (); } static void gst_vulkan_color_convert_init (GstVulkanColorConvert * conv) { } static void _init_value_string_list (GValue * list, ...) { GValue item = G_VALUE_INIT; gchar *str; va_list args; g_value_init (list, GST_TYPE_LIST); va_start (args, list); while ((str = va_arg (args, gchar *))) { g_value_init (&item, G_TYPE_STRING); g_value_set_string (&item, str); gst_value_list_append_value (list, &item); g_value_unset (&item); } va_end (args); } static void _append_value_string_list (GValue * list, ...) { GValue item = G_VALUE_INIT; gchar *str; va_list args; va_start (args, list); while ((str = va_arg (args, gchar *))) { g_value_init (&item, G_TYPE_STRING); g_value_set_string (&item, str); gst_value_list_append_value (list, &item); g_value_unset (&item); } va_end (args); } static void _init_supported_formats (GstVulkanDevice * device, gboolean output, GValue * supported_formats) { /* Assume if device == NULL that we don't have a Vulkan device and can * do the conversion */ /* Always supported input and output formats */ _init_value_string_list (supported_formats, "RGBA", "RGB", "RGBx", "BGR", "BGRx", "BGRA", "xRGB", "xBGR", "ARGB", "ABGR", NULL); _append_value_string_list (supported_formats, "AYUV", "YUY2", "UYVY", "NV12", NULL); } /* copies the given caps */ static GstCaps * gst_vulkan_color_convert_transform_format_info (GstVulkanDevice * device, gboolean output, GstCaps * caps) { GstStructure *st; GstCapsFeatures *f; gint i, n; GstCaps *res; GValue supported_formats = G_VALUE_INIT; GValue rgb_formats = G_VALUE_INIT; GValue supported_rgb_formats = G_VALUE_INIT; /* There are effectively two modes here with the RGB/YUV transition: * 1. There is a RGB-like format as input and we can transform to YUV or, * 2. No RGB-like format as input so we can only transform to RGB-like formats * * We also filter down the list of formats depending on what the OpenGL * device supports (when provided). */ _init_value_string_list (&rgb_formats, "RGBA", "ARGB", "BGRA", "ABGR", "RGBx", "xRGB", "BGRx", "xBGR", "RGB", "BGR", "ARGB64", NULL); _init_supported_formats (device, output, &supported_formats); gst_value_intersect (&supported_rgb_formats, &rgb_formats, &supported_formats); res = gst_caps_new_empty (); n = gst_caps_get_size (caps); for (i = 0; i < n; i++) { const GValue *format; st = gst_caps_get_structure (caps, i); f = gst_caps_get_features (caps, i); format = gst_structure_get_value (st, "format"); st = gst_structure_copy (st); if (GST_VALUE_HOLDS_LIST (format)) { gboolean have_rgb_formats = FALSE; GValue passthrough_formats = G_VALUE_INIT; gint j, len; g_value_init (&passthrough_formats, GST_TYPE_LIST); len = gst_value_list_get_size (format); for (j = 0; j < len; j++) { const GValue *val; val = gst_value_list_get_value (format, j); if (G_VALUE_HOLDS_STRING (val)) { const gchar *format_str = g_value_get_string (val); GstVideoFormat v_format = gst_video_format_from_string (format_str); const GstVideoFormatInfo *t_info = gst_video_format_get_info (v_format); if (GST_VIDEO_FORMAT_INFO_FLAGS (t_info) & (GST_VIDEO_FORMAT_FLAG_YUV | GST_VIDEO_FORMAT_FLAG_GRAY)) { gst_value_list_append_value (&passthrough_formats, val); } else if (GST_VIDEO_FORMAT_INFO_FLAGS (t_info) & GST_VIDEO_FORMAT_FLAG_RGB) { have_rgb_formats = TRUE; break; } } } if (have_rgb_formats) { gst_structure_set_value (st, "format", &supported_formats); } else { /* add passthrough structure, then the rgb conversion structure */ gst_structure_set_value (st, "format", &passthrough_formats); gst_caps_append_structure_full (res, gst_structure_copy (st), gst_caps_features_copy (f)); gst_structure_set_value (st, "format", &supported_rgb_formats); } g_value_unset (&passthrough_formats); } else if (G_VALUE_HOLDS_STRING (format)) { const gchar *format_str = g_value_get_string (format); GstVideoFormat v_format = gst_video_format_from_string (format_str); const GstVideoFormatInfo *t_info = gst_video_format_get_info (v_format); if (GST_VIDEO_FORMAT_INFO_FLAGS (t_info) & (GST_VIDEO_FORMAT_FLAG_YUV | GST_VIDEO_FORMAT_FLAG_GRAY)) { /* add passthrough structure, then the rgb conversion structure */ gst_structure_set_value (st, "format", format); gst_caps_append_structure_full (res, gst_structure_copy (st), gst_caps_features_copy (f)); gst_structure_set_value (st, "format", &supported_rgb_formats); } else { /* RGB */ gst_structure_set_value (st, "format", &supported_formats); } } gst_structure_remove_fields (st, "colorimetry", "chroma-site", NULL); gst_caps_append_structure_full (res, st, gst_caps_features_copy (f)); } g_value_unset (&supported_formats); g_value_unset (&rgb_formats); g_value_unset (&supported_rgb_formats); return res; } static GstCaps * gst_vulkan_color_convert_transform_caps (GstBaseTransform * bt, GstPadDirection direction, GstCaps * caps, GstCaps * filter) { GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (bt); caps = gst_vulkan_color_convert_transform_format_info (render->device, direction == GST_PAD_SRC, caps); if (filter) { GstCaps *tmp; tmp = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); gst_caps_unref (caps); caps = tmp; } return caps; } static void destroy_shader_create_info (GstVulkanFullScreenRender * render, gpointer data) { VkPipelineShaderStageCreateInfo *info = data; int i; for (i = 0; i < render->n_shader_stages; i++) { vkDestroyShaderModule (render->device->device, info[i].module, NULL); } g_free (info); } static void gst_vulkan_color_convert_shader_create_info (GstVulkanFullScreenRender * render) { GstVulkanColorConvert *conv = GST_VULKAN_COLOR_CONVERT (render); VkShaderModule vert_module, frag_module; vert_module = _vk_create_shader (render->device, identity_vert, identity_vert_size, NULL); frag_module = _vk_create_shader (render->device, conv->current_shader->frag_code, conv->current_shader->frag_size, NULL); render->n_shader_stages = 2; render->shader_create_info = g_new0 (VkPipelineShaderStageCreateInfo, render->n_shader_stages); render->destroy_shader_create_info = destroy_shader_create_info; /* *INDENT-OFF* */ render->shader_create_info[0] = (VkPipelineShaderStageCreateInfo) { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .pNext = NULL, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = vert_module, .pName = "main" }; render->shader_create_info[1] = (VkPipelineShaderStageCreateInfo) { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .pNext = NULL, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = frag_module, .pName = "main" }; /* *INDENT-ON* */ } static VkPushConstantRange * gst_vulkan_color_convert_push_constant_ranges (GstVulkanFullScreenRender * render, guint * n_constants) { GstVulkanColorConvert *conv = GST_VULKAN_COLOR_CONVERT (render); VkPushConstantRange *ranges; int i; for (i = 0; i < G_N_ELEMENTS (conv->current_shader->push_constant_ranges); i++) { if (conv->current_shader->push_constant_ranges[i].stageFlags == 0) { break; } } *n_constants = i; GST_DEBUG_OBJECT (conv, "%s->%s has %u push constants", gst_video_format_to_string (conv->current_shader->from), gst_video_format_to_string (conv->current_shader->to), *n_constants); if (*n_constants <= 0) ranges = NULL; else ranges = g_memdup (conv->current_shader->push_constant_ranges, sizeof (VkPushConstantRange) * *n_constants); return ranges; } static VkDescriptorSetLayoutBinding * gst_vulkan_color_convert_descriptor_set_layout_bindings (GstVulkanFullScreenRender * render, guint * n_bindings) { GstVulkanColorConvert *conv = GST_VULKAN_COLOR_CONVERT (render); VkDescriptorSetLayoutBinding *bindings; guint i; *n_bindings = 0; *n_bindings += GST_VIDEO_INFO_N_PLANES (&render->in_info); if (conv->current_shader->uniform_size) *n_bindings += 1; bindings = g_new0 (VkDescriptorSetLayoutBinding, *n_bindings); GST_DEBUG_OBJECT (conv, "%s->%s has %u descriptor set layout bindings", gst_video_format_to_string (conv->current_shader->from), gst_video_format_to_string (conv->current_shader->to), *n_bindings); for (i = 0; i < GST_VIDEO_INFO_N_PLANES (&render->in_info); i++) { /* *INDENT-OFF* */ bindings[i] = (VkDescriptorSetLayoutBinding) { .binding = i, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImmutableSamplers = NULL, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT }; /* *INDENT-ON* */ } if (conv->current_shader->uniform_size) { /* *INDENT-OFF* */ bindings[i] = (VkDescriptorSetLayoutBinding) { .binding = i, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .pImmutableSamplers = NULL, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT }; /* *INDENT-ON* */ i++; } g_assert (i == *n_bindings); return bindings; } static VkAttachmentReference * gst_vulkan_color_convert_render_pass_attachment_references (GstVulkanFullScreenRender * render, guint * n_attachments) { VkAttachmentReference *attachments; int i; *n_attachments = GST_VIDEO_INFO_N_PLANES (&render->out_info); attachments = g_new0 (VkAttachmentReference, *n_attachments); for (i = 0; i < *n_attachments; i++) { /* *INDENT-OFF* */ attachments[i] = (VkAttachmentReference) { .attachment = i, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; /* *INDENT-ON* */ } return attachments; } static VkAttachmentDescription * gst_vulkan_color_convert_render_pass_attachment_descriptions (GstVulkanFullScreenRender * render, guint * n_descriptions) { VkAttachmentDescription *color_attachments; int i; *n_descriptions = GST_VIDEO_INFO_N_PLANES (&render->out_info); color_attachments = g_new0 (VkAttachmentDescription, *n_descriptions); for (i = 0; i < *n_descriptions; i++) { /* *INDENT-OFF* */ color_attachments[i] = (VkAttachmentDescription) { .format = gst_vulkan_format_from_video_format (GST_VIDEO_INFO_FORMAT (&render->out_info), i), .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, /* FIXME: share this between elements to avoid pipeline barriers */ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; /* *INDENT-ON* */ } return color_attachments; } static VkSampler _create_sampler (GstVulkanColorConvert * conv) { GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (conv); /* *INDENT-OFF* */ VkSamplerCreateInfo samplerInfo = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_LINEAR, .minFilter = VK_FILTER_LINEAR, .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, .anisotropyEnable = VK_FALSE, .maxAnisotropy = 1, .borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK, .unnormalizedCoordinates = VK_FALSE, .compareEnable = VK_FALSE, .compareOp = VK_COMPARE_OP_ALWAYS, .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, .mipLodBias = 0.0f, .minLod = 0.0f, .maxLod = 0.0f }; /* *INDENT-ON* */ GError *error = NULL; VkSampler sampler; VkResult err; err = vkCreateSampler (render->device->device, &samplerInfo, NULL, &sampler); if (gst_vulkan_error_to_g_error (err, &error, "vkCreateSampler") < 0) { GST_ERROR_OBJECT (conv, "Failed to create sampler: %s", error->message); g_clear_error (&error); return NULL; } return sampler; } static gboolean gst_vulkan_color_convert_start (GstBaseTransform * bt) { GstVulkanColorConvert *conv = GST_VULKAN_COLOR_CONVERT (bt); if (!GST_BASE_TRANSFORM_CLASS (parent_class)->start (bt)) return FALSE; if (!(conv->sampler = _create_sampler (conv))) return FALSE; return TRUE; } static VkDescriptorPool _create_descriptor_pool (GstVulkanColorConvert * conv) { GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (conv); /* *INDENT-OFF* */ VkDescriptorPoolSize pool_sizes[] = { { .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = GST_VIDEO_INFO_N_PLANES (&render->in_info), }, { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1 }, }; VkDescriptorPoolCreateInfo pool_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .pNext = NULL, .poolSizeCount = G_N_ELEMENTS (pool_sizes), .pPoolSizes = pool_sizes, .maxSets = 1 }; /* *INDENT-ON* */ VkDescriptorPool pool; GError *error = NULL; VkResult err; err = vkCreateDescriptorPool (render->device->device, &pool_info, NULL, &pool); if (gst_vulkan_error_to_g_error (err, &error, "vkCreateDescriptorPool") < 0) { GST_ERROR_OBJECT (render, "Failed to create descriptor pool: %s", error->message); g_clear_error (&error); return NULL; } return pool; } static VkDescriptorSet _create_descriptor_set (GstVulkanColorConvert * conv) { GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (conv); /* *INDENT-OFF* */ VkDescriptorSetAllocateInfo alloc_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .pNext = NULL, .descriptorPool = conv->descriptor_pool, .descriptorSetCount = 1, .pSetLayouts = &render->descriptor_set_layout }; /* *INDENT-ON* */ VkDescriptorSet descriptor; GError *error = NULL; VkResult err; err = vkAllocateDescriptorSets (render->device->device, &alloc_info, &descriptor); if (gst_vulkan_error_to_g_error (err, &error, "vkAllocateDescriptorSets") < 0) { GST_ERROR_OBJECT (conv, "Failed to allocate descriptor: %s", error->message); g_clear_error (&error); return NULL; } return descriptor; } static gboolean _create_uniform_buffer (GstVulkanColorConvert * conv) { GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (conv); if (conv->current_shader->uniform_size) { conv->uniform = gst_vulkan_buffer_memory_alloc (render->device, VK_FORMAT_R8_UNORM, conv->current_shader->uniform_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); } return TRUE; } static gboolean gst_vulkan_color_convert_set_caps (GstBaseTransform * bt, GstCaps * in_caps, GstCaps * out_caps) { GstVulkanColorConvert *conv = GST_VULKAN_COLOR_CONVERT (bt); GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (bt); GstVideoInfo in_info, out_info; int i; conv->current_shader = NULL; if (!gst_video_info_from_caps (&in_info, in_caps)) return FALSE; if (!gst_video_info_from_caps (&out_info, out_caps)) return FALSE; if (conv->current_shader) { conv->current_shader->notify (conv->current_shader); conv->current_shader = NULL; } for (i = 0; i < G_N_ELEMENTS (shader_infos); i++) { if (shader_infos[i].from != GST_VIDEO_INFO_FORMAT (&in_info)) continue; if (shader_infos[i].to != GST_VIDEO_INFO_FORMAT (&out_info)) continue; GST_INFO_OBJECT (conv, "Found compatible conversion information from %s to %s", gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (&in_info)), gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (&out_info))); conv->current_shader = &shader_infos[i]; } if (!conv->current_shader) { GST_ERROR_OBJECT (conv, "Could not find a conversion info for the " "requested formats"); return FALSE; } if (render->last_fence) { if (conv->descriptor_pool) gst_vulkan_trash_list_add (render->trash_list, gst_vulkan_trash_new_free_descriptor_pool (gst_vulkan_fence_ref (render->last_fence), conv->descriptor_pool)); conv->descriptor_set = NULL; conv->descriptor_pool = NULL; if (conv->uniform) gst_vulkan_trash_list_add (render->trash_list, gst_vulkan_trash_new_mini_object_unref (gst_vulkan_fence_ref (render->last_fence), (GstMiniObject *) conv->uniform)); conv->uniform = NULL; } else { if (conv->descriptor_pool) vkDestroyDescriptorPool (render->device->device, conv->descriptor_pool, NULL); conv->descriptor_set = NULL; conv->descriptor_pool = NULL; if (conv->uniform) gst_memory_unref (conv->uniform); conv->uniform = NULL; } if (!GST_BASE_TRANSFORM_CLASS (parent_class)->set_caps (bt, in_caps, out_caps)) return FALSE; if (!(conv->descriptor_pool = _create_descriptor_pool (conv))) return FALSE; if (!(conv->descriptor_set = _create_descriptor_set (conv))) return FALSE; if (!_create_uniform_buffer (conv)) return FALSE; return TRUE; } static gboolean gst_vulkan_color_convert_stop (GstBaseTransform * bt) { GstVulkanColorConvert *conv = GST_VULKAN_COLOR_CONVERT (bt); GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (bt); conv->current_shader = NULL; if (render->device) { if (render->last_fence) { if (conv->descriptor_pool) gst_vulkan_trash_list_add (render->trash_list, gst_vulkan_trash_new_free_descriptor_pool (gst_vulkan_fence_ref (render->last_fence), conv->descriptor_pool)); conv->descriptor_set = NULL; conv->descriptor_pool = NULL; if (conv->sampler) gst_vulkan_trash_list_add (render->trash_list, gst_vulkan_trash_new_free_sampler (gst_vulkan_fence_ref (render->last_fence), conv->sampler)); conv->sampler = NULL; if (conv->uniform) gst_vulkan_trash_list_add (render->trash_list, gst_vulkan_trash_new_mini_object_unref (gst_vulkan_fence_ref (render->last_fence), (GstMiniObject *) conv->uniform)); conv->uniform = NULL; } else { if (conv->descriptor_pool) vkDestroyDescriptorPool (render->device->device, conv->descriptor_pool, NULL); conv->descriptor_set = NULL; conv->descriptor_pool = NULL; if (conv->sampler) vkDestroySampler (render->device->device, conv->sampler, NULL); conv->sampler = NULL; if (conv->uniform) gst_memory_unref (conv->uniform); conv->uniform = NULL; } } if (conv->cmd_pool) gst_object_unref (conv->cmd_pool); conv->cmd_pool = NULL; return GST_BASE_TRANSFORM_CLASS (parent_class)->stop (bt); } static VkFramebuffer _create_framebuffer (GstVulkanColorConvert * conv, guint n_views, VkImageView * views) { GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (conv); /* *INDENT-OFF* */ VkFramebufferCreateInfo framebuffer_info = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .pNext = NULL, .renderPass = render->render_pass, .attachmentCount = n_views, .pAttachments = views, .width = GST_VIDEO_INFO_WIDTH (&render->in_info), .height = GST_VIDEO_INFO_HEIGHT (&render->in_info), .layers = 1 }; /* *INDENT-ON* */ VkFramebuffer framebuffer; GError *error = NULL; VkResult err; err = vkCreateFramebuffer (render->device->device, &framebuffer_info, NULL, &framebuffer); if (gst_vulkan_error_to_g_error (err, &error, "vkCreateFramebuffer") < 0) { GST_ERROR_OBJECT (render, "Failed to create framebuffer: %s", error->message); g_clear_error (&error); return NULL; } return framebuffer; } static GstFlowReturn gst_vulkan_color_convert_transform (GstBaseTransform * bt, GstBuffer * inbuf, GstBuffer * outbuf) { GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (bt); GstVulkanColorConvert *conv = GST_VULKAN_COLOR_CONVERT (bt); GstVulkanImageMemory *in_img_mems[GST_VIDEO_MAX_PLANES] = { NULL, }; GstVulkanImageMemory *render_img_mems[GST_VIDEO_MAX_PLANES] = { NULL, }; GstVulkanImageMemory *out_img_mems[4] = { NULL, }; GstVulkanFence *fence = NULL; VkFramebuffer framebuffer; VkCommandBuffer cmd; GError *error = NULL; VkResult err; int i; for (i = 0; i < GST_VIDEO_INFO_N_PLANES (&render->in_info); i++) { GstMemory *mem = gst_buffer_peek_memory (inbuf, i); if (!gst_is_vulkan_image_memory (mem)) { g_set_error_literal (&error, GST_VULKAN_ERROR, GST_VULKAN_FAILED, "Input memory must be a GstVulkanImageMemory"); goto error; } in_img_mems[i] = (GstVulkanImageMemory *) mem; } for (i = 0; i < GST_VIDEO_INFO_N_PLANES (&render->out_info); i++) { GstMemory *mem = gst_buffer_peek_memory (outbuf, i); if (!gst_is_vulkan_image_memory (mem)) { g_set_error_literal (&error, GST_VULKAN_ERROR, GST_VULKAN_FAILED, "Output memory must be a GstVulkanImageMemory"); goto error; } out_img_mems[i] = (GstVulkanImageMemory *) mem; } if (!conv->cmd_pool) { if (!(conv->cmd_pool = gst_vulkan_queue_create_command_pool (render->queue, &error))) goto error; } if (!(cmd = gst_vulkan_command_pool_create (conv->cmd_pool, &error))) goto error; fence = gst_vulkan_fence_new (render->device, 0, &error); if (!fence) goto error; { VkCommandBufferBeginInfo cmd_buf_info = { 0, }; /* *INDENT-OFF* */ cmd_buf_info = (VkCommandBufferBeginInfo) { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .pNext = NULL, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, .pInheritanceInfo = NULL }; /* *INDENT-ON* */ err = vkBeginCommandBuffer (cmd, &cmd_buf_info); if (gst_vulkan_error_to_g_error (err, &error, "vkBeginCommandBuffer") < 0) goto error; } for (i = 0; i < GST_VIDEO_INFO_N_PLANES (&render->in_info); i++) { /* *INDENT-OFF* */ VkImageMemoryBarrier in_image_memory_barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = NULL, .srcAccessMask = in_img_mems[i]->barrier.parent.access_flags, .dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT, .oldLayout = in_img_mems[i]->barrier.image_layout, .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, /* FIXME: implement exclusive transfers */ .srcQueueFamilyIndex = 0, .dstQueueFamilyIndex = 0, .image = in_img_mems[i]->image, .subresourceRange = in_img_mems[i]->barrier.subresource_range }; /* *INDENT-ON* */ vkCmdPipelineBarrier (cmd, in_img_mems[i]->barrier.parent.pipeline_stages, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, &in_image_memory_barrier); in_img_mems[i]->barrier.parent.pipeline_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; in_img_mems[i]->barrier.parent.access_flags = in_image_memory_barrier.dstAccessMask; in_img_mems[i]->barrier.image_layout = in_image_memory_barrier.newLayout; } for (i = 0; i < GST_VIDEO_INFO_N_PLANES (&render->out_info); i++) { VkImageMemoryBarrier render_image_memory_barrier; if (GST_VIDEO_INFO_WIDTH (&render->out_info) == GST_VIDEO_INFO_COMP_WIDTH (&render->out_info, i)) { render_img_mems[i] = out_img_mems[i]; } else { /* we need a scratch buffer because framebuffers can only output to * attachments of at least the same size which means no sub-sampled * rendering */ GstVideoFormat v_format = GST_VIDEO_INFO_FORMAT (&render->out_info); VkImageTiling tiling = VK_IMAGE_TILING_OPTIMAL; VkFormat vk_format; GstMemory *mem; vk_format = gst_vulkan_format_from_video_format (v_format, i); mem = gst_vulkan_image_memory_alloc (render->device, vk_format, GST_VIDEO_INFO_WIDTH (&render->out_info), GST_VIDEO_INFO_HEIGHT (&render->out_info), tiling, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); render_img_mems[i] = (GstVulkanImageMemory *) mem; } /* *INDENT-OFF* */ render_image_memory_barrier = (VkImageMemoryBarrier) { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = NULL, .srcAccessMask = render_img_mems[i]->barrier.parent.access_flags, .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, .oldLayout = render_img_mems[i]->barrier.image_layout, .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, /* FIXME: implement exclusive transfers */ .srcQueueFamilyIndex = 0, .dstQueueFamilyIndex = 0, .image = render_img_mems[i]->image, .subresourceRange = render_img_mems[i]->barrier.subresource_range }; /* *INDENT-ON* */ vkCmdPipelineBarrier (cmd, render_img_mems[i]->barrier.parent.pipeline_stages, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, NULL, 0, NULL, 1, &render_image_memory_barrier); render_img_mems[i]->barrier.parent.pipeline_stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; render_img_mems[i]->barrier.parent.access_flags = render_image_memory_barrier.dstAccessMask; render_img_mems[i]->barrier.image_layout = render_image_memory_barrier.newLayout; } { VkImageView attachments[4] = { 0, }; for (i = 0; i < GST_VIDEO_INFO_N_PLANES (&render->out_info); i++) { attachments[i] = render_img_mems[i]->view; } if (!(framebuffer = _create_framebuffer (conv, GST_VIDEO_INFO_N_PLANES (&render->out_info), attachments))) { g_set_error_literal (&error, GST_VULKAN_ERROR, GST_VULKAN_FAILED, "Failed to create framebuffer"); goto error; } } conv->current_shader->cmd_state_update (conv, cmd, conv->current_shader, in_img_mems, render_img_mems); if (!gst_vulkan_full_screen_render_fill_command_buffer (render, cmd, framebuffer)) return GST_FLOW_ERROR; for (i = 0; i < GST_VIDEO_INFO_N_PLANES (&render->out_info); i++) { if (render_img_mems[i] != out_img_mems[i]) { VkImageMemoryBarrier out_image_memory_barrier; VkImageMemoryBarrier render_image_memory_barrier; VkImageBlit blit; /* *INDENT-OFF* */ render_image_memory_barrier = (VkImageMemoryBarrier) { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = NULL, .srcAccessMask = render_img_mems[i]->barrier.parent.access_flags, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, .oldLayout = render_img_mems[i]->barrier.image_layout, .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, /* FIXME: implement exclusive transfers */ .srcQueueFamilyIndex = 0, .dstQueueFamilyIndex = 0, .image = render_img_mems[i]->image, .subresourceRange = render_img_mems[i]->barrier.subresource_range }; out_image_memory_barrier = (VkImageMemoryBarrier) { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = NULL, .srcAccessMask = out_img_mems[i]->barrier.parent.access_flags, .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .oldLayout = out_img_mems[i]->barrier.image_layout, .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, /* FIXME: implement exclusive transfers */ .srcQueueFamilyIndex = 0, .dstQueueFamilyIndex = 0, .image = out_img_mems[i]->image, .subresourceRange = out_img_mems[i]->barrier.subresource_range }; blit = (VkImageBlit) { .srcSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1, }, .srcOffsets = { {0, 0, 0}, {GST_VIDEO_INFO_COMP_WIDTH (&render->out_info, i), GST_VIDEO_INFO_COMP_HEIGHT (&render->out_info, i), 1}, }, .dstSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1, }, .dstOffsets = { {0, 0, 0}, {GST_VIDEO_INFO_COMP_WIDTH (&render->out_info, i), GST_VIDEO_INFO_COMP_HEIGHT (&render->out_info, i), 1}, }, }; /* *INDENT-ON* */ vkCmdPipelineBarrier (cmd, render_img_mems[i]->barrier.parent.pipeline_stages, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &render_image_memory_barrier); render_img_mems[i]->barrier.parent.pipeline_stages = VK_PIPELINE_STAGE_TRANSFER_BIT; render_img_mems[i]->barrier.parent.access_flags = render_image_memory_barrier.dstAccessMask; render_img_mems[i]->barrier.image_layout = render_image_memory_barrier.newLayout; vkCmdPipelineBarrier (cmd, out_img_mems[i]->barrier.parent.pipeline_stages, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &out_image_memory_barrier); out_img_mems[i]->barrier.parent.pipeline_stages = VK_PIPELINE_STAGE_TRANSFER_BIT; out_img_mems[i]->barrier.parent.access_flags = out_image_memory_barrier.dstAccessMask; out_img_mems[i]->barrier.image_layout = out_image_memory_barrier.newLayout; /* XXX: This is mostly right for a downsampling pass however if * anything is more complicated, then we will need a new render pass */ vkCmdBlitImage (cmd, render_img_mems[i]->image, render_img_mems[i]->barrier.image_layout, out_img_mems[i]->image, out_img_mems[i]->barrier.image_layout, 1, &blit, VK_FILTER_LINEAR); /* XXX: try to reuse this image later */ gst_vulkan_trash_list_add (render->trash_list, gst_vulkan_trash_new_mini_object_unref (gst_vulkan_fence_ref (fence), (GstMiniObject *) render_img_mems[i])); } } err = vkEndCommandBuffer (cmd); if (gst_vulkan_error_to_g_error (err, &error, "vkEndCommandBuffer") < 0) goto error; gst_vulkan_trash_list_add (render->trash_list, gst_vulkan_trash_new_free_framebuffer (gst_vulkan_fence_ref (fence), framebuffer)); gst_vulkan_trash_list_add (render->trash_list, gst_vulkan_trash_new_free_command_buffer (gst_vulkan_fence_ref (fence), conv->cmd_pool, cmd)); if (!gst_vulkan_full_screen_render_submit (render, cmd, fence)) return GST_FLOW_ERROR; return GST_FLOW_OK; error: GST_ELEMENT_ERROR (bt, LIBRARY, FAILED, ("%s", error->message), (NULL)); g_clear_error (&error); return GST_FLOW_ERROR; }