/*
 * Copyright 2019 Google LLC
 * SPDX-License-Identifier: MIT
 */

#ifndef VN_RENDERER_H
#define VN_RENDERER_H

#include "vn_common.h"

struct vn_renderer_shmem {
   struct vn_refcount refcount;

   uint32_t res_id;
   size_t mmap_size; /* for internal use only (i.e., munmap) */
   void *mmap_ptr;
};

struct vn_renderer_bo {
   struct vn_refcount refcount;

   uint32_t res_id;
   /* for internal use only */
   size_t mmap_size;
   void *mmap_ptr;
};

/*
 * A sync consists of a uint64_t counter.  The counter can be updated by CPU
 * or by GPU.  It can also be waited on by CPU or by GPU until it reaches
 * certain values.
 *
 * This models after timeline VkSemaphore rather than timeline drm_syncobj.
 * The main difference is that drm_syncobj can have unsignaled value 0.
 */
struct vn_renderer_sync {
   uint32_t sync_id;
};

struct vn_renderer_info {
   struct {
      uint16_t vendor_id;
      uint16_t device_id;

      bool has_bus_info;
      uint16_t domain;
      uint8_t bus;
      uint8_t device;
      uint8_t function;
   } pci;

   bool has_dma_buf_import;
   bool has_cache_management;
   bool has_external_sync;
   bool has_implicit_fencing;

   uint32_t max_sync_queue_count;

   /* hw capset */
   uint32_t wire_format_version;
   uint32_t vk_xml_version;
   uint32_t vk_ext_command_serialization_spec_version;
   uint32_t vk_mesa_venus_protocol_spec_version;
};

struct vn_renderer_submit_batch {
   const void *cs_data;
   size_t cs_size;

   /*
    * Submit cs to the virtual sync queue identified by sync_queue_index.  The
    * virtual queue is assumed to be associated with the physical VkQueue
    * identified by vk_queue_id.  After the execution completes on the
    * VkQueue, the virtual sync queue is signaled.
    *
    * sync_queue_index must be less than max_sync_queue_count.
    *
    * vk_queue_id specifies the object id of a VkQueue.
    *
    * When sync_queue_cpu is true, it specifies the special CPU sync queue,
    * and sync_queue_index/vk_queue_id are ignored.  TODO revisit this later
    */
   uint32_t sync_queue_index;
   bool sync_queue_cpu;
   vn_object_id vk_queue_id;

   /* syncs to update when the virtual sync queue is signaled */
   struct vn_renderer_sync *const *syncs;
   /* TODO allow NULL when syncs are all binary? */
   const uint64_t *sync_values;
   uint32_t sync_count;
};

struct vn_renderer_submit {
   /* BOs to pin and to fence implicitly
    *
    * TODO track all bos and automatically pin them.  We don't do it yet
    * because each vn_command_buffer owns a bo.  We can probably make do by
    * returning the bos to a bo cache and exclude bo cache from pinning.
    */
   struct vn_renderer_bo *const *bos;
   uint32_t bo_count;

   const struct vn_renderer_submit_batch *batches;
   uint32_t batch_count;
};

struct vn_renderer_wait {
   bool wait_any;
   uint64_t timeout;

   struct vn_renderer_sync *const *syncs;
   /* TODO allow NULL when syncs are all binary? */
   const uint64_t *sync_values;
   uint32_t sync_count;
};

struct vn_renderer_ops {
   void (*destroy)(struct vn_renderer *renderer,
                   const VkAllocationCallbacks *alloc);

   void (*get_info)(struct vn_renderer *renderer,
                    struct vn_renderer_info *info);

   VkResult (*submit)(struct vn_renderer *renderer,
                      const struct vn_renderer_submit *submit);

   /*
    * On success, returns VK_SUCCESS or VK_TIMEOUT.  On failure, returns
    * VK_ERROR_DEVICE_LOST or out of device/host memory.
    */
   VkResult (*wait)(struct vn_renderer *renderer,
                    const struct vn_renderer_wait *wait);
};

