#include "config.h"

#include "gskvulkanframeprivate.h"

#include "gskgpuopprivate.h"
#include "gskgpuutilsprivate.h"
#include "gskgpuglobalsopprivate.h"
#include "gskvulkanbufferprivate.h"
#include "gskvulkandeviceprivate.h"
#include "gskvulkanimageprivate.h"

#include "gdk/gdkdmabuftextureprivate.h"
#include "gdk/gdkglcontextprivate.h"
#include "gdk/gdkgltextureprivate.h"

#ifdef GDK_WINDOWING_WIN32
#include "gdk/win32/gdkd3d12textureprivate.h"
#endif

#define GDK_ARRAY_NAME gsk_semaphores
#define GDK_ARRAY_TYPE_NAME GskSemaphores
#define GDK_ARRAY_ELEMENT_TYPE VkSemaphore
#define GDK_ARRAY_PREALLOC 16
#define GDK_ARRAY_NO_MEMSET 1
#include "gdk/gdkarrayimpl.c"

#define GDK_ARRAY_NAME gsk_semaphore_values
#define GDK_ARRAY_TYPE_NAME GskSemaphoreValues
#define GDK_ARRAY_ELEMENT_TYPE uint64_t
#define GDK_ARRAY_PREALLOC 16
#define GDK_ARRAY_NO_MEMSET 1
#include "gdk/gdkarrayimpl.c"

#define GDK_ARRAY_NAME gsk_pipeline_stages
#define GDK_ARRAY_TYPE_NAME GskPipelineStages
#define GDK_ARRAY_ELEMENT_TYPE VkPipelineStageFlags
#define GDK_ARRAY_PREALLOC 16
#define GDK_ARRAY_NO_MEMSET 1
#include "gdk/gdkarrayimpl.c"

struct _GskVulkanSemaphores
{
  GskSemaphores wait_semaphores;
  GskSemaphoreValues wait_semaphore_values;
  GskPipelineStages wait_stages;
  GskSemaphores signal_semaphores;
};

typedef struct _GskVulkanFramePrivate GskVulkanFramePrivate;

struct _GskVulkanFramePrivate
{
  VkSemaphore vk_acquire_semaphore;
  VkFence vk_fence;
  VkCommandBuffer vk_command_buffer;

  gsize pool_n_sets;
  gsize pool_n_images;
  gsize pool_n_buffers;

  VkDescriptorSet vk_globals_descriptor_set;
  GskGpuBuffer   *globals_descriptor_for;
  gsize           globals_pool_id;
};

G_DEFINE_TYPE_WITH_PRIVATE (GskVulkanFrame, gsk_vulkan_frame, GSK_TYPE_GPU_FRAME)

static gboolean
gsk_vulkan_frame_is_busy (GskGpuFrame *frame)
{
  GskVulkanFrame *self = GSK_VULKAN_FRAME (frame);
  GskVulkanFramePrivate *priv = gsk_vulkan_frame_get_instance_private (self);
  VkDevice device;
  VkResult res;

  device = gsk_vulkan_device_get_vk_device (GSK_VULKAN_DEVICE (gsk_gpu_frame_get_device (frame)));

  res = vkGetFenceStatus (device, priv->vk_fence);
  if (res == VK_NOT_READY)
    return TRUE;

  gsk_vulkan_handle_result (res, "vkGetFenceStatus");
  return res;
}

static void
gsk_vulkan_frame_wait (GskGpuFrame *frame)
{
  GskVulkanFrame *self = GSK_VULKAN_FRAME (frame);
  GskVulkanFramePrivate *priv = gsk_vulkan_frame_get_instance_private (self);
  VkDevice vk_device;

  vk_device = gsk_vulkan_device_get_vk_device (GSK_VULKAN_DEVICE (gsk_gpu_frame_get_device (frame)));

  GSK_VK_CHECK (vkWaitForFences, vk_device,
                                 1,
                                 &priv->vk_fence,
                                 VK_FALSE,
                                 INT64_MAX);
}

