/*
 * GStreamer
 * Copyright (C) 2023 Igalia, S.L.
 *
 * 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.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "gstvkoperation.h"
#include "gstvkphysicaldevice-private.h"

/**
 * SECTION:vkoperation
 * @title: GstVulkanOperation
 * @short_description: Vulkan Operation
 * @see_also: #GstVulkanCommandPool, #GstVulkanCommandBuffer
 *
 * A #GstVulkanOperation abstract a queue operation for images adding
 * automatically semaphores and barriers. It uses Synchronization2 extension if
 * available. Also it enables a VkQueryPool if it's possible and it's requested.
 *
 * Since: 1.24
 */

typedef struct _GstVulkanDependencyFrame GstVulkanDependencyFrame;

struct _GstVulkanDependencyFrame
{
  GstVulkanImageMemory *mem[GST_VIDEO_MAX_PLANES];
  gboolean updated;
  gboolean semaphored;
  guint64 dst_stage;
  guint64 new_access;
  VkImageLayout new_layout;
  GstVulkanQueue *new_queue;
};

struct _GstVulkanOperationPrivate
{
  GstVulkanCommandPool *cmd_pool;
  GstVulkanTrashList *trash_list;

  VkQueryPool query_pool;
  VkQueryType query_type;
#if defined(VK_KHR_video_maintenance1)
  VkVideoInlineQueryInfoKHR inline_query;
#endif
  guint n_queries;
  gsize query_data_size;
  gsize query_data_stride;
  gpointer query_data;
  gboolean op_submitted;

  gboolean has_sync2;
  gboolean has_video;
  gboolean has_timeline;
  gboolean has_video_maintenance1;
  gboolean use_inline_query;

  GArray *barriers;

  struct
  {
    GArray *frames;
    GArray *wait_semaphores;
    GArray *signal_semaphores;

    /* if sync2 isn't supported but timeline is */
    GArray *wait_dst_stage_mask;
    GArray *wait_semaphore_values;
    GArray *signal_semaphore_values;
  } deps;

#if defined(VK_KHR_synchronization2)
  PFN_vkQueueSubmit2KHR QueueSubmit2;
  PFN_vkCmdPipelineBarrier2KHR CmdPipelineBarrier2;
#endif
};

enum
{
  PROP_COMMAND_POOL = 1,
  N_PROPERTIES,
};

static GParamSpec *g_properties[N_PROPERTIES];

GST_DEBUG_CATEGORY_STATIC (GST_CAT_VULKAN_OPERATION);
#define GST_CAT_DEFAULT GST_CAT_VULKAN_OPERATION

#define GET_PRIV(operation) ((GstVulkanOperationPrivate *)              \
  gst_vulkan_operation_get_instance_private (GST_VULKAN_OPERATION (operation)))

#define gst_vulkan_operation_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstVulkanOperation, gst_vulkan_operation,
    GST_TYPE_OBJECT, G_ADD_PRIVATE (GstVulkanOperation)
    GST_DEBUG_CATEGORY_INIT (GST_CAT_VULKAN_OPERATION,
        "vulkanoperation", 0, "Vulkan Operation"));