struct vn_renderer_shmem_ops {
   struct vn_renderer_shmem *(*create)(struct vn_renderer *renderer,
                                       size_t size);
   void (*destroy)(struct vn_renderer *renderer,
                   struct vn_renderer_shmem *shmem);
};

struct vn_renderer_bo_ops {
   VkResult (*create_from_device_memory)(
      struct vn_renderer *renderer,
      VkDeviceSize size,
      vn_object_id mem_id,
      VkMemoryPropertyFlags flags,
      VkExternalMemoryHandleTypeFlags external_handles,
      struct vn_renderer_bo **out_bo);

   VkResult (*create_from_dma_buf)(struct vn_renderer *renderer,
                                   VkDeviceSize size,
                                   int fd,
                                   VkMemoryPropertyFlags flags,
                                   struct vn_renderer_bo **out_bo);

   bool (*destroy)(struct vn_renderer *renderer, struct vn_renderer_bo *bo);

   int (*export_dma_buf)(struct vn_renderer *renderer,
                         struct vn_renderer_bo *bo);

   /* map is not thread-safe */
   void *(*map)(struct vn_renderer *renderer, struct vn_renderer_bo *bo);

   void (*flush)(struct vn_renderer *renderer,
                 struct vn_renderer_bo *bo,
                 VkDeviceSize offset,
                 VkDeviceSize size);
   void (*invalidate)(struct vn_renderer *renderer,
                      struct vn_renderer_bo *bo,
                      VkDeviceSize offset,
                      VkDeviceSize size);
};

enum vn_renderer_sync_flags {
   VN_RENDERER_SYNC_SHAREABLE = 1u << 0,
   VN_RENDERER_SYNC_BINARY = 1u << 1,
};

struct vn_renderer_sync_ops {
   VkResult (*create)(struct vn_renderer *renderer,
                      uint64_t initial_val,
                      uint32_t flags,
                      struct vn_renderer_sync **out_sync);

   VkResult (*create_from_syncobj)(struct vn_renderer *renderer,
                                   int fd,
                                   bool sync_file,
                                   struct vn_renderer_sync **out_sync);
   void (*destroy)(struct vn_renderer *renderer,
                   struct vn_renderer_sync *sync);

   int (*export_syncobj)(struct vn_renderer *renderer,
                         struct vn_renderer_sync *sync,
                         bool sync_file);

   /* reset the counter */
   VkResult (*reset)(struct vn_renderer *renderer,
                     struct vn_renderer_sync *sync,
                     uint64_t initial_val);

   /* read the current value from the counter */
   VkResult (*read)(struct vn_renderer *renderer,
                    struct vn_renderer_sync *sync,
                    uint64_t *val);

   /* write a new value (larger than the current one) to the counter */
   VkResult (*write)(struct vn_renderer *renderer,
                     struct vn_renderer_sync *sync,
                     uint64_t val);
};

struct vn_renderer {
   struct vn_renderer_ops ops;
   struct vn_renderer_shmem_ops shmem_ops;
   struct vn_renderer_bo_ops bo_ops;
   struct vn_renderer_sync_ops sync_ops;
};

VkResult
vn_renderer_create_virtgpu(struct vn_instance *instance,
                           const VkAllocationCallbacks *alloc,
                           struct vn_renderer **renderer);

VkResult
vn_renderer_create_vtest(struct vn_instance *instance,
                         const VkAllocationCallbacks *alloc,
                         struct vn_renderer **renderer);

static inline VkResult
vn_renderer_create(struct vn_instance *instance,
                   const VkAllocationCallbacks *alloc,
                   struct vn_renderer **renderer)
{
   if (VN_DEBUG(VTEST)) {
      VkResult result = vn_renderer_create_vtest(instance, alloc, renderer);
      if (result == VK_SUCCESS)
         return VK_SUCCESS;
   }

   return vn_renderer_create_virtgpu(instance, alloc, renderer);
}