static void
gsk_vulkan_frame_setup (GskGpuFrame *frame)
{
  GskVulkanFrame *self = GSK_VULKAN_FRAME (frame);
  GskVulkanFramePrivate *priv = gsk_vulkan_frame_get_instance_private (self);
  GskVulkanDevice *device;
  VkDevice vk_device;
  VkCommandPool vk_command_pool;

  device = GSK_VULKAN_DEVICE (gsk_gpu_frame_get_device (frame));
  vk_device = gsk_vulkan_device_get_vk_device (device);
  vk_command_pool = gsk_vulkan_device_get_vk_command_pool (device);

  GSK_VK_CHECK (vkAllocateCommandBuffers, vk_device,
                                          &(VkCommandBufferAllocateInfo) {
                                              .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
                                              .commandPool = vk_command_pool,
                                              .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
                                              .commandBufferCount = 1,
                                          },
                                          &priv->vk_command_buffer);

  GDK_VK_CHECK (vkCreateSemaphore, vk_device,
                                   &(VkSemaphoreCreateInfo) {
                                       .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
                                   },
                                   NULL,
                                   &priv->vk_acquire_semaphore);

  GSK_VK_CHECK (vkCreateFence, vk_device,
                               &(VkFenceCreateInfo) {
                                   .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
                               },
                               NULL,
                               &priv->vk_fence);
}

static void
gsk_vulkan_frame_cleanup (GskGpuFrame *frame)
{
  GskVulkanFrame *self = GSK_VULKAN_FRAME (frame);
  GskVulkanFramePrivate *priv = gsk_vulkan_frame_get_instance_private (self);
  GskVulkanDevice *device;
  VkDevice vk_device;

  device = GSK_VULKAN_DEVICE (gsk_gpu_frame_get_device (frame));
  vk_device = gsk_vulkan_device_get_vk_device (device);

  GSK_VK_CHECK (vkWaitForFences, vk_device,
                                 1,
                                 &priv->vk_fence,
                                 VK_TRUE,
                                 INT64_MAX);

  GSK_VK_CHECK (vkResetFences, vk_device,
                               1,
                               &priv->vk_fence);

  GSK_VK_CHECK (vkResetCommandBuffer, priv->vk_command_buffer,
                                      0);

  GSK_GPU_FRAME_CLASS (gsk_vulkan_frame_parent_class)->cleanup (frame);
}

static void
gsk_vulkan_frame_begin (GskGpuFrame           *frame,
                        GdkDrawContext        *context,
                        GskRenderNode         *node,
                        const cairo_region_t  *region)
{
  GskVulkanFrame *self = GSK_VULKAN_FRAME (frame);
  GskVulkanFramePrivate *priv = gsk_vulkan_frame_get_instance_private (self);

  gdk_draw_context_begin_frame_full (context,
                                     /* We pass a pointer here for 32bit architectures */
                                     &priv->vk_acquire_semaphore,
                                     node,
                                     region);
}