static void
gst_vulkan_operation_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstVulkanOperationPrivate *priv = GET_PRIV (object);

  switch (prop_id) {
    case PROP_COMMAND_POOL:
      /* G_PARAM_CONSTRUCT_ONLY */
      g_assert (!priv->cmd_pool);
      priv->cmd_pool = g_value_dup_object (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_vulkan_operation_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstVulkanOperationPrivate *priv = GET_PRIV (object);

  switch (prop_id) {
    case PROP_COMMAND_POOL:
      g_value_set_object (value, priv->cmd_pool);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_vulkan_operation_constructed (GObject * object)
{
  GstVulkanOperation *self = GST_VULKAN_OPERATION (object);
  GstVulkanOperationPrivate *priv = GET_PRIV (self);
  GstVulkanDevice *device = priv->cmd_pool->queue->device;

  priv->has_sync2 =
      gst_vulkan_physical_device_has_feature_synchronization2
      (device->physical_device);

  priv->has_timeline =
      gst_vulkan_physical_device_has_feature_timeline_sempahore
      (device->physical_device);

  if (priv->has_sync2) {
    gboolean vulkan_1_3 =
        gst_vulkan_physical_device_check_api_version (device->physical_device,
        1, 3, 0);

    if (vulkan_1_3) {
      priv->QueueSubmit2 =
          gst_vulkan_device_get_proc_address (device, "vkQueueSubmit2");
      priv->CmdPipelineBarrier2 =
          gst_vulkan_device_get_proc_address (device, "vkCmdPipelineBarrier2");
    }

    if (!priv->QueueSubmit2) {
      priv->QueueSubmit2 =
          gst_vulkan_device_get_proc_address (device, "vkQueueSubmit2KHR");
    }
    if (!priv->CmdPipelineBarrier2) {
      priv->CmdPipelineBarrier2 =
          gst_vulkan_device_get_proc_address (device,
          "vkCmdPipelineBarrier2KHR");
    }

    priv->has_sync2 = (priv->QueueSubmit2 && priv->CmdPipelineBarrier2);
  }
#if defined(VK_KHR_video_queue)
  priv->has_video = gst_vulkan_device_is_extension_enabled (device,
      VK_KHR_VIDEO_QUEUE_EXTENSION_NAME);
#endif
  priv->has_video_maintenance1 =
      gst_vulkan_physical_device_has_feature_video_maintenance1
      (device->physical_device);

  G_OBJECT_CLASS (parent_class)->constructed (object);
}

static void
gst_vulkan_operation_finalize (GObject * object)
{
  GstVulkanOperationPrivate *priv = GET_PRIV (object);

  gst_vulkan_operation_reset (GST_VULKAN_OPERATION (object));

  g_clear_pointer (&priv->query_data, g_free);

  if (priv->query_pool) {
    vkDestroyQueryPool (priv->cmd_pool->queue->device->device, priv->query_pool,
        NULL);
    priv->query_pool = VK_NULL_HANDLE;
  }

  gst_clear_object (&priv->trash_list);
  gst_clear_object (&priv->cmd_pool);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gst_vulkan_operation_init (GstVulkanOperation * self)
{
  GstVulkanOperationPrivate *priv = GET_PRIV (self);

  priv->trash_list = gst_vulkan_trash_fence_list_new ();
}

static void
gst_vulkan_operation_class_init (GstVulkanOperationClass * klass)
{
  GObjectClass *gobject_class = (GObjectClass *) klass;

  gobject_class->set_property = gst_vulkan_operation_set_property;
  gobject_class->get_property = gst_vulkan_operation_get_property;
  gobject_class->constructed = gst_vulkan_operation_constructed;
  gobject_class->finalize = gst_vulkan_operation_finalize;

  g_properties[PROP_COMMAND_POOL] =
      g_param_spec_object ("command-pool", "GstVulkanCommandPool",
      "Vulkan Command Pool", GST_TYPE_VULKAN_COMMAND_POOL,
      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);

  g_object_class_install_properties (gobject_class, N_PROPERTIES, g_properties);
}

static void
_dependency_frame_free (gpointer data)
{
  GstVulkanDependencyFrame *frame = data;
  guint i;

  for (i = 0; i < GST_VIDEO_MAX_PLANES && frame->mem[i] != NULL; i++) {
    gst_memory_unref (GST_MEMORY_CAST (frame->mem[i]));
    frame->mem[i] = NULL;
  }
  gst_clear_object (&frame->new_queue);
}

static GArray *
_get_dependency_frames (GstVulkanOperation * self)
{
  GstVulkanOperationPrivate *priv = GET_PRIV (self);

  if (!priv->deps.frames) {
    priv->deps.frames =
        g_array_new (FALSE, FALSE, sizeof (GstVulkanDependencyFrame));
    g_array_set_clear_func (priv->deps.frames, _dependency_frame_free);
  }

  return priv->deps.frames;
}

/**
 * gst_vulkan_operation_reset:
 * @self: a #GstVulkanOperation
 *
 * Resets the operation to a clean state.
 */
void
gst_vulkan_operation_reset (GstVulkanOperation * self)
{
  GstVulkanOperationPrivate *priv;

  g_return_if_fail (GST_IS_VULKAN_OPERATION (self));

  priv = GET_PRIV (self);

  gst_clear_vulkan_command_buffer (&self->cmd_buf);

  gst_vulkan_operation_discard_dependencies (self);

  gst_vulkan_trash_list_wait (priv->trash_list, G_MAXUINT64);
  gst_vulkan_trash_list_gc (priv->trash_list);
}

/**
 * gst_vulkan_operation_begin:
 * @self: a #GstVulkanOperation
 * @error: a #GError
 *
 * See also: gst_vulkan_operation_end() and gst_vulkan_operation_reset()
 *
 * Attempts to set the operation ready to work. It instantiates the common
 * command buffer in @self and calls vkBeginCommandBuffer.
 *
 * After calling this function you can register commands in the command buffer,
 * and finally call gst_vulkan_operation_end(). gst_vulkan_operation_reset() is
 * called internally if something failed.
 *
 * Returns: whether the operation started. It might fill @error.
 */
gboolean
gst_vulkan_operation_begin (GstVulkanOperation * self, GError ** error)
{
  GstVulkanOperationPrivate *priv;
  VkCommandBufferBeginInfo cmd_buf_info = {
    .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
    .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
  };
  VkResult err;

  g_return_val_if_fail (GST_IS_VULKAN_OPERATION (self), FALSE);

  priv = GET_PRIV (self);

  GST_OBJECT_LOCK (self);
  if (self->cmd_buf) {
    GST_OBJECT_UNLOCK (self);
    return TRUE;
  }

  if (!(self->cmd_buf = gst_vulkan_command_pool_create (priv->cmd_pool, error))) {
    GST_OBJECT_UNLOCK (self);
    g_set_error (error, GST_VULKAN_ERROR, VK_ERROR_INITIALIZATION_FAILED,
        "Instantiate of command buffer failed");
    return FALSE;
  }
  GST_OBJECT_UNLOCK (self);

  gst_vulkan_command_buffer_lock (self->cmd_buf);

  err = vkBeginCommandBuffer (self->cmd_buf->cmd, &cmd_buf_info);
  if (gst_vulkan_error_to_g_error (err, error, "vkBeginCommandBuffer") < 0)
    goto error;

  if (priv->query_pool)
    vkCmdResetQueryPool (self->cmd_buf->cmd, priv->query_pool, 0, 1);

  gst_vulkan_command_buffer_unlock (self->cmd_buf);

  return TRUE;

error:
  {
    gst_vulkan_command_buffer_unlock (self->cmd_buf);
    gst_vulkan_operation_reset (self);
    return FALSE;
  }
}

static gboolean
gst_vulkan_operation_submit2 (GstVulkanOperation * self, GstVulkanFence * fence,
    GError ** error)
{
#if defined(VK_KHR_synchronization2)
  GstVulkanOperationPrivate *priv = GET_PRIV (self);
  VkCommandBufferSubmitInfoKHR cmd_buf_info;
  VkSubmitInfo2KHR submit_info;
  VkResult err;

  GST_OBJECT_LOCK (self);

  /* *INDENT-OFF* */
  cmd_buf_info = (VkCommandBufferSubmitInfoKHR) {
    .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO_KHR,
    .commandBuffer = self->cmd_buf->cmd,
  };

  submit_info = (VkSubmitInfo2KHR) {
    .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2_KHR,
    .pCommandBufferInfos = &cmd_buf_info,
    .commandBufferInfoCount = 1,
    .pWaitSemaphoreInfos = priv->deps.wait_semaphores ?
        (const VkSemaphoreSubmitInfoKHR *) priv->deps.wait_semaphores->data : NULL,
    .waitSemaphoreInfoCount = priv->deps.wait_semaphores ?
        priv->deps.wait_semaphores->len : 0,
    .pSignalSemaphoreInfos = priv->deps.signal_semaphores ?
        (const VkSemaphoreSubmitInfoKHR *) priv->deps.signal_semaphores->data : NULL,
    .signalSemaphoreInfoCount = priv->deps.signal_semaphores ?
        priv->deps.signal_semaphores->len : 0,
  };
  /* *INDENT-ON* */

  gst_vulkan_queue_submit_lock (priv->cmd_pool->queue);
  err = priv->QueueSubmit2 (priv->cmd_pool->queue->queue, 1, &submit_info,
      GST_VULKAN_FENCE_FENCE (fence));
  gst_vulkan_queue_submit_unlock (priv->cmd_pool->queue);

  GST_OBJECT_UNLOCK (self);

  if (gst_vulkan_error_to_g_error (err, error, "vkQueueSubmit2KHR") < 0)
    goto error;

  return TRUE;

error:
#endif
  return FALSE;
}

static gboolean
gst_vulkan_operation_submit1 (GstVulkanOperation * self, GstVulkanFence * fence,
    GError ** error)
{
  GstVulkanOperationPrivate *priv = GET_PRIV (self);
#if defined(VK_KHR_timeline_semaphore)
  VkTimelineSemaphoreSubmitInfoKHR semaphore_submit_info;
#endif
  gpointer pnext = NULL;
  VkSubmitInfo submit_info;
  VkResult err;

  GST_OBJECT_LOCK (self);
  /* *INDENT-OFF* */
#if defined(VK_KHR_timeline_semaphore)
  if (priv->has_timeline) {
    semaphore_submit_info = (VkTimelineSemaphoreSubmitInfoKHR) {
      .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR,
      .waitSemaphoreValueCount = priv->deps.wait_semaphore_values ?
         priv->deps.wait_semaphore_values->len : 0,
      .pWaitSemaphoreValues = priv->deps.wait_semaphore_values ?
          (const guint64 *) priv->deps.wait_semaphore_values->data : NULL,
      .signalSemaphoreValueCount = priv->deps.signal_semaphore_values ?
          priv->deps.signal_semaphore_values->len : 0,
      .pSignalSemaphoreValues = priv->deps.signal_semaphore_values ?
          (const guint64 *) priv->deps.signal_semaphore_values->data : NULL,
    };

    pnext = &semaphore_submit_info;
  }
#endif

  submit_info = (VkSubmitInfo) {
    .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
    .pNext = pnext,
    .commandBufferCount = 1,
    .pCommandBuffers = &self->cmd_buf->cmd,
    .pWaitSemaphores = priv->deps.wait_semaphores ?
        (const VkSemaphore *) priv->deps.wait_semaphores->data : NULL,
    .waitSemaphoreCount = priv->deps.wait_semaphores ?
        priv->deps.wait_semaphores->len : 0,
    .pSignalSemaphores = priv->deps.signal_semaphores ?
        (const VkSemaphore *) priv->deps.signal_semaphores->data : NULL,
    .signalSemaphoreCount = priv->deps.signal_semaphores ?
        priv->deps.signal_semaphores->len : 0,
    .pWaitDstStageMask = priv->deps.wait_dst_stage_mask ?
        (const VkPipelineStageFlags *)
        priv->deps.wait_dst_stage_mask->data : NULL,
  };
  /* *INDENT-ON* */

  gst_vulkan_queue_submit_lock (priv->cmd_pool->queue);
  err = vkQueueSubmit (priv->cmd_pool->queue->queue, 1, &submit_info,
      GST_VULKAN_FENCE_FENCE (fence));
  gst_vulkan_queue_submit_unlock (priv->cmd_pool->queue);
  GST_OBJECT_UNLOCK (self);

  if (gst_vulkan_error_to_g_error (err, error, "vkQueueSubmit") < 0)
    return FALSE;

  return TRUE;
}

/**
 * gst_vulkan_operation_end:
 * @self: a #GstVulkanOperation
 * @error: a #GError
 *
 * See also: gst_vulkan_operation_begin() and gst_vulkan_operation_reset()
 *
 * It calls vkEndCommandBuffer, and later either vkQueueSubmit or
 * vkQueueSubmit2KHR filling up the semaphores from images declared as
 * dependencies.
 *
 * You have called gst_vulkan_operation_begin() before.
 * gst_vulkan_operation_reset() is called internally if something fails
 *
 * Returns: whether the operation failed. It might fill @error.
 */
gboolean
gst_vulkan_operation_end (GstVulkanOperation * self, GError ** error)
{
  GstVulkanOperationPrivate *priv;
  GstVulkanFence *fence;
  GstVulkanDevice *device;
  VkResult err;
  guint i, j;

  g_return_val_if_fail (GST_IS_VULKAN_OPERATION (self), FALSE);

  if (!self->cmd_buf) {
    GST_INFO_OBJECT (self, "Cannot end operation without begin it");
    return FALSE;
  }

  priv = GET_PRIV (self);

  device = priv->cmd_pool->queue->device;
  fence = gst_vulkan_device_create_fence (device, error);
  if (!fence)
    return FALSE;

  gst_vulkan_command_buffer_lock (self->cmd_buf);
  err = vkEndCommandBuffer (self->cmd_buf->cmd);
  gst_vulkan_command_buffer_unlock (self->cmd_buf);
  if (gst_vulkan_error_to_g_error (err, error, "vkEndCommandBuffer") < 0)
    return FALSE;

  if (priv->has_sync2) {
    if (!gst_vulkan_operation_submit2 (self, fence, error))
      goto bail;
  } else if (!gst_vulkan_operation_submit1 (self, fence, error))
    goto bail;

  gst_vulkan_trash_list_add (priv->trash_list,
      gst_vulkan_trash_list_acquire (priv->trash_list, fence,
          gst_vulkan_trash_mini_object_unref,
          GST_MINI_OBJECT_CAST (self->cmd_buf)));

  gst_vulkan_fence_unref (fence);

  gst_vulkan_trash_list_gc (priv->trash_list);

  GST_OBJECT_LOCK (self);

  for (i = 0; priv->deps.frames && i < priv->deps.frames->len; i++) {
    GstVulkanDependencyFrame *frame =
        &g_array_index (priv->deps.frames, GstVulkanDependencyFrame, i);

    for (j = 0; j < GST_VIDEO_MAX_PLANES && frame->mem[j] != NULL; j++) {
      if (frame->updated) {
        frame->mem[j]->barrier.parent.pipeline_stages = frame->dst_stage;
        frame->mem[j]->barrier.parent.access_flags = frame->new_access;
        frame->mem[j]->barrier.parent.queue = frame->new_queue;
        frame->mem[j]->barrier.image_layout = frame->new_layout;
      }

      if (frame->semaphored)
        frame->mem[j]->barrier.parent.semaphore_value++;
    }

    frame->updated = FALSE;
    frame->semaphored = FALSE;
  }

  g_clear_pointer (&priv->barriers, g_array_unref);
  self->cmd_buf = NULL;

  priv->op_submitted = TRUE;

  GST_OBJECT_UNLOCK (self);

  gst_vulkan_operation_discard_dependencies (self);

  return TRUE;

bail:
  gst_vulkan_fence_unref (fence);
  gst_vulkan_operation_reset (self);
  return FALSE;
}

/**
 * gst_vulkan_operation_wait:
 * @self: a #GstVulkanOperation
 *
 * Waits for the operation's fence to signal.
 *
 * Returns: whether the operation succeed.
 */
gboolean
gst_vulkan_operation_wait (GstVulkanOperation * self)
{
  GstVulkanOperationPrivate *priv;
  gboolean ret;

  g_return_val_if_fail (GST_IS_VULKAN_OPERATION (self), FALSE);

  priv = GET_PRIV (self);
  ret = gst_vulkan_trash_list_wait (priv->trash_list, G_MAXUINT64);

  return ret;
}

static gboolean
_dep_has_buffer (GstVulkanDependencyFrame * dep, GstBuffer * buffer)
{
  guint i, n_mems;
  GstMemory *mem;

  n_mems = gst_buffer_n_memory (buffer);
  g_assert (n_mems <= GST_VIDEO_MAX_PLANES);

  for (i = 0; i < n_mems; i++) {
    mem = gst_buffer_peek_memory (buffer, i);
    if (!(mem && gst_is_vulkan_image_memory (mem))) {
      GST_ERROR ("Memory %" GST_PTR_FORMAT " is not a Vulkan Image", mem);
      return FALSE;
    }

    if (dep->mem[i] != (GstVulkanImageMemory *) mem)
      return FALSE;
  }

  return TRUE;
}

static void
_dep_set_buffer (GstVulkanDependencyFrame * dep, GstBuffer * buffer)
{
  guint i, n_mems;
  GstMemory *mem;

  n_mems = gst_buffer_n_memory (buffer);
  g_assert (n_mems <= GST_VIDEO_MAX_PLANES);

  for (i = 0; i < n_mems; i++) {
    mem = gst_buffer_peek_memory (buffer, i);
    if (!(mem && gst_is_vulkan_image_memory (mem))) {
      GST_ERROR ("Memory %" GST_PTR_FORMAT " is not a Vulkan Image", mem);
      return;
    }

    dep->mem[i] = (GstVulkanImageMemory *) gst_memory_ref (mem);
  }

  for (; i < GST_VIDEO_MAX_PLANES; i++)
    dep->mem[i] = NULL;
}

static void
gst_vulkan_operation_update_frame_unlocked (GstVulkanOperation * self,
    GstBuffer * frame, guint64 dst_stage, guint64 new_access,
    VkImageLayout new_layout, GstVulkanQueue * new_queue)
{
  GArray *frames;
  guint i;
  GstVulkanDependencyFrame *dep_frame = NULL;

  frames = _get_dependency_frames (self);
  for (i = 0; i < frames->len; i++) {
    dep_frame = &g_array_index (frames, GstVulkanDependencyFrame, i);

    if (_dep_has_buffer (dep_frame, frame))
      break;
  }

  if (i >= frames->len) {
    GstVulkanDependencyFrame dframe;

    _dep_set_buffer (&dframe, frame);
    g_array_append_val (frames, dframe);
    dep_frame =
        &g_array_index (frames, GstVulkanDependencyFrame, frames->len - 1);
  }

  dep_frame->updated = TRUE;
  dep_frame->dst_stage = dst_stage;
  dep_frame->new_access = new_access;
  dep_frame->new_layout = new_layout;
  dep_frame->new_queue = new_queue ? gst_object_ref (new_queue) : NULL;
}

/**
 * gst_vulkan_operation_update_frame:
 * @self: a #GstVulkanOperation
 * @frame: a #GstBuffer to update after submit
 * @dst_stage: destination pipeline stage (VkPipelineStageFlags or
 *   VkPipelineStageFlags2)
 * @new_access: the new access flags (VkAccessFlags2 or VkAccessFlags)
 * @new_layout: the new VkImageLayout
 * @new_queue: (nullable): destination #GstVulkanQueue for a transfer of @frame
 *   ownership
 *
 * Add or update the internal list of the future state of @frame. This state
 * will be set after gst_vulkan_operation_end().
 *
 * This method is useful when new barriers are added to the array without using
 * gst_vulkan_operation_add_frame_barrier().
 */
void
gst_vulkan_operation_update_frame (GstVulkanOperation * self, GstBuffer * frame,
    guint64 dst_stage, guint64 new_access, VkImageLayout new_layout,
    GstVulkanQueue * new_queue)
{
  g_return_if_fail (GST_IS_VULKAN_OPERATION (self));

  GST_OBJECT_LOCK (self);
  gst_vulkan_operation_update_frame_unlocked (self, frame, dst_stage,
      new_access, new_layout, new_queue);
  GST_OBJECT_UNLOCK (self);
}

static GArray *
_new_image_barriers (GstVulkanOperation * self)
{
#if defined(VK_KHR_synchronization2)
  GstVulkanOperationPrivate *priv;

  priv = GET_PRIV (self);

  if (priv->has_sync2) {
    return g_array_sized_new (FALSE, FALSE, sizeof (VkImageMemoryBarrier2KHR),
        GST_VIDEO_MAX_PLANES);
  }
#endif
  return g_array_sized_new (FALSE, FALSE, sizeof (VkImageMemoryBarrier),
      GST_VIDEO_MAX_PLANES);
}

static GArray *
_get_image_barriers_unlocked (GstVulkanOperation * self)
{
  GstVulkanOperationPrivate *priv;
  priv = GET_PRIV (self);

  if (priv->barriers)
    return priv->barriers;

  priv->barriers = _new_image_barriers (self);
  return priv->barriers;
}

/**
 * gst_vulkan_operation_retrieve_image_barriers: (skip)
 * @self: a #GstVulkanOperation
 *
 * Retrieves a copy of the current defined barriers internally, which will be
 * used by vkCmdPipelineBarrier or vkCmdPipelineBarrier2KHR by the API user.
 *
 * The element type of the array might be, depending on if synchronization2
 * extension is used, either VkImageMemoryBarrier or VkImageMemoryBarrier2KHR.
 *
 * Returns: (transfer full): Current barriers #GArray, either
 *    VkImageMemoryBarrier or VkImageMemoryBarrier2KHR, depending whether
 *    synchronization2 extension is used. Call g_array_unref() after
 *    the operation is using.
 */
GArray *
gst_vulkan_operation_retrieve_image_barriers (GstVulkanOperation * self)
{
  GArray *barriers;

  GST_OBJECT_LOCK (self);
  barriers = g_array_copy (_get_image_barriers_unlocked (self));
  GST_OBJECT_UNLOCK (self);

  return barriers;
}

/**
 * gst_vulkan_operation_new_extra_image_barriers: (skip)
 * @self: a #GstVulkanOperation
 *
 * See also: gst_vulkan_operation_use_sync2(),
 *     gst_vulkan_operation_add_extra_image_barriers() and
 *     gst_vulkan_operation_update_frame()
 *
 * If it's required to add custom image memory barriers this function will
 * allocate a new array to append in it the extra image memory barriers to
 * handle.
 *
 * Remember to call gst_vulkan_operation_update_frame() after adding the barrier
 * related with that frame.
 *
 * Returns: (transfer full): A new allocated #GArray of barriers, either
 *     VkImageMemoryBarrier or VkImageMemoryBarrier2KHR, depending whether
 *     synchronization2 extension is used.
 */
GArray *
gst_vulkan_operation_new_extra_image_barriers (GstVulkanOperation * self)
{
  return _new_image_barriers (self);
}

/**
 * gst_vulkan_operation_add_extra_image_barriers: (skip)
 * @self: a #GstVulkanOperation
 * @extra_barriers: (transfer none): a #GArray of extra image memory barriers to handle, either
 * VkImageMemoryBarrier or VkImageMemoryBarrier2KHR, depending whether
 * synchronization2 extension is used.
 *
 * See also: gst_vulkan_operation_use_sync2(),
 *     gst_vulkan_operation_new_extra_image_barriers() and
 *     gst_vulkan_operation_update_frame()
 *
 * Any non-repeated image barrier in @extra_barriers is appended to the internal
 * barrier list.
 *
 * Remember to call gst_vulkan_operation_update_frame() on those frames with
 * images in @extra_barriers.
 */
void
gst_vulkan_operation_add_extra_image_barriers (GstVulkanOperation * self,
    GArray * extra_barriers)
{
  guint i, j;
  GArray *barriers;
  gboolean found;
#if defined(VK_KHR_synchronization2)
  GstVulkanOperationPrivate *priv;
#endif

  g_return_if_fail (GST_IS_VULKAN_OPERATION (self));

#if defined(VK_KHR_synchronization2)
  priv = GET_PRIV (self);
#endif

  GST_OBJECT_LOCK (self);

  barriers = _get_image_barriers_unlocked (self);

#define FIND_BARRIER(Type) G_STMT_START {                   \
    Type *barrier = &g_array_index (barriers, Type, j);     \
    Type *extra = &g_array_index (extra_barriers, Type, i); \
    found = (barrier->image == extra->image);               \
  } G_STMT_END;

#define APPEND_BARRIER(Type) G_STMT_START {                 \
    Type extra = g_array_index (extra_barriers, Type, i);   \
    g_array_append_val (barriers, extra);                   \
  } G_STMT_END;

  /* if barrier is already there, skip it */
  for (i = 0; i < extra_barriers->len; i++) {
    found = FALSE;

    for (j = 0; !found && j < barriers->len; j++) {
#if defined(VK_KHR_synchronization2)
      if (priv->has_sync2) {
        FIND_BARRIER (VkImageMemoryBarrier2KHR);
      } else
#endif
      {
        FIND_BARRIER (VkImageMemoryBarrier);
      }
    }

    if (!found) {
#if defined(VK_KHR_synchronization2)
      if (priv->has_sync2) {
        APPEND_BARRIER (VkImageMemoryBarrier2KHR);
      } else
#endif
      {
        APPEND_BARRIER (VkImageMemoryBarrier);
      }
    }
  }

#undef FIND_BARRIER
#undef APPEND_BARRIER

  GST_OBJECT_UNLOCK (self);
}

/**
 * gst_vulkan_operation_add_frame_barrier:
 * @self: a #GstVulkanOperation
 * @frame: a Vulkan Image #GstBuffer
 * @src_stage: source pipeline stage (VkPipelineStageFlags or
 *   VkPipelineStageFlags2)
 * @dst_stage: destination pipeline stage (VkPipelineStageFlags or
 *   VkPipelineStageFlags2)
 * @new_access: the new access flags (VkAccessFlags2 or VkAccessFlags)
 * @new_layout: the new VkImageLayout
 * @new_queue: (nullable): destination #GstVulkanQueue for a transfer of @frame
 *   ownership
 *
 * See also: gst_vulkan_operation_update_frame()
 *
 * Adds an image memory barrier per memory in @frame with its future state. And
 * it updates the @frame barrier state by calling internally
 * gst_vulkan_operation_update_frame().
 *
 * Returns: whether the @frame barriers were appended
 */
gboolean
gst_vulkan_operation_add_frame_barrier (GstVulkanOperation * self,
    GstBuffer * frame, guint64 src_stage, guint64 dst_stage, guint64 new_access,
    VkImageLayout new_layout, GstVulkanQueue * new_queue)
{
  guint i, n_mems;
#if defined(VK_KHR_synchronization2)
  GstVulkanOperationPrivate *priv;
#endif
  GstVulkanDependencyFrame *dep_frame = NULL;
  GArray *frames, *barriers;

  g_return_val_if_fail (GST_IS_VULKAN_OPERATION (self), FALSE);
  g_return_val_if_fail (GST_IS_BUFFER (frame), FALSE);

#if defined(VK_KHR_synchronization2)
  priv = GET_PRIV (self);
#endif
  n_mems = gst_buffer_n_memory (frame);

  GST_OBJECT_LOCK (self);
  barriers = _get_image_barriers_unlocked (self);

  frames = _get_dependency_frames (self);
  for (i = 0; i < frames->len; i++) {
    dep_frame = &g_array_index (frames, GstVulkanDependencyFrame, i);
    if (_dep_has_buffer (dep_frame, frame))
      break;
  }

  if (i >= frames->len || !(dep_frame && dep_frame->updated))
    dep_frame = NULL;

  for (i = 0; i < n_mems; i++) {
    guint32 queue_familiy_index = VK_QUEUE_FAMILY_IGNORED;
    GstVulkanImageMemory *vkmem;
    GstMemory *mem = gst_buffer_peek_memory (frame, i);

    if (!gst_is_vulkan_image_memory (mem)) {
      GST_OBJECT_UNLOCK (self);
      GST_ERROR_OBJECT (self, "Memory %" GST_PTR_FORMAT
          " is not a Vulkan Image", mem);
      return FALSE;
    }

    vkmem = (GstVulkanImageMemory *) mem;

    if (dep_frame && dep_frame->new_queue)
      queue_familiy_index = dep_frame->new_queue->family;
    else if (vkmem->barrier.parent.queue)
      queue_familiy_index = vkmem->barrier.parent.queue->family;

    /* *INDENT-OFF* */
#if defined(VK_KHR_synchronization2)
    if (priv->has_sync2) {
      VkImageMemoryBarrier2KHR barrier2  = {
        .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2_KHR,
        .pNext = NULL,
        .srcStageMask = src_stage,
        .dstStageMask = dst_stage,
        .srcAccessMask = dep_frame ?
            dep_frame->new_access : vkmem->barrier.parent.access_flags,
        .dstAccessMask = new_access,
        .oldLayout = dep_frame ?
            dep_frame->new_layout : vkmem->barrier.image_layout,
        .newLayout = new_layout,
        .srcQueueFamilyIndex = queue_familiy_index,
        .dstQueueFamilyIndex = new_queue ?
            new_queue->family : VK_QUEUE_FAMILY_IGNORED,
        .image = vkmem->image,
        .subresourceRange = vkmem->barrier.subresource_range,
      };

      g_array_append_val (barriers, barrier2);
    } else
#endif
    {
      VkImageMemoryBarrier barrier;

      /* this might overflow */
      if (new_access > VK_ACCESS_FLAG_BITS_MAX_ENUM) {
        GST_OBJECT_UNLOCK (self);
        GST_ERROR_OBJECT (self, "Invalid new access value: %" G_GUINT64_FORMAT,
            new_access);
        return FALSE;
      }

      barrier = (VkImageMemoryBarrier) {
        .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
        .pNext = NULL,
        .srcAccessMask = vkmem->barrier.parent.access_flags,
        .dstAccessMask = (VkAccessFlags) new_access,
        .oldLayout = vkmem->barrier.image_layout,
        .newLayout = new_layout,
        .srcQueueFamilyIndex = queue_familiy_index,
        .dstQueueFamilyIndex = new_queue ?
            new_queue->family : VK_QUEUE_FAMILY_IGNORED,
        .image = vkmem->image,
        .subresourceRange = vkmem->barrier.subresource_range,
      };

      g_array_append_val (barriers, barrier);
    }
    /* *INDENT-ON* */
  }

  gst_vulkan_operation_update_frame_unlocked (self, frame, dst_stage,
      new_access, new_layout, new_queue);
  GST_OBJECT_UNLOCK (self);

  return TRUE;
}

/**
 * gst_vulkan_operation_add_dependency_frame:
 * @self: a #GstVulkanOperation
 * @frame: a Vulkan Image #GstBuffer
 * @wait_stage: pipeline stage to wait (VkPipelineStageFlags or
 *   VkPipelineStageFlags2)
 * @signal_stage: pipeline stage to signal (VkPipelineStageFlags or
 *   VkPipelineStageFlags2)
 *
 * Add @frame as an operation dependency by adding the timeline semaphores in
 * each memory of @frame into either the wait semaphore array. The signal array
 * hold the same semaphores but increasing their current value.
 *
 * Returns: whether the @frame was added as dependency.
 */
gboolean
gst_vulkan_operation_add_dependency_frame (GstVulkanOperation * self,
    GstBuffer * frame, guint64 wait_stage, guint64 signal_stage)
{
  GstVulkanOperationPrivate *priv;
  guint i, n_mems;
  GArray *frames;
  GstVulkanDependencyFrame *dep_frame = NULL;

  g_return_val_if_fail (GST_IS_VULKAN_OPERATION (self), FALSE);
  g_return_val_if_fail (GST_IS_BUFFER (frame), FALSE);

#if defined(VK_KHR_timeline_semaphore)
  priv = GET_PRIV (self);

  GST_OBJECT_LOCK (self);
  frames = _get_dependency_frames (self);

  for (i = 0; i < frames->len; i++) {
    dep_frame = &g_array_index (frames, GstVulkanDependencyFrame, i);
    if (_dep_has_buffer (dep_frame, frame) && dep_frame->semaphored) {
      GST_OBJECT_UNLOCK (self);
      return TRUE;
    }
  }

  if (i >= frames->len) {
    GstVulkanDependencyFrame dframe = {
      .semaphored = TRUE,
    };
    _dep_set_buffer (&dframe, frame);
    g_array_append_val (frames, dframe);
  } else if (dep_frame) {
    dep_frame->semaphored = TRUE;
  }
#if defined(VK_KHR_synchronization2)
  if (priv->has_sync2 && priv->has_timeline) {
    if (!priv->deps.signal_semaphores) {
      priv->deps.signal_semaphores =
          g_array_new (FALSE, FALSE, sizeof (VkSemaphoreSubmitInfoKHR));
    }

    if (!priv->deps.wait_semaphores) {
      priv->deps.wait_semaphores =
          g_array_new (FALSE, FALSE, sizeof (VkSemaphoreSubmitInfoKHR));
    }

    n_mems = gst_buffer_n_memory (frame);
    for (i = 0; i < n_mems; i++) {
      GstVulkanImageMemory *vkmem;
      GstMemory *mem = gst_buffer_peek_memory (frame, i);

      if (!gst_is_vulkan_image_memory (mem)) {
        GST_OBJECT_UNLOCK (self);
        GST_ERROR_OBJECT (self, "Memory %" GST_PTR_FORMAT
            " is not a Vulkan Image", mem);
        return FALSE;
      }

      vkmem = (GstVulkanImageMemory *) mem;

      if (vkmem->barrier.parent.semaphore == VK_NULL_HANDLE)
        break;

      /* *INDENT-OFF* */
      g_array_append_vals (priv->deps.wait_semaphores, &(VkSemaphoreSubmitInfoKHR) {
          .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO_KHR,
          .semaphore = vkmem->barrier.parent.semaphore,
          .value = vkmem->barrier.parent.semaphore_value,
          .stageMask = wait_stage,
        }, 1);
      g_array_append_vals (priv->deps.signal_semaphores, &(VkSemaphoreSubmitInfoKHR) {
          .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO_KHR,
          .semaphore = vkmem->barrier.parent.semaphore,
          .value = vkmem->barrier.parent.semaphore_value + 1,
          .stageMask = signal_stage,
        }, 1);
      /* *INDENT-ON* */
    }

    GST_OBJECT_UNLOCK (self);
    return TRUE;
  }
#endif /* synchronization2 */
  if (priv->has_timeline && wait_stage <= G_MAXUINT32) {
    if (!priv->deps.signal_semaphores) {
      priv->deps.signal_semaphores =
          g_array_new (FALSE, FALSE, sizeof (VkSemaphore));
    }

    if (!priv->deps.wait_semaphores) {
      priv->deps.wait_semaphores =
          g_array_new (FALSE, FALSE, sizeof (VkSemaphore));
    }

    if (!priv->deps.wait_dst_stage_mask) {
      priv->deps.wait_dst_stage_mask =
          g_array_new (FALSE, FALSE, sizeof (VkPipelineStageFlags));
    }

    if (!priv->deps.wait_semaphore_values) {
      priv->deps.wait_semaphore_values =
          g_array_new (FALSE, FALSE, sizeof (guint64));
    }
    if (!priv->deps.signal_semaphore_values) {
      priv->deps.signal_semaphore_values =
          g_array_new (FALSE, FALSE, sizeof (guint64));
    }

    n_mems = gst_buffer_n_memory (frame);
    for (i = 0; i < n_mems; i++) {
      GstVulkanImageMemory *vkmem;
      GstMemory *mem = gst_buffer_peek_memory (frame, i);
      VkPipelineStageFlags wait_stage1 = (VkPipelineStageFlags) wait_stage;
      guint64 signal_value;

      if (!gst_is_vulkan_image_memory (mem)) {
        GST_OBJECT_UNLOCK (self);
        GST_ERROR_OBJECT (self, "Memory %" GST_PTR_FORMAT
            " is not a Vulkan Image", mem);
        return FALSE;
      }

      vkmem = (GstVulkanImageMemory *) mem;

      if (vkmem->barrier.parent.semaphore == VK_NULL_HANDLE)
        break;

      g_array_append_val (priv->deps.wait_semaphores,
          vkmem->barrier.parent.semaphore);
      g_array_append_val (priv->deps.signal_semaphores,
          vkmem->barrier.parent.semaphore);
      g_array_append_val (priv->deps.wait_semaphore_values,
          vkmem->barrier.parent.semaphore_value);
      signal_value = vkmem->barrier.parent.semaphore_value + 1;
      g_array_append_val (priv->deps.signal_semaphore_values, signal_value);
      g_array_append_val (priv->deps.wait_dst_stage_mask, wait_stage1);
    }

    GST_OBJECT_UNLOCK (self);
    return TRUE;
  }

  GST_OBJECT_UNLOCK (self);
#endif /* timeline semaphore */

  return TRUE;
}

/**
 * gst_vulkan_operation_discard_dependencies:
 * @self: a #GstVulkanOperation
 *
 * Discards barriers, and all the semaphore arrays populated by
 * gst_vulkan_operation_add_dependency_frame().
 */
void
gst_vulkan_operation_discard_dependencies (GstVulkanOperation * self)
{
  GstVulkanOperationPrivate *priv;

  g_return_if_fail (GST_IS_VULKAN_OPERATION (self));

  priv = GET_PRIV (self);

  GST_OBJECT_LOCK (self);
  g_clear_pointer (&priv->barriers, g_array_unref);

  g_clear_pointer (&priv->deps.frames, g_array_unref);
  g_clear_pointer (&priv->deps.signal_semaphores, g_array_unref);
  g_clear_pointer (&priv->deps.wait_semaphores, g_array_unref);

  g_clear_pointer (&priv->deps.wait_dst_stage_mask, g_array_unref);
  g_clear_pointer (&priv->deps.wait_semaphore_values, g_array_unref);
  g_clear_pointer (&priv->deps.signal_semaphore_values, g_array_unref);

  GST_OBJECT_UNLOCK (self);
}

#if defined(VK_KHR_video_queue)
static inline gboolean
_query_type_is_video (VkQueryType query_type)
{
  if (query_type == VK_QUERY_TYPE_RESULT_STATUS_ONLY_KHR)
    return TRUE;
# if defined(VK_KHR_video_encode_queue)
  if (query_type == VK_QUERY_TYPE_VIDEO_ENCODE_FEEDBACK_KHR)
    return TRUE;
# endif
  return FALSE;
}
#endif /* defined(VK_KHR_video_queue) */

/**
 * gst_vulkan_operation_enable_query:
 * @self: a #GstVulkanOperation
 * @query_type: (type guint32): the VkQueryType to enable
 * @n_queries: number of queries to enable
 * @pnext: the structure pointer to use as pNext
 * @error: a #GError
 *
 * Tries to enable the query pool for the current operation.
 *
 * Returns: whether the query pool was enabled. It might populate @error in case
 *    of error.
 *
 * Since: 1.24
 */
gboolean
gst_vulkan_operation_enable_query (GstVulkanOperation * self,
    VkQueryType query_type, guint n_queries, gpointer pnext, GError ** error)
{
  GstVulkanOperationPrivate *priv;
  VkQueryPoolCreateInfo query_pool_info = {
    .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
    .pNext = pnext,
    .queryType = query_type,
    .queryCount = n_queries,
  };
  VkResult res;
  size_t stride = 0;

  g_return_val_if_fail (GST_IS_VULKAN_OPERATION (self), FALSE);
  g_return_val_if_fail (n_queries > 0, FALSE);

  priv = GET_PRIV (self);

  if (priv->query_pool)
    return TRUE;

#if defined(VK_KHR_video_queue)
  {
    guint32 queue_family = priv->cmd_pool->queue->family;
    GstVulkanPhysicalDevice *device =
        priv->cmd_pool->queue->device->physical_device;

    /*
     * The VK_QUERY_TYPE_RESULT_STATUS_ONLY_KHR can be optional, so .query_result_status
     * can be FALSE, see AMD's case.
     * vkCreateQueryPool needs to be called when the query is
     * VK_QUERY_TYPE_VIDEO_ENCODE_FEEDBACK_KHR to enable it anyway.
     */
    if (!device->queue_family_ops[queue_family].query_result_status &&
        query_type == VK_QUERY_TYPE_RESULT_STATUS_ONLY_KHR) {
      g_set_error (error, GST_VULKAN_ERROR, VK_ERROR_FEATURE_NOT_PRESENT,
          "Queue %" GST_PTR_FORMAT
          " doesn't support result status query operations",
          priv->cmd_pool->queue);
      return FALSE;
    }

    priv->use_inline_query = (_query_type_is_video (query_type)
        && priv->has_video && priv->has_video_maintenance1
        && (vk_find_struct (pnext,
                VK_STRUCTURE_TYPE_VIDEO_PROFILE_INFO_KHR) != NULL));
  }
#endif

  res = vkCreateQueryPool (priv->cmd_pool->queue->device->device,
      &query_pool_info, NULL, &priv->query_pool);
  if (gst_vulkan_error_to_g_error (res, error,
          "vkCreateQueryPool") != VK_SUCCESS)
    return FALSE;

  priv->query_type = query_type;
  priv->n_queries = n_queries;

  /* @TODO:
   * + support 64bit
   * + result support other structures besides a guint32 array
   */
  switch (query_type) {
#if defined(VK_KHR_video_queue)
    case VK_QUERY_TYPE_RESULT_STATUS_ONLY_KHR:
      if (priv->has_video)
        stride = sizeof (guint32);
      break;
#endif
#if defined(VK_KHR_video_encode_queue)
    case VK_QUERY_TYPE_VIDEO_ENCODE_FEEDBACK_KHR:
      if (priv->has_video)
        stride = sizeof (GstVulkanEncodeQueryResult);
      break;
#endif
    default:
      break;
  }

  priv->query_data_size = n_queries * stride;
  priv->query_data_stride = stride;
  priv->query_data = g_malloc0 (priv->query_data_size);

  return TRUE;
}

/**
 * gst_vulkan_operation_get_query:
 * @self: a #GstVulkanOperation
 * @data: (out callee-allocates) (transfer none): result of all queries
 * @error: a #GError
 *
 * Gets the latest operation results of all the queries in @data. API users have
 * to parse the binary array of @data according of their needs (usually is a
 * guint32 array of size of n_query).
 *
 * Don't free @data.
 *
 * Returns: whether a status was fetched. If not, it might populate @error
 */
gboolean
gst_vulkan_operation_get_query (GstVulkanOperation * self, gpointer * result,
    GError ** error)
{
  GstVulkanOperationPrivate *priv;
  VkResult res;
  VkQueryResultFlagBits flags = 0;

  g_return_val_if_fail (GST_IS_VULKAN_OPERATION (self), FALSE);

  priv = GET_PRIV (self);
  if (!priv->query_pool || !priv->query_data || !priv->op_submitted)
    return TRUE;

#if defined(VK_KHR_video_queue)
  if (priv->has_video && _query_type_is_video (priv->query_type)) {
    flags |= VK_QUERY_RESULT_WITH_STATUS_BIT_KHR;
  }
#endif

  /* grab the results of all the queries */
  res = vkGetQueryPoolResults (priv->cmd_pool->queue->device->device,
      priv->query_pool, 0, priv->n_queries, priv->query_data_size,
      priv->query_data, priv->query_data_stride, flags);
  if (res != VK_SUCCESS && res != VK_NOT_READY) {
    gst_vulkan_error_to_g_error (res, error, "vkGetQueryPoolResults");
    return FALSE;
  }

  if (result)
    *result = priv->query_data;

  return TRUE;
}

/**
 * gst_vulkan_operation_begin_query:
 * @self: a #GstVulkanOperation
 * @base: a VkBaseInStructure base
 * @id: query id
 *
 * Begins a query operation with @id in the current command buffer. If video maintenance1 extension
 * is available the query will be recorded as a video inline query. If NULL is passed to @base,
 * the query will be recorded as a normal query anyway.
 *
 * Returns: whether the begin command was set
 *
 * Since: 1.26
 *
 */
gboolean
gst_vulkan_operation_begin_query (GstVulkanOperation * self,
    VkBaseInStructure * base, guint32 id)
{
  GstVulkanOperationPrivate *priv;

  g_return_val_if_fail (GST_IS_VULKAN_OPERATION (self), FALSE);

  priv = GET_PRIV (self);
  if (!priv->query_pool)
    return TRUE;

  if (!self->cmd_buf) {
    GST_INFO_OBJECT (self, "Cannot begin query without begin operation");
    return FALSE;
  }

#if defined(VK_KHR_video_maintenance1)
  if (priv->use_inline_query) {
    g_return_val_if_fail (base, FALSE);

    /* *INDENT-OFF* */
    priv->inline_query = (VkVideoInlineQueryInfoKHR) {
      .sType = VK_STRUCTURE_TYPE_VIDEO_INLINE_QUERY_INFO_KHR,
      .pNext = base->pNext,
      .queryPool = priv->query_pool,
      .firstQuery = id,
      .queryCount = 1,
    };
    /* *INDENT-ON* */
    base->pNext = (VkBaseInStructure *) & priv->inline_query;

    return TRUE;
  }
#endif

  gst_vulkan_command_buffer_lock (self->cmd_buf);
  vkCmdBeginQuery (self->cmd_buf->cmd, priv->query_pool, id, 0);
  gst_vulkan_command_buffer_unlock (self->cmd_buf);

  return TRUE;
}

/**
 * gst_vulkan_operation_end_query:
 * @self: a #GstVulkanOperation
 * @id: query id
 *
 * Ends a query operation with @id in the current command buffer. A query with
 * @id has had started with gst_vulkan_operation_begin_query()
 *
 * Returns: whether the end command was set
 */
gboolean
gst_vulkan_operation_end_query (GstVulkanOperation * self, guint32 id)
{
  GstVulkanOperationPrivate *priv;

  g_return_val_if_fail (GST_IS_VULKAN_OPERATION (self), FALSE);

  priv = GET_PRIV (self);
  if (!priv->query_pool)
    return TRUE;

  if (!self->cmd_buf) {
    GST_INFO_OBJECT (self, "Cannot end query without begin operation");
    return FALSE;
  }

  if (priv->use_inline_query)
    return TRUE;

  gst_vulkan_command_buffer_lock (self->cmd_buf);
  vkCmdEndQuery (self->cmd_buf->cmd, priv->query_pool, id);
  gst_vulkan_command_buffer_unlock (self->cmd_buf);

  return TRUE;
}

/**
 * gst_vulkan_operation_use_sync2:
 * @self: a #GstVulkanOperation
 *
 * Returns: whether the operations are using synchronization2 extension.
 */
gboolean
gst_vulkan_operation_use_sync2 (GstVulkanOperation * self)
{
  g_return_val_if_fail (GST_IS_VULKAN_OPERATION (self), FALSE);

  return GET_PRIV (self)->has_sync2;
}

/**
 * gst_vulkan_operation_pipeline_barrier2:
 * @self: a #GstVulkanOperation
 * @dependency_info: a pointer to VkDependencyInfo
 *
 * It's a wrapper to vkCmdPipelineBarrier2{KHR} if it's available.
 *
 * Returns: %TRUE if vkCmdPipelineBarrier2{KHR} it's available. %FALSE,
 *   otherwise.
 */
gboolean
gst_vulkan_operation_pipeline_barrier2 (GstVulkanOperation * self,
    gpointer dependency_info)
{
#if defined(VK_KHR_synchronization2)
  GstVulkanOperationPrivate *priv;

  g_return_val_if_fail (GST_IS_VULKAN_OPERATION (self), FALSE);

  priv = GET_PRIV (self);

  if (priv->has_sync2) {
    VkDependencyInfoKHR *info = dependency_info;

    g_return_val_if_fail (info
        && info->sType == VK_STRUCTURE_TYPE_DEPENDENCY_INFO_KHR, FALSE);

    if (!self->cmd_buf) {
      GST_INFO_OBJECT (self, "Cannot record a barrier without begin operation");
      return FALSE;
    }

    gst_vulkan_command_buffer_lock (self->cmd_buf);
    priv->CmdPipelineBarrier2 (self->cmd_buf->cmd, info);
    gst_vulkan_command_buffer_unlock (self->cmd_buf);
    return TRUE;
  }
#endif
  return FALSE;
}


/**
 * gst_vulkan_operation_new:
 * @cmd_pool: a #GstVulkanCommandPool
 *
 * Returns: (transfer full): a newly allocated #GstVulkanOperation
 */
GstVulkanOperation *
gst_vulkan_operation_new (GstVulkanCommandPool * cmd_pool)
{
  GstVulkanOperation *self;

  g_return_val_if_fail (GST_IS_VULKAN_COMMAND_POOL (cmd_pool), NULL);

  self = g_object_new (GST_TYPE_VULKAN_OPERATION, "command-pool", cmd_pool,
      NULL);

  gst_object_ref_sink (self);

  return self;
}