static inline void
vn_renderer_destroy(struct vn_renderer *renderer,
                    const VkAllocationCallbacks *alloc)
{
   renderer->ops.destroy(renderer, alloc);
}

static inline void
vn_renderer_get_info(struct vn_renderer *renderer,
                     struct vn_renderer_info *info)
{
   renderer->ops.get_info(renderer, info);
}

static inline VkResult
vn_renderer_submit(struct vn_renderer *renderer,
                   const struct vn_renderer_submit *submit)
{
   return renderer->ops.submit(renderer, submit);
}

static inline VkResult
vn_renderer_submit_simple(struct vn_renderer *renderer,
                          const void *cs_data,
                          size_t cs_size)
{
   const struct vn_renderer_submit submit = {
      .batches =
         &(const struct vn_renderer_submit_batch){
            .cs_data = cs_data,
            .cs_size = cs_size,
         },
      .batch_count = 1,
   };
   return vn_renderer_submit(renderer, &submit);
}

static inline VkResult
vn_renderer_wait(struct vn_renderer *renderer,
                 const struct vn_renderer_wait *wait)
{
   return renderer->ops.wait(renderer, wait);
}

static inline struct vn_renderer_shmem *
vn_renderer_shmem_create(struct vn_renderer *renderer, size_t size)
{
   struct vn_renderer_shmem *shmem =
      renderer->shmem_ops.create(renderer, size);
   if (shmem) {
      assert(vn_refcount_is_valid(&shmem->refcount));
      assert(shmem->res_id);
      assert(shmem->mmap_size >= size);
      assert(shmem->mmap_ptr);
   }

   return shmem;
}

static inline struct vn_renderer_shmem *
vn_renderer_shmem_ref(struct vn_renderer *renderer,
                      struct vn_renderer_shmem *shmem)
{
   vn_refcount_inc(&shmem->refcount);
   return shmem;
}

static inline void
vn_renderer_shmem_unref(struct vn_renderer *renderer,
                        struct vn_renderer_shmem *shmem)
{
   if (vn_refcount_dec(&shmem->refcount))
      renderer->shmem_ops.destroy(renderer, shmem);
}

static inline VkResult
vn_renderer_bo_create_from_device_memory(
   struct vn_renderer *renderer,
   VkDeviceSize size,
   vn_object_id mem_id,
   VkMemoryPropertyFlags flags,
   VkExternalMemoryHandleTypeFlags external_handles,
   struct vn_renderer_bo **out_bo)
{
   struct vn_renderer_bo *bo;
   VkResult result = renderer->bo_ops.create_from_device_memory(
      renderer, size, mem_id, flags, external_handles, &bo);
   if (result != VK_SUCCESS)
      return result;

   assert(vn_refcount_is_valid(&bo->refcount));
   assert(bo->res_id);
   assert(!bo->mmap_size || bo->mmap_size >= size);

   *out_bo = bo;
   return VK_SUCCESS;
}

static inline VkResult
vn_renderer_bo_create_from_dma_buf(struct vn_renderer *renderer,
                                   VkDeviceSize size,
                                   int fd,
                                   VkMemoryPropertyFlags flags,
                                   struct vn_renderer_bo **out_bo)
{
   struct vn_renderer_bo *bo;
   VkResult result =
      renderer->bo_ops.create_from_dma_buf(renderer, size, fd, flags, &bo);
   if (result != VK_SUCCESS)
      return result;

   assert(vn_refcount_is_valid(&bo->refcount));
   assert(bo->res_id);
   assert(!bo->mmap_size || bo->mmap_size >= size);

   *out_bo = bo;
   return VK_SUCCESS;
}

static inline struct vn_renderer_bo *
vn_renderer_bo_ref(struct vn_renderer *renderer, struct vn_renderer_bo *bo)
{
   vn_refcount_inc(&bo->refcount);
   return bo;
}

static inline bool
vn_renderer_bo_unref(struct vn_renderer *renderer, struct vn_renderer_bo *bo)
{
   if (vn_refcount_dec(&bo->refcount))
      return renderer->bo_ops.destroy(renderer, bo);
   return false;
}