static GskGpuImage *
gsk_vulkan_frame_upload_texture (GskGpuFrame  *frame,
                                 gboolean      with_mipmap,
                                 GdkTexture   *texture)
{
#ifdef HAVE_DMABUF
  if (GDK_IS_GL_TEXTURE (texture))
    {
      GdkGLTexture *gltexture = GDK_GL_TEXTURE (texture);
      GdkDisplay *display;
      GdkGLContext *glcontext;
      GdkDmabuf dmabuf;

      display = gsk_gpu_device_get_display (gsk_gpu_frame_get_device (frame));
      glcontext = gdk_display_get_gl_context (display);
      if (glcontext && gdk_gl_context_is_shared (glcontext, gdk_gl_texture_get_context (gltexture)))
        {
          gdk_gl_context_make_current (glcontext);

          if (gdk_gl_context_export_dmabuf (gdk_gl_texture_get_context (gltexture),
                                            gdk_gl_texture_get_id (gltexture),
                                            &dmabuf))
            {
              GskGpuImage *image;

              image = gsk_vulkan_image_new_for_dmabuf (GSK_VULKAN_DEVICE (gsk_gpu_frame_get_device (frame)),
                                                       gdk_texture_get_width (texture),
                                                       gdk_texture_get_height (texture),
                                                       &dmabuf,
                                                       gdk_memory_format_alpha (gdk_texture_get_format (texture)) == GDK_MEMORY_ALPHA_PREMULTIPLIED,
                                                       GSK_GPU_CONVERSION_NONE);

              /* Vulkan import dups the fds, so we can close these */
              gdk_dmabuf_close_fds (&dmabuf);

              if (image)
                {
                  gsk_gpu_image_toggle_ref_texture (image, texture);
                  return image;
                }
            }
        }
    }

  if (GDK_IS_DMABUF_TEXTURE (texture))
    {
      GskGpuImage *image;

      image = gsk_vulkan_image_new_for_dmabuf (GSK_VULKAN_DEVICE (gsk_gpu_frame_get_device (frame)),
                                               gdk_texture_get_width (texture),
                                               gdk_texture_get_height (texture),
                                               gdk_dmabuf_texture_get_dmabuf (GDK_DMABUF_TEXTURE (texture)),
                                               gdk_memory_format_alpha (gdk_texture_get_format (texture)) == GDK_MEMORY_ALPHA_PREMULTIPLIED,
                                               gsk_gpu_color_state_get_conversion (gdk_texture_get_color_state (texture)));
      if (image)
        {
          gsk_gpu_image_toggle_ref_texture (image, texture);
          return image;
        }
    }
#endif
#ifdef GDK_WINDOWING_WIN32
  if (GDK_IS_D3D12_TEXTURE (texture))
    {
      GdkD3D12Texture *d3d_texture = GDK_D3D12_TEXTURE (texture);
      GskGpuImage *image;

      image = gsk_vulkan_image_new_for_d3d12resource (GSK_VULKAN_DEVICE (gsk_gpu_frame_get_device (frame)),
                                                      gdk_d3d12_texture_get_resource (d3d_texture),
                                                      gdk_d3d12_texture_get_resource_handle (d3d_texture),
                                                      gdk_d3d12_texture_get_fence (d3d_texture),
                                                      gdk_d3d12_texture_get_fence_handle (d3d_texture),
                                                      gdk_d3d12_texture_get_fence_wait (d3d_texture),
                                                      gdk_memory_format_alpha (gdk_texture_get_format (texture)) != GDK_MEMORY_ALPHA_STRAIGHT);
      if (image)
        {
          gsk_gpu_image_toggle_ref_texture (image, texture);
          return image;
        }
    }
#endif

  return GSK_GPU_FRAME_CLASS (gsk_vulkan_frame_parent_class)->upload_texture (frame, with_mipmap, texture);
}

static GskGpuBuffer *
gsk_vulkan_frame_create_vertex_buffer (GskGpuFrame *frame,
                                       gsize        size)
{
  return gsk_vulkan_buffer_new_vertex (GSK_VULKAN_DEVICE (gsk_gpu_frame_get_device (frame)), size);
}

static GskGpuBuffer *
gsk_vulkan_frame_create_globals_buffer (GskGpuFrame *frame,
                                        gsize        size)
{
  return gsk_vulkan_buffer_new_globals (GSK_VULKAN_DEVICE (gsk_gpu_frame_get_device (frame)), size);
}

static GskGpuBuffer *
gsk_vulkan_frame_create_storage_buffer (GskGpuFrame *frame,
                                        gsize        size)
{
  return gsk_vulkan_buffer_new_storage (GSK_VULKAN_DEVICE (gsk_gpu_frame_get_device (frame)), size);
}

static void
gsk_vulkan_frame_write_texture_vertex_data (GskGpuFrame    *self,
                                            guchar         *data,
                                            GskGpuImage   **images,
                                            GskGpuSampler  *samplers,
                                            gsize           n_images)
{
}

static void
gsk_vulkan_frame_submit_ops (GskVulkanFrame        *frame,
                             GskVulkanCommandState *state,
                             GskGpuOp              *op)
{
  while (op)
    {
      op = gsk_gpu_op_vk_command (op, GSK_GPU_FRAME (frame), state);
    }
}

