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

/** \file
 * \ingroup gpu
 */

#pragma once

#include "gpu_texture_private.hh"

#include "vk_context.hh"
#include "vk_image_view.hh"
#include "vk_memory.hh"

namespace blender::gpu {

class VKSampler;
class VKDescriptorSetTracker;
class VKVertexBuffer;
class VKPixelBuffer;

/** Additional modifiers when requesting image views. */
enum class VKImageViewFlags {
  DEFAULT = 0,
  NO_SWIZZLING = 1 << 0,
};
ENUM_OPERATORS(VKImageViewFlags, VKImageViewFlags::NO_SWIZZLING)

class VKTexture : public Texture {
  friend class VKDescriptorSetUpdator;

  /**
   * Texture format how the texture is stored on the device.
   *
   * This can be a different format then #Texture.format_ in case the texture format isn't natively
   * supported by the device.
   */
  eGPUTextureFormat device_format_ = (eGPUTextureFormat)-1;

  /** When set the instance is considered to be a texture view from `source_texture_` */
  VKTexture *source_texture_ = nullptr;

  /**
   * Store of source vertex buffer. Related to `GPU_texture_create_from_vertbuf`.
   *
   * In vulkan a texel buffer is a buffer and not a texture. Calls will be forwarded to the vertex
   * buffer in this case. GPU_texture_create_from_vertbuf should be phased out (currently only used
   * by particle hair).
   */
  VKVertexBuffer *source_buffer_ = nullptr;
  VkImage vk_image_ = VK_NULL_HANDLE;
  VmaAllocation allocation_ = VK_NULL_HANDLE;
  VmaAllocationInfo allocation_info_ = {};

  /**
   * Image views are owned by VKTexture. When a specific image view is needed it will be created
   * and stored here. Image view can be requested by calling `image_view_get` method.
   */
  Vector<VKImageView> image_views_;

  int layer_offset_ = 0;
  bool use_stencil_ = false;

  char swizzle_[4] = {'r', 'g', 'b', 'a'};
  VKImageViewInfo image_view_info_ = {eImageViewUsage::ShaderBinding,
                                      IndexRange(0, VK_REMAINING_ARRAY_LAYERS),
                                      IndexRange(0, VK_REMAINING_MIP_LEVELS),
                                      {{'r', 'g', 'b', 'a'}},
                                      false,
                                      false,
                                      VKImageViewArrayed::DONT_CARE};

 public:
  VKTexture(const char *name) : Texture(name) {}

  virtual ~VKTexture() override;

  void generate_mipmap() override;
  void copy_to(Texture *tex) override;
  void copy_to(VKTexture &dst_texture, VkImageAspectFlags vk_image_aspect);
  void clear(eGPUDataFormat format, const void *data) override;
  void clear_depth_stencil(const eGPUFrameBufferBits buffer,
                           float clear_depth,
                           uint clear_stencil,
                           std::optional<int> layer);
  void swizzle_set(const char swizzle_mask[4]) override;
  void mip_range_set(int min, int max) override;
  void *read(int mip, eGPUDataFormat format) override;
  void read_sub(
      int mip, eGPUDataFormat format, const int region[6], IndexRange layers, void *r_data);
  void update_sub(int mip,
                  int offset[3],
                  int extent[3],
                  eGPUDataFormat format,
                  const void *data,
                  VKPixelBuffer *pixel_buffer);

  void update_sub(
      int mip, int offset[3], int extent[3], eGPUDataFormat format, const void *data) override;
  void update_sub(int offset[3],
                  int extent[3],
                  eGPUDataFormat format,
                  GPUPixelBuffer *pixbuf) override;

  /* TODO(fclem): Legacy. Should be removed at some point. */
  uint gl_bindcode_get() const override;
  /**
   * Export the memory associated with this texture to be imported by a different
   * API/Process/Instance.
   *
   * Returns the handle + offset of the image inside the handle.
   */
  VKMemoryExport export_memory(VkExternalMemoryHandleTypeFlagBits handle_type);

  VkImage vk_image_handle() const
  {
    if (is_texture_view()) {
      return source_texture_->vk_image_handle();
    }
    BLI_assert(vk_image_ != VK_NULL_HANDLE);
    return vk_image_;
  }

  /**
   * Get the texture format how the texture is stored on the device.
   */
  eGPUTextureFormat device_format_get() const
  {
    return device_format_;
  }

  /**
   * Get a specific image view for this texture. The specification of the image view are passed
   * inside the `info` parameter.
   */
  const VKImageView &image_view_get(const VKImageViewInfo &info);

  /**
   * Get the current image view for this texture.
   */
  const VKImageView &image_view_get(VKImageViewArrayed arrayed, VKImageViewFlags flags);

 protected:
  bool init_internal() override;
  bool init_internal(VertBuf *vbo) override;
  bool init_internal(GPUTexture *src, int mip_offset, int layer_offset, bool use_stencil) override;

 private:
  /** Is this texture a view of another texture. */
  bool is_texture_view() const;

  /**
   * Allocate the texture of the device. Result is `true` when texture is successfully allocated
   * on the device.
   */
  bool allocate();

  /**
   * Determine the layerCount for vulkan based on the texture type. Will pass the
   * #non_layered_value for non layered textures.
   */
  int vk_layer_count(int non_layered_value) const;

  /**
   * Determine the VkExtent3D for the given mip_level.
   */
  VkExtent3D vk_extent_3d(int mip_level) const;

  /* -------------------------------------------------------------------- */
  /** \name Image Views
   * \{ */

 private:
  IndexRange mip_map_range() const;
  IndexRange layer_range() const;

  /** \} */
};

BLI_INLINE VKTexture *unwrap(Texture *tex)
{
  return static_cast<VKTexture *>(tex);
}

BLI_INLINE Texture *wrap(VKTexture *texture)
{
  return static_cast<Texture *>(texture);
}

}  // namespace blender::gpu