static inline int
vn_renderer_bo_export_dma_buf(struct vn_renderer *renderer,
                              struct vn_renderer_bo *bo)
{
   return renderer->bo_ops.export_dma_buf(renderer, bo);
}

static inline void *
vn_renderer_bo_map(struct vn_renderer *renderer, struct vn_renderer_bo *bo)
{
   return renderer->bo_ops.map(renderer, bo);
}

static inline void
vn_renderer_bo_flush(struct vn_renderer *renderer,
                     struct vn_renderer_bo *bo,
                     VkDeviceSize offset,
                     VkDeviceSize end)
{
   renderer->bo_ops.flush(renderer, bo, offset, end);
}

static inline void
vn_renderer_bo_invalidate(struct vn_renderer *renderer,
                          struct vn_renderer_bo *bo,
                          VkDeviceSize offset,
                          VkDeviceSize size)
{
   renderer->bo_ops.invalidate(renderer, bo, offset, size);
}

static inline VkResult
vn_renderer_sync_create(struct vn_renderer *renderer,
                        uint64_t initial_val,
                        uint32_t flags,
                        struct vn_renderer_sync **out_sync)
{
   return renderer->sync_ops.create(renderer, initial_val, flags, out_sync);
}

static inline VkResult
vn_renderer_sync_create_from_syncobj(struct vn_renderer *renderer,
                                     int fd,
                                     bool sync_file,
                                     struct vn_renderer_sync **out_sync)
{
   return renderer->sync_ops.create_from_syncobj(renderer, fd, sync_file,
                                                 out_sync);
}

static inline void
vn_renderer_sync_destroy(struct vn_renderer *renderer,
                         struct vn_renderer_sync *sync)
{
   renderer->sync_ops.destroy(renderer, sync);
}

static inline int
vn_renderer_sync_export_syncobj(struct vn_renderer *renderer,
                                struct vn_renderer_sync *sync,
                                bool sync_file)
{
   return renderer->sync_ops.export_syncobj(renderer, sync, sync_file);
}

static inline VkResult
vn_renderer_sync_reset(struct vn_renderer *renderer,
                       struct vn_renderer_sync *sync,
                       uint64_t initial_val)
{
   return renderer->sync_ops.reset(renderer, sync, initial_val);
}

static inline VkResult
vn_renderer_sync_read(struct vn_renderer *renderer,
                      struct vn_renderer_sync *sync,
                      uint64_t *val)
{
   return renderer->sync_ops.read(renderer, sync, val);
}

static inline VkResult
vn_renderer_sync_write(struct vn_renderer *renderer,
                       struct vn_renderer_sync *sync,
                       uint64_t val)
{
   return renderer->sync_ops.write(renderer, sync, val);
}

static inline VkResult
vn_renderer_submit_simple_sync(struct vn_renderer *renderer,
                               const void *cs_data,
                               size_t cs_size)
{
   struct vn_renderer_sync *sync;
   VkResult result =
      vn_renderer_sync_create(renderer, 0, VN_RENDERER_SYNC_BINARY, &sync);
   if (result != VK_SUCCESS)
      return result;

   const struct vn_renderer_submit submit = {
      .batches =
         &(const struct vn_renderer_submit_batch){
            .cs_data = cs_data,
            .cs_size = cs_size,
            .sync_queue_cpu = true,
            .syncs = &sync,
            .sync_values = &(const uint64_t){ 1 },
            .sync_count = 1,
         },
      .batch_count = 1,
   };
   const struct vn_renderer_wait wait = {
      .timeout = UINT64_MAX,
      .syncs = &sync,
      .sync_values = &(const uint64_t){ 1 },
      .sync_count = 1,
   };

   result = vn_renderer_submit(renderer, &submit);
   if (result == VK_SUCCESS)
      result = vn_renderer_wait(renderer, &wait);

   vn_renderer_sync_destroy(renderer, sync);

   return result;
}

#endif /* VN_RENDERER_H */