static void
gsk_vulkan_frame_submit (GskGpuFrame       *frame,
                         GskRenderPassType  pass_type,
                         GskGpuBuffer      *vertex_buffer,
                         GskGpuBuffer      *globals_buffer,
                         GskGpuOp          *op)
{
  GskVulkanFrame *self = GSK_VULKAN_FRAME (frame);
  GskVulkanFramePrivate *priv = gsk_vulkan_frame_get_instance_private (self);
  GskVulkanDevice *device;
  GskVulkanSemaphores semaphores;
  GskVulkanCommandState state = { 0, };

  device = GSK_VULKAN_DEVICE (gsk_gpu_frame_get_device (frame));

  GSK_VK_CHECK (vkBeginCommandBuffer, priv->vk_command_buffer,
                                      &(VkCommandBufferBeginInfo) {
                                          .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
                                          .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
                                      });

  if (vertex_buffer)
    vkCmdBindVertexBuffers (priv->vk_command_buffer,
                            0,
                            1,
                            (VkBuffer[1]) {
                                gsk_vulkan_buffer_get_vk_buffer (GSK_VULKAN_BUFFER (vertex_buffer))
                            },
                            (VkDeviceSize[1]) { 0 });

  gsk_semaphores_init (&semaphores.wait_semaphores);
  gsk_semaphore_values_init (&semaphores.wait_semaphore_values);
  gsk_pipeline_stages_init (&semaphores.wait_stages);
  gsk_semaphores_init (&semaphores.signal_semaphores);

  if (pass_type == GSK_RENDER_PASS_PRESENT)
    {
      GdkVulkanContext *context = GDK_VULKAN_CONTEXT (gsk_gpu_frame_get_context (frame));
      gsk_vulkan_semaphores_add_wait (&semaphores,
                                      priv->vk_acquire_semaphore,
                                      0,
                                      VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
      gsk_vulkan_semaphores_add_signal (&semaphores,
                                        gdk_vulkan_context_get_present_semaphore (context));
    }

  state.vk_command_buffer = priv->vk_command_buffer;
  state.vk_render_pass = VK_NULL_HANDLE;
  state.vk_format = VK_FORMAT_UNDEFINED;
  state.blend = GSK_GPU_BLEND_OVER; /* should we have a BLEND_NONE? */
  state.semaphores = &semaphores;

  if (globals_buffer != NULL)
    {
      if (priv->vk_globals_descriptor_set == VK_NULL_HANDLE)
        priv->vk_globals_descriptor_set = gsk_vulkan_device_allocate_descriptor (device,
                                           gsk_vulkan_device_get_vk_globals_set_layout (device),
                                           &priv->globals_pool_id);
      if (priv->globals_descriptor_for != globals_buffer)
        {
          vkUpdateDescriptorSets (gsk_vulkan_device_get_vk_device (device), 1,
              &(VkWriteDescriptorSet) {
                  .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
                  .dstSet = priv->vk_globals_descriptor_set,
                  .dstBinding = 0,
                  .descriptorCount = 1,
                  .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
                  .pBufferInfo = &(VkDescriptorBufferInfo) {
                      .buffer = gsk_vulkan_buffer_get_vk_buffer (GSK_VULKAN_BUFFER (globals_buffer)),
                      .offset = 0,
                      .range = sizeof (GskGpuGlobalsInstance),
                  },
              }, 0, NULL);
          priv->globals_descriptor_for = globals_buffer;
        }
      state.vk_globals_descriptor_set = priv->vk_globals_descriptor_set;
    }

  GSK_VULKAN_FRAME_GET_CLASS (self)->submit_ops (self, &state, op);

  GSK_VK_CHECK (vkEndCommandBuffer, priv->vk_command_buffer);

  GSK_VK_CHECK (vkQueueSubmit, gsk_vulkan_device_get_vk_queue (device),
                               1,
                               &(VkSubmitInfo) {
                                  .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
                                  .commandBufferCount = 1,
                                  .pCommandBuffers = &priv->vk_command_buffer,
                                  .pWaitSemaphores = gsk_semaphores_get_data (&semaphores.wait_semaphores),
                                  .pWaitDstStageMask = gsk_pipeline_stages_get_data (&semaphores.wait_stages),
                                  .waitSemaphoreCount = gsk_semaphores_get_size (&semaphores.wait_semaphores),
                                  .pSignalSemaphores = gsk_semaphores_get_data (&semaphores.signal_semaphores),
                                  .signalSemaphoreCount = gsk_semaphores_get_size (&semaphores.signal_semaphores),
                                  .pNext = gsk_vulkan_device_has_feature (device, GDK_VULKAN_FEATURE_TIMELINE_SEMAPHORE) ? &(VkTimelineSemaphoreSubmitInfo) {
                                       .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
                                       .waitSemaphoreValueCount = gsk_semaphore_values_get_size (&semaphores.wait_semaphore_values),
                                       .pWaitSemaphoreValues = gsk_semaphore_values_get_data (&semaphores.wait_semaphore_values),
                                  } : NULL,
                               },
                               priv->vk_fence);

  gsk_semaphores_clear (&semaphores.wait_semaphores);
  gsk_semaphore_values_clear (&semaphores.wait_semaphore_values);
  gsk_pipeline_stages_clear (&semaphores.wait_stages);
  gsk_semaphores_clear (&semaphores.signal_semaphores);
}

static void
gsk_vulkan_frame_finalize (GObject *object)
{
  GskVulkanFrame *self = GSK_VULKAN_FRAME (object);
  GskVulkanFramePrivate *priv = gsk_vulkan_frame_get_instance_private (self);
  GskVulkanDevice *device;
  VkDevice vk_device;
  VkCommandPool vk_command_pool;

  device = GSK_VULKAN_DEVICE (gsk_gpu_frame_get_device (GSK_GPU_FRAME (self)));
  vk_device = gsk_vulkan_device_get_vk_device (device);
  vk_command_pool = gsk_vulkan_device_get_vk_command_pool (device);

  vkFreeCommandBuffers (vk_device,
                        vk_command_pool,
                        1, &priv->vk_command_buffer);
  vkDestroySemaphore (vk_device,
                      priv->vk_acquire_semaphore,
                      NULL);
  vkDestroyFence (vk_device,
                  priv->vk_fence,
                  NULL);

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

static void
gsk_vulkan_frame_class_init (GskVulkanFrameClass *klass)
{
  GskGpuFrameClass *gpu_frame_class = GSK_GPU_FRAME_CLASS (klass);
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  klass->submit_ops = gsk_vulkan_frame_submit_ops;

  gpu_frame_class->is_busy = gsk_vulkan_frame_is_busy;
  gpu_frame_class->wait = gsk_vulkan_frame_wait;
  gpu_frame_class->setup = gsk_vulkan_frame_setup;
  gpu_frame_class->cleanup = gsk_vulkan_frame_cleanup;
  gpu_frame_class->begin = gsk_vulkan_frame_begin;
  gpu_frame_class->upload_texture = gsk_vulkan_frame_upload_texture;
  gpu_frame_class->create_vertex_buffer = gsk_vulkan_frame_create_vertex_buffer;
  gpu_frame_class->create_globals_buffer = gsk_vulkan_frame_create_globals_buffer;
  gpu_frame_class->create_storage_buffer = gsk_vulkan_frame_create_storage_buffer;
  gpu_frame_class->write_texture_vertex_data = gsk_vulkan_frame_write_texture_vertex_data;
  gpu_frame_class->submit = gsk_vulkan_frame_submit;

  object_class->finalize = gsk_vulkan_frame_finalize;
}

static void
gsk_vulkan_frame_init (GskVulkanFrame *self)
{
  GskVulkanFramePrivate *priv = gsk_vulkan_frame_get_instance_private (self);

  priv->pool_n_sets = 4;
  priv->pool_n_images = 8;
  priv->pool_n_buffers = 8;
}

void
gsk_vulkan_semaphores_add_wait (GskVulkanSemaphores  *self,
                                VkSemaphore           semaphore,
                                uint64_t              semaphore_wait,
                                VkPipelineStageFlags  stage)
{
  gsk_semaphores_append (&self->wait_semaphores, semaphore);
  gsk_semaphore_values_append (&self->wait_semaphore_values, semaphore_wait);
  gsk_pipeline_stages_append (&self->wait_stages, stage);
}

void
gsk_vulkan_semaphores_add_signal (GskVulkanSemaphores  *self,
                                  VkSemaphore           semaphore)
{
  gsk_semaphores_append (&self->signal_semaphores, semaphore);
}

