/* SPDX-FileCopyrightText: 2023 Blender Authors
 *
 * SPDX-License-Identifier: GPL-2.0-or-later */

/** \file
 * \ingroup gpu
 */

#pragma once

#include "gpu_context_private.hh"

#include "BLI_utility_mixins.hh"
#include "vk_common.hh"

namespace blender::gpu {
class VKContext;
class VKDevice;

/**
 * Class for handing vulkan buffers (allocation/updating/binding).
 */
class VKBuffer : public NonCopyable {
  size_t size_in_bytes_ = 0;
  size_t alloc_size_in_bytes_ = 0;
  VkBuffer vk_buffer_ = VK_NULL_HANDLE;
  VmaAllocation allocation_ = VK_NULL_HANDLE;
  VkMemoryPropertyFlags vk_memory_property_flags_;
  TimelineValue async_timeline_ = 0;
  /** Has a previous allocation failed. Will skip reallocations. */
  bool allocation_failed_ = false;

  /* Pointer to the virtually mapped memory. */
  void *mapped_memory_ = nullptr;

  VkDeviceAddress vk_device_address = 0;

 public:
  VKBuffer() = default;
  virtual ~VKBuffer();

  /** Has this buffer been allocated? */
  bool is_allocated() const;

  /**
   * Allocate the buffer.
   */
  bool create(size_t size,
              VkBufferUsageFlags buffer_usage,
              VkMemoryPropertyFlags required_flags,
              VkMemoryPropertyFlags preferred_flags,
              VmaAllocationCreateFlags vma_allocation_flags,
              float priority,
              bool export_memory = false);
  void clear(VKContext &context, uint32_t clear_value);
  void update_immediately(const void *data) const;
  void update_sub_immediately(size_t start_offset, size_t data_size, const void *data) const;

  /**
   * Update the buffer as part of the render graph evaluation. The ownership of data will be
   * transferred to the render graph and should have been allocated using guarded alloc.
   */
  void update_render_graph(VKContext &context, void *data) const;
  void flush() const;

  /**
   * Read the buffer (synchronously).
   */
  void read(VKContext &context, void *data) const;

  /**
   * Start a async read-back.
   */
  void async_flush_to_host(VKContext &context);

  /**
   * Wait until the async read back is finished and fill the given data with the content of the
   * buffer.
   *
   * Will start a new async read-back when there is no read back in progress.
   */
  void read_async(VKContext &context, void *data);

  /**
   * Free the buffer.
   *
   * Discards the buffer so it can be destroyed safely later. Buffers can still be used when
   * rendering so we can only destroy them after the rendering is completed.
   */
  bool free();

  /**
   * Destroy the buffer immediately.
   */
  void free_immediately(VKDevice &device);

  int64_t size_in_bytes() const
  {
    return size_in_bytes_;
  }

  VkBuffer vk_handle() const
  {
    return vk_buffer_;
  }

  /**
   * Get the reference to the mapped memory.
   *
   * Can only be called when the buffer is (still) mapped.
   */
  void *mapped_memory_get() const;

  VkDeviceAddress device_address_get() const
  {
    return vk_device_address;
  }

  /**
   * Is this buffer mapped (visible on host)
   */
  bool is_mapped() const;

  /**
   * Get allocated device memory.
   */
  VkDeviceMemory export_memory_get(size_t &memory_size);

 private:
  /** Check if this buffer is mapped. */
  bool map();
  void unmap();
};

inline void *VKBuffer::mapped_memory_get() const
{
  BLI_assert_msg(this->is_mapped(), "Cannot access a non-mapped buffer.");
  return mapped_memory_;
}

inline bool VKBuffer::is_mapped() const
{
  return mapped_memory_ != nullptr;
}

inline bool VKBuffer::is_allocated() const
{
  return allocation_ != VK_NULL_HANDLE;
}

/**
 * Helper struct to enable buffers to be bound with an offset.
 *
 * Used for de-interleaved vertex input buffers and immediate mode buffers.
 */
struct VKBufferWithOffset {
  const VkBuffer buffer;
  VkDeviceSize offset;
};

}  // namespace blender::gpu
