/* * 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 "vkimageidentity.h" #include "vkshader.h" #include "vkelementutils.h" #include "shaders/identity.vert.h" #include "shaders/identity.frag.h" GST_DEBUG_CATEGORY (gst_debug_vulkan_image_identity); #define GST_CAT_DEFAULT gst_debug_vulkan_image_identity static gboolean gst_vulkan_image_identity_start (GstBaseTransform * bt); static gboolean gst_vulkan_image_identity_stop (GstBaseTransform * bt); static GstCaps *gst_vulkan_image_identity_transform_caps (GstBaseTransform * bt, GstPadDirection direction, GstCaps * caps, GstCaps * filter); static GstFlowReturn gst_vulkan_image_identity_transform (GstBaseTransform * bt, GstBuffer * inbuf, GstBuffer * outbuf); static gboolean gst_vulkan_image_identity_set_caps (GstBaseTransform * bt, GstCaps * in_caps, GstCaps * out_caps); static VkAttachmentReference * gst_vulkan_image_identity_render_pass_attachment_references (GstVulkanFullScreenRender * render, guint * n_attachments); static VkAttachmentDescription * gst_vulkan_image_identity_render_pass_attachment_descriptions (GstVulkanFullScreenRender * render, guint * n_descriptions); static VkDescriptorSetLayoutBinding * gst_vulkan_image_identity_descriptor_set_layout_bindings (GstVulkanFullScreenRender * render, guint * n_bindings); static void gst_vulkan_image_identity_shader_create_info (GstVulkanFullScreenRender * render); #define IMAGE_FORMATS " { BGRA }" 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, IMAGE_FORMATS))); 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, IMAGE_FORMATS))); enum { PROP_0, }; enum { SIGNAL_0, LAST_SIGNAL }; /* static guint gst_vulkan_image_identity_signals[LAST_SIGNAL] = { 0 }; */ #define gst_vulkan_image_identity_parent_class parent_class G_DEFINE_TYPE_WITH_CODE (GstVulkanImageIdentity, gst_vulkan_image_identity, GST_TYPE_VULKAN_FULL_SCREEN_RENDER, GST_DEBUG_CATEGORY_INIT (gst_debug_vulkan_image_identity, "vulkanimageidentity", 0, "Vulkan Image identity")); static void gst_vulkan_image_identity_class_init (GstVulkanImageIdentityClass * 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", "A Vulkan image copier", "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_image_identity_start); gstbasetransform_class->stop = GST_DEBUG_FUNCPTR (gst_vulkan_image_identity_stop); gstbasetransform_class->transform_caps = gst_vulkan_image_identity_transform_caps; gstbasetransform_class->set_caps = gst_vulkan_image_identity_set_caps; gstbasetransform_class->transform = gst_vulkan_image_identity_transform; fullscreenrender_class->render_pass_attachment_references = gst_vulkan_image_identity_render_pass_attachment_references; fullscreenrender_class->render_pass_attachment_descriptions = gst_vulkan_image_identity_render_pass_attachment_descriptions; fullscreenrender_class->descriptor_set_layout_bindings = gst_vulkan_image_identity_descriptor_set_layout_bindings; fullscreenrender_class->shader_create_info = gst_vulkan_image_identity_shader_create_info; } static void gst_vulkan_image_identity_init (GstVulkanImageIdentity * vk_identity) { } static GstCaps * gst_vulkan_image_identity_transform_caps (GstBaseTransform * bt, GstPadDirection direction, GstCaps * caps, GstCaps * filter) { GstCaps *result, *tmp; tmp = gst_caps_copy (caps); if (filter) { result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST); gst_caps_unref (tmp); } else { result = tmp; } return result; } 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_image_identity_shader_create_info (GstVulkanFullScreenRender * 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, identity_frag, identity_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" }; } static VkDescriptorSetLayoutBinding * gst_vulkan_image_identity_descriptor_set_layout_bindings (GstVulkanFullScreenRender * render, guint * n_bindings) { VkDescriptorSetLayoutBinding *bindings; *n_bindings = 1; bindings = g_new0 (VkDescriptorSetLayoutBinding, *n_bindings); /* *INDENT-OFF* */ bindings[0] = (VkDescriptorSetLayoutBinding) { .binding = 0, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImmutableSamplers = NULL, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT }; /* *INDENT-ON* */ return bindings; } static VkAttachmentReference * gst_vulkan_image_identity_render_pass_attachment_references (GstVulkanFullScreenRender * render, guint * n_attachments) { VkAttachmentReference *attachments; *n_attachments = 1; attachments = g_new0 (VkAttachmentReference, *n_attachments); /* *INDENT-OFF* */ attachments[0] = (VkAttachmentReference) { .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; /* *INDENT-ON* */ return attachments; } static VkAttachmentDescription * gst_vulkan_image_identity_render_pass_attachment_descriptions (GstVulkanFullScreenRender * render, guint * n_descriptions) { VkAttachmentDescription *color_attachments; *n_descriptions = 1; color_attachments = g_new0 (VkAttachmentDescription, *n_descriptions); /* *INDENT-OFF* */ color_attachments[0] = (VkAttachmentDescription) { .format = gst_vulkan_format_from_video_info (&render->in_info, 0), .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 GstVulkanDescriptorCache * _create_descriptor_pool (GstVulkanImageIdentity * vk_identity) { GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (vk_identity); guint max_sets = 32; /* FIXME: Don't hardcode this! */ /* *INDENT-OFF* */ VkDescriptorPoolSize pool_sizes = { .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = max_sets }; VkDescriptorPoolCreateInfo pool_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .pNext = NULL, .poolSizeCount = 1, .pPoolSizes = &pool_sizes, .maxSets = max_sets }; /* *INDENT-ON* */ VkDescriptorPool pool; GstVulkanDescriptorPool *ret; GstVulkanDescriptorCache *cache; 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; } ret = gst_vulkan_descriptor_pool_new_wrapped (render->device, pool, max_sets); cache = gst_vulkan_descriptor_cache_new (ret, 1, &render->descriptor_set_layout); gst_object_unref (ret); return cache; } static gboolean gst_vulkan_image_identity_set_caps (GstBaseTransform * bt, GstCaps * in_caps, GstCaps * out_caps) { GstVulkanImageIdentity *vk_identity = GST_VULKAN_IMAGE_IDENTITY (bt); GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (bt); GstVulkanFence *last_fence; if (!GST_BASE_TRANSFORM_CLASS (parent_class)->set_caps (bt, in_caps, out_caps)) return FALSE; if (render->last_fence) last_fence = gst_vulkan_fence_ref (render->last_fence); else last_fence = gst_vulkan_fence_new_always_signalled (render->device); if (vk_identity->descriptor_pool) gst_vulkan_trash_list_add (render->trash_list, gst_vulkan_trash_new_object_unref (last_fence, (GstObject *) vk_identity->descriptor_pool)); vk_identity->descriptor_pool = NULL; gst_vulkan_fence_unref (last_fence); if (!(vk_identity->descriptor_pool = _create_descriptor_pool (vk_identity))) return FALSE; return TRUE; } static VkSampler _create_sampler (GstVulkanImageIdentity * vk_identity) { GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (vk_identity); /* *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_EDGE, .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, .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 (vk_identity, "Failed to create sampler: %s", error->message); g_clear_error (&error); return VK_NULL_HANDLE; } return sampler; } static gboolean gst_vulkan_image_identity_start (GstBaseTransform * bt) { GstVulkanImageIdentity *vk_identity = GST_VULKAN_IMAGE_IDENTITY (bt); if (!GST_BASE_TRANSFORM_CLASS (parent_class)->start (bt)) return FALSE; if (!(vk_identity->sampler = _create_sampler (vk_identity))) return FALSE; return TRUE; } static gboolean gst_vulkan_image_identity_stop (GstBaseTransform * bt) { GstVulkanImageIdentity *vk_identity = GST_VULKAN_IMAGE_IDENTITY (bt); GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (bt); if (render->device) { GstVulkanFence *last_fence; if (render->last_fence) last_fence = gst_vulkan_fence_ref (render->last_fence); else last_fence = gst_vulkan_fence_new_always_signalled (render->device); if (vk_identity->descriptor_pool) gst_vulkan_trash_list_add (render->trash_list, gst_vulkan_trash_new_object_unref (last_fence, (GstObject *) vk_identity->descriptor_pool)); vk_identity->descriptor_pool = NULL; if (vk_identity->sampler) gst_vulkan_trash_list_add (render->trash_list, gst_vulkan_trash_new_free_sampler (last_fence, vk_identity->sampler)); vk_identity->sampler = VK_NULL_HANDLE; gst_vulkan_fence_unref (last_fence); } if (vk_identity->cmd_pool) gst_object_unref (vk_identity->cmd_pool); vk_identity->cmd_pool = VK_NULL_HANDLE; return GST_BASE_TRANSFORM_CLASS (parent_class)->stop (bt); } static void update_descriptor_set (GstVulkanImageIdentity * vk_identity, VkDescriptorSet set, VkImageView view) { GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (vk_identity); /* *INDENT-OFF* */ VkDescriptorImageInfo image_info = { .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, .imageView = view, .sampler = vk_identity->sampler }; VkWriteDescriptorSet writes = { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .pNext = NULL, .dstSet = set, .dstBinding = 0, .dstArrayElement = 0, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1, .pImageInfo = &image_info }; /* *INDENT-ON* */ vkUpdateDescriptorSets (render->device->device, 1, &writes, 0, NULL); } static VkFramebuffer _create_framebuffer (GstVulkanImageIdentity * vk_identity, VkImageView view) { GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (vk_identity); /* *INDENT-OFF* */ VkImageView attachments[] = { view, }; VkFramebufferCreateInfo framebuffer_info = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .pNext = NULL, .renderPass = render->render_pass, .attachmentCount = 1, .pAttachments = attachments, .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 VK_NULL_HANDLE; } return framebuffer; } static GstFlowReturn gst_vulkan_image_identity_transform (GstBaseTransform * bt, GstBuffer * inbuf, GstBuffer * outbuf) { GstVulkanFullScreenRender *render = GST_VULKAN_FULL_SCREEN_RENDER (bt); GstVulkanImageIdentity *vk_identity = GST_VULKAN_IMAGE_IDENTITY (bt); GstVulkanImageMemory *in_img_mem, *out_img_mem; GstVulkanImageView *in_img_view, *out_img_view; GstVulkanFence *fence = NULL; GstVulkanDescriptorSet *set; GstMemory *in_mem, *out_mem; VkFramebuffer framebuffer; GstVulkanCommandBuffer *cmd_buf; GError *error = NULL; VkResult err; fence = gst_vulkan_fence_new (render->device, 0, &error); if (!fence) goto error; in_mem = gst_buffer_peek_memory (inbuf, 0); if (!gst_is_vulkan_image_memory (in_mem)) { g_set_error_literal (&error, GST_VULKAN_ERROR, GST_VULKAN_FAILED, "Input memory must be a GstVulkanImageMemory"); goto error; } in_img_mem = (GstVulkanImageMemory *) in_mem; in_img_view = get_or_create_image_view (in_img_mem); gst_vulkan_trash_list_add (render->trash_list, gst_vulkan_trash_new_mini_object_unref (fence, GST_MINI_OBJECT_CAST (in_img_view))); out_mem = gst_buffer_peek_memory (outbuf, 0); if (!gst_is_vulkan_image_memory (out_mem)) { g_set_error_literal (&error, GST_VULKAN_ERROR, GST_VULKAN_FAILED, "Input memory must be a GstVulkanImageMemory"); goto error; } out_img_mem = (GstVulkanImageMemory *) out_mem; out_img_view = get_or_create_image_view (out_img_mem); gst_vulkan_trash_list_add (render->trash_list, gst_vulkan_trash_new_mini_object_unref (fence, GST_MINI_OBJECT_CAST (out_img_view))); if (!vk_identity->cmd_pool) { if (!(vk_identity->cmd_pool = gst_vulkan_queue_create_command_pool (render->queue, &error))) goto error; } if (!(set = gst_vulkan_descriptor_cache_acquire (vk_identity->descriptor_pool, &error))) goto error; update_descriptor_set (vk_identity, set->set, in_img_view->view); if (!(cmd_buf = gst_vulkan_command_pool_create (vk_identity->cmd_pool, &error))) goto error; if (!(framebuffer = _create_framebuffer (vk_identity, out_img_view->view))) { g_set_error_literal (&error, GST_VULKAN_ERROR, GST_VULKAN_FAILED, "Failed to create framebuffer"); 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* */ gst_vulkan_command_buffer_lock (cmd_buf); err = vkBeginCommandBuffer (cmd_buf->cmd, &cmd_buf_info); if (gst_vulkan_error_to_g_error (err, &error, "vkBeginCommandBuffer") < 0) goto unlock_error; } { /* *INDENT-OFF* */ VkImageMemoryBarrier in_image_memory_barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = NULL, .srcAccessMask = in_img_mem->barrier.parent.access_flags, .dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT, .oldLayout = in_img_mem->barrier.image_layout, .newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, /* FIXME: implement exclusive transfers */ .srcQueueFamilyIndex = 0, .dstQueueFamilyIndex = 0, .image = in_img_mem->image, .subresourceRange = in_img_mem->barrier.subresource_range }; VkImageMemoryBarrier out_image_memory_barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = NULL, .srcAccessMask = out_img_mem->barrier.parent.access_flags, .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, .oldLayout = out_img_mem->barrier.image_layout, .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, /* FIXME: implement exclusive transfers */ .srcQueueFamilyIndex = 0, .dstQueueFamilyIndex = 0, .image = out_img_mem->image, .subresourceRange = out_img_mem->barrier.subresource_range }; /* *INDENT-ON* */ vkCmdPipelineBarrier (cmd_buf->cmd, in_img_mem->barrier.parent.pipeline_stages, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, &in_image_memory_barrier); in_img_mem->barrier.parent.pipeline_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; in_img_mem->barrier.parent.access_flags = in_image_memory_barrier.dstAccessMask; in_img_mem->barrier.image_layout = in_image_memory_barrier.newLayout; vkCmdPipelineBarrier (cmd_buf->cmd, out_img_mem->barrier.parent.pipeline_stages, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, NULL, 0, NULL, 1, &out_image_memory_barrier); out_img_mem->barrier.parent.pipeline_stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; out_img_mem->barrier.parent.access_flags = out_image_memory_barrier.dstAccessMask; out_img_mem->barrier.image_layout = out_image_memory_barrier.newLayout; } vkCmdBindDescriptorSets (cmd_buf->cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, render->pipeline_layout, 0, 1, &set->set, 0, NULL); if (!gst_vulkan_full_screen_render_fill_command_buffer (render, cmd_buf->cmd, framebuffer)) goto unlock_error; err = vkEndCommandBuffer (cmd_buf->cmd); gst_vulkan_command_buffer_unlock (cmd_buf); 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_mini_object_unref (fence, GST_MINI_OBJECT_CAST (set))); gst_vulkan_trash_list_add (render->trash_list, gst_vulkan_trash_new_free_framebuffer (fence, framebuffer)); gst_vulkan_trash_list_add (render->trash_list, gst_vulkan_trash_new_mini_object_unref (fence, GST_MINI_OBJECT_CAST (cmd_buf))); if (!gst_vulkan_full_screen_render_submit (render, cmd_buf->cmd, fence)) return GST_FLOW_ERROR; return GST_FLOW_OK; unlock_error: if (cmd_buf) { gst_vulkan_command_buffer_unlock (cmd_buf); gst_vulkan_command_buffer_unref (cmd_buf); } error: GST_ELEMENT_ERROR (bt, LIBRARY, FAILED, ("%s", error->message), (NULL)); g_clear_error (&error); return GST_FLOW_ERROR; }