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

/** \file
 * \ingroup gpu
 */

#include "BLI_string.h"

#include "GPU_framebuffer.hh"
#include "GPU_texture.hh"

#include "gpu_backend.hh"
#include "gpu_context_private.hh"
#include "gpu_framebuffer_private.hh"

#include "gpu_texture_private.hh"

namespace blender::gpu {

/* -------------------------------------------------------------------- */
/** \name Creation & Deletion
 * \{ */

Texture::Texture(const char *name)
{
  if (name) {
    STRNCPY(name_, name);
  }
  else {
    name_[0] = '\0';
  }

  for (int i = 0; i < ARRAY_SIZE(fb_); i++) {
    fb_[i] = nullptr;
  }

  gpu_image_usage_flags_ = GPU_TEXTURE_USAGE_GENERAL;
}

Texture::~Texture()
{
  for (int i = 0; i < ARRAY_SIZE(fb_); i++) {
    if (fb_[i] != nullptr) {
      fb_[i]->attachment_remove(fb_attachment_[i]);
    }
  }

#ifndef GPU_NO_USE_PY_REFERENCES
  if (this->py_ref) {
    *this->py_ref = nullptr;
  }
#endif
}

bool Texture::init_1D(int w, int layers, int mip_len, eGPUTextureFormat format)
{
  w_ = w;
  h_ = layers;
  d_ = 0;
  int mip_len_max = 1 + floorf(log2f(w));
  mipmaps_ = min_ii(mip_len, mip_len_max);
  format_ = format;
  format_flag_ = to_format_flag(format);
  type_ = (layers > 0) ? GPU_TEXTURE_1D_ARRAY : GPU_TEXTURE_1D;
  if ((format_flag_ & (GPU_FORMAT_DEPTH_STENCIL | GPU_FORMAT_INTEGER)) == 0) {
    sampler_state.filtering = GPU_SAMPLER_FILTERING_LINEAR;
  }
  return this->init_internal();
}

bool Texture::init_2D(int w, int h, int layers, int mip_len, eGPUTextureFormat format)
{
  w_ = w;
  h_ = h;
  d_ = layers;
  int mip_len_max = 1 + floorf(log2f(max_ii(w, h)));
  mipmaps_ = min_ii(mip_len, mip_len_max);
  format_ = format;
  format_flag_ = to_format_flag(format);
  type_ = (layers > 0) ? GPU_TEXTURE_2D_ARRAY : GPU_TEXTURE_2D;
  if ((format_flag_ & (GPU_FORMAT_DEPTH_STENCIL | GPU_FORMAT_INTEGER)) == 0) {
    sampler_state.filtering = GPU_SAMPLER_FILTERING_LINEAR;
  }
  return this->init_internal();
}

bool Texture::init_3D(int w, int h, int d, int mip_len, eGPUTextureFormat format)
{
  w_ = w;
  h_ = h;
  d_ = d;
  int mip_len_max = 1 + floorf(log2f(max_iii(w, h, d)));
  mipmaps_ = min_ii(mip_len, mip_len_max);
  format_ = format;
  format_flag_ = to_format_flag(format);
  type_ = GPU_TEXTURE_3D;
  if ((format_flag_ & (GPU_FORMAT_DEPTH_STENCIL | GPU_FORMAT_INTEGER)) == 0) {
    sampler_state.filtering = GPU_SAMPLER_FILTERING_LINEAR;
  }
  return this->init_internal();
}

bool Texture::init_cubemap(int w, int layers, int mip_len, eGPUTextureFormat format)
{
  w_ = w;
  h_ = w;
  d_ = max_ii(1, layers) * 6;
  int mip_len_max = 1 + floorf(log2f(w));
  mipmaps_ = min_ii(mip_len, mip_len_max);
  format_ = format;
  format_flag_ = to_format_flag(format);
  type_ = (layers > 0) ? GPU_TEXTURE_CUBE_ARRAY : GPU_TEXTURE_CUBE;
  if ((format_flag_ & (GPU_FORMAT_DEPTH_STENCIL | GPU_FORMAT_INTEGER)) == 0) {
    sampler_state.filtering = GPU_SAMPLER_FILTERING_LINEAR;
  }
  return this->init_internal();
}

bool Texture::init_buffer(VertBuf *vbo, eGPUTextureFormat format)
{
  /* See to_texture_format(). */
  if (format == GPU_DEPTH_COMPONENT24) {
    return false;
  }
  w_ = GPU_vertbuf_get_vertex_len(vbo);
  h_ = 0;
  d_ = 0;
  format_ = format;
  format_flag_ = to_format_flag(format);
  type_ = GPU_TEXTURE_BUFFER;
  return this->init_internal(vbo);
}

bool Texture::init_view(GPUTexture *src_,
                        eGPUTextureFormat format,
                        eGPUTextureType type,
                        int mip_start,
                        int mip_len,
                        int layer_start,
                        int layer_len,
                        bool cube_as_array,
                        bool use_stencil)
{
  const Texture *src = unwrap(src_);
  w_ = src->w_;
  h_ = src->h_;
  d_ = src->d_;
  layer_start = min_ii(layer_start, src->layer_count() - 1);
  layer_len = min_ii(layer_len, (src->layer_count() - layer_start));
  switch (type) {
    case GPU_TEXTURE_1D_ARRAY:
      h_ = layer_len;
      break;
    case GPU_TEXTURE_CUBE_ARRAY:
      BLI_assert(layer_len % 6 == 0);
      ATTR_FALLTHROUGH;
    case GPU_TEXTURE_2D_ARRAY:
      d_ = layer_len;
      break;
    default:
      BLI_assert(layer_len == 1 && layer_start == 0);
      break;
  }
  mip_start = min_ii(mip_start, src->mipmaps_ - 1);
  mip_len = min_ii(mip_len, (src->mipmaps_ - mip_start));
  mipmaps_ = mip_len;
  format_ = format;
  format_flag_ = to_format_flag(format);
  type_ = type;
  if (cube_as_array) {
    BLI_assert(type_ & GPU_TEXTURE_CUBE);
    type_ = (type_ & ~GPU_TEXTURE_CUBE) | GPU_TEXTURE_2D_ARRAY;
  }
  sampler_state = src->sampler_state;
  return this->init_internal(src_, mip_start, layer_start, use_stencil);
}

void Texture::usage_set(eGPUTextureUsage usage_flags)
{
  gpu_image_usage_flags_ = usage_flags;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name Operation
 * \{ */

void Texture::attach_to(FrameBuffer *fb, GPUAttachmentType type)
{
  for (int i = 0; i < ARRAY_SIZE(fb_); i++) {
    if (fb_[i] == fb) {
      /* Already stores a reference */
      if (fb_attachment_[i] != type) {
        /* Ensure it's not attached twice to the same FrameBuffer. */
        fb_[i]->attachment_remove(fb_attachment_[i]);
        fb_attachment_[i] = type;
      }
      return;
    }
  }
  for (int i = 0; i < ARRAY_SIZE(fb_); i++) {
    if (fb_[i] == nullptr) {
      fb_attachment_[i] = type;
      fb_[i] = fb;
      return;
    }
  }
  BLI_assert_msg(0, "GPU: Error: Texture: Not enough attachment");
}

void Texture::detach_from(FrameBuffer *fb)
{
  for (int i = 0; i < ARRAY_SIZE(fb_); i++) {
    if (fb_[i] == fb) {
      fb_[i]->attachment_remove(fb_attachment_[i]);
      fb_[i] = nullptr;
      return;
    }
  }
  BLI_assert_msg(0, "GPU: Error: Texture: Framebuffer is not attached");
}

void Texture::update(eGPUDataFormat format, const void *data)
{
  int mip = 0;
  int extent[3] = {1, 1, 1};
  int offset[3] = {0, 0, 0};
  this->mip_size_get(mip, extent);
  this->update_sub(mip, offset, extent, format, data);
}

/** \} */

}  // namespace blender::gpu

/* -------------------------------------------------------------------- */
/** \name C-API
 * \{ */

using namespace blender;
using namespace blender::gpu;

/* ------ Memory Management ------ */

uint GPU_texture_memory_usage_get()
{
  /* TODO(fclem): Do that inside the new Texture class. */
  return 0;
}

/* ------ Creation ------ */

static inline GPUTexture *gpu_texture_create(const char *name,
                                             const int w,
                                             const int h,
                                             const int d,
                                             const eGPUTextureType type,
                                             int mip_len,
                                             eGPUTextureFormat tex_format,
                                             eGPUTextureUsage usage,
                                             const void *pixels,
                                             eGPUDataFormat data_format = GPU_DATA_FLOAT)
{
  BLI_assert(mip_len > 0);
  Texture *tex = GPUBackend::get()->texture_alloc(name);
  tex->usage_set(usage);

  bool success = false;
  switch (type) {
    case GPU_TEXTURE_1D:
    case GPU_TEXTURE_1D_ARRAY:
      success = tex->init_1D(w, h, mip_len, tex_format);
      break;
    case GPU_TEXTURE_2D:
    case GPU_TEXTURE_2D_ARRAY:
      success = tex->init_2D(w, h, d, mip_len, tex_format);
      break;
    case GPU_TEXTURE_3D:
      success = tex->init_3D(w, h, d, mip_len, tex_format);
      break;
    case GPU_TEXTURE_CUBE:
    case GPU_TEXTURE_CUBE_ARRAY:
      success = tex->init_cubemap(w, d, mip_len, tex_format);
      break;
    default:
      break;
  }

  if (!success) {
    delete tex;
    return nullptr;
  }
  if (pixels) {
    tex->update(data_format, pixels);
  }
  return reinterpret_cast<GPUTexture *>(tex);
}

GPUTexture *GPU_texture_create_1d(const char *name,
                                  int width,
                                  int mip_len,
                                  eGPUTextureFormat format,
                                  eGPUTextureUsage usage,
                                  const float *data)
{
  return gpu_texture_create(name, width, 0, 0, GPU_TEXTURE_1D, mip_len, format, usage, data);
}

GPUTexture *GPU_texture_create_1d_array(const char *name,
                                        int width,
                                        int layer_len,
                                        int mip_len,
                                        eGPUTextureFormat format,
                                        eGPUTextureUsage usage,
                                        const float *data)
{
  return gpu_texture_create(
      name, width, layer_len, 0, GPU_TEXTURE_1D_ARRAY, mip_len, format, usage, data);
}

GPUTexture *GPU_texture_create_2d(const char *name,
                                  int width,
                                  int height,
                                  int mip_len,
                                  eGPUTextureFormat format,
                                  eGPUTextureUsage usage,
                                  const float *data)
{
  return gpu_texture_create(name, width, height, 0, GPU_TEXTURE_2D, mip_len, format, usage, data);
}

GPUTexture *GPU_texture_create_2d_array(const char *name,
                                        int width,
                                        int height,
                                        int layer_len,
                                        int mip_len,
                                        eGPUTextureFormat format,
                                        eGPUTextureUsage usage,
                                        const float *data)
{
  return gpu_texture_create(
      name, width, height, layer_len, GPU_TEXTURE_2D_ARRAY, mip_len, format, usage, data);
}

GPUTexture *GPU_texture_create_3d(const char *name,
                                  int width,
                                  int height,
                                  int depth,
                                  int mip_len,
                                  eGPUTextureFormat texture_format,
                                  eGPUTextureUsage usage,
                                  const void *data)
{
  return gpu_texture_create(
      name, width, height, depth, GPU_TEXTURE_3D, mip_len, texture_format, usage, data);
}

GPUTexture *GPU_texture_create_cube(const char *name,
                                    int width,
                                    int mip_len,
                                    eGPUTextureFormat format,
                                    eGPUTextureUsage usage,
                                    const float *data)
{
  return gpu_texture_create(name, width, width, 0, GPU_TEXTURE_CUBE, mip_len, format, usage, data);
}

GPUTexture *GPU_texture_create_cube_array(const char *name,
                                          int width,
                                          int layer_len,
                                          int mip_len,
                                          eGPUTextureFormat format,
                                          eGPUTextureUsage usage,
                                          const float *data)
{
  return gpu_texture_create(
      name, width, width, layer_len, GPU_TEXTURE_CUBE_ARRAY, mip_len, format, usage, data);
}

GPUTexture *GPU_texture_create_compressed_2d(const char *name,
                                             int width,
                                             int height,
                                             int mip_len,
                                             eGPUTextureFormat tex_format,
                                             eGPUTextureUsage usage,
                                             const void *data)
{
  Texture *tex = GPUBackend::get()->texture_alloc(name);
  tex->usage_set(usage);
  bool success = tex->init_2D(width, height, 0, mip_len, tex_format);

  if (!success) {
    delete tex;
    return nullptr;
  }
  if (data) {
    size_t ofs = 0;
    for (int mip = 0; mip < mip_len; mip++) {
      int extent[3], offset[3] = {0, 0, 0};
      tex->mip_size_get(mip, extent);

      size_t size = ((extent[0] + 3) / 4) * ((extent[1] + 3) / 4) * to_block_size(tex_format);
      tex->update_sub(mip, offset, extent, to_data_format(tex_format), (uchar *)data + ofs);

      ofs += size;
    }
  }
  return reinterpret_cast<GPUTexture *>(tex);
}

GPUTexture *GPU_texture_create_from_vertbuf(const char *name, blender::gpu::VertBuf *vert)
{
#ifndef NDEBUG
  /* Vertex buffers used for texture buffers must be flagged with:
   * GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY. */
  BLI_assert_msg(vert->extended_usage_ & GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY,
                 "Vertex Buffers used for textures should have usage flag "
                 "GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY.");
#endif
  eGPUTextureFormat tex_format = to_texture_format(GPU_vertbuf_get_format(vert));
  Texture *tex = GPUBackend::get()->texture_alloc(name);

  bool success = tex->init_buffer(vert, tex_format);
  if (!success) {
    delete tex;
    return nullptr;
  }
  return reinterpret_cast<GPUTexture *>(tex);
}

GPUTexture *GPU_texture_create_error(int dimension, bool is_array)
{
  const float pixel[4] = {1.0f, 0.0f, 1.0f, 1.0f};
  int w = 1;
  int h = (dimension < 2 && !is_array) ? 0 : 1;
  int d = (dimension < 3 && !is_array) ? 0 : 1;

  eGPUTextureType type = GPU_TEXTURE_3D;
  type = (dimension == 2) ? (is_array ? GPU_TEXTURE_2D_ARRAY : GPU_TEXTURE_2D) : type;
  type = (dimension == 1) ? (is_array ? GPU_TEXTURE_1D_ARRAY : GPU_TEXTURE_1D) : type;

  return gpu_texture_create(
      "invalid_tex", w, h, d, type, 1, GPU_RGBA8, GPU_TEXTURE_USAGE_GENERAL, pixel);
}

GPUTexture *GPU_texture_create_view(const char *name,
                                    GPUTexture *source_texture,
                                    eGPUTextureFormat view_format,
                                    int mip_start,
                                    int mip_len,
                                    int layer_start,
                                    int layer_len,
                                    bool cube_as_array,
                                    bool use_stencil)
{
  BLI_assert(mip_len > 0);
  BLI_assert(layer_len > 0);
  BLI_assert_msg(use_stencil == false ||
                     (GPU_texture_usage(source_texture) & GPU_TEXTURE_USAGE_FORMAT_VIEW),
                 "Source texture of TextureView must have GPU_TEXTURE_USAGE_FORMAT_VIEW usage "
                 "flag if view texture uses stencil texturing.");
  BLI_assert_msg((view_format == GPU_texture_format(source_texture)) ||
                     (GPU_texture_usage(source_texture) & GPU_TEXTURE_USAGE_FORMAT_VIEW),
                 "Source texture of TextureView must have GPU_TEXTURE_USAGE_FORMAT_VIEW usage "
                 "flag if view texture format is different.");
  Texture *view = GPUBackend::get()->texture_alloc(name);
  view->init_view(source_texture,
                  view_format,
                  unwrap(source_texture)->type_get(),
                  mip_start,
                  mip_len,
                  layer_start,
                  layer_len,
                  cube_as_array,
                  use_stencil);
  return wrap(view);
}

/* ------ Usage ------ */
eGPUTextureUsage GPU_texture_usage(const GPUTexture *texture_)
{
  const Texture *tex = reinterpret_cast<const Texture *>(texture_);
  return tex->usage_get();
}

/* ------ Update ------ */

void GPU_texture_update_mipmap(GPUTexture *texture,
                               int mip_level,
                               eGPUDataFormat data_format,
                               const void *pixels)
{
  int extent[3] = {1, 1, 1}, offset[3] = {0, 0, 0};
  unwrap(texture)->mip_size_get(mip_level, extent);
  unwrap(texture)->update_sub(mip_level, offset, extent, data_format, pixels);
}

void GPU_texture_update_sub(GPUTexture *tex,
                            eGPUDataFormat data_format,
                            const void *pixels,
                            int offset_x,
                            int offset_y,
                            int offset_z,
                            int width,
                            int height,
                            int depth)
{
  int offset[3] = {offset_x, offset_y, offset_z};
  int extent[3] = {width, height, depth};
  unwrap(tex)->update_sub(0, offset, extent, data_format, pixels);
}

void GPU_texture_update_sub_from_pixel_buffer(GPUTexture *texture,
                                              eGPUDataFormat data_format,
                                              GPUPixelBuffer *pixel_buf,
                                              int offset_x,
                                              int offset_y,
                                              int offset_z,
                                              int width,
                                              int height,
                                              int depth)
{
  int offset[3] = {offset_x, offset_y, offset_z};
  int extent[3] = {width, height, depth};
  unwrap(texture)->update_sub(offset, extent, data_format, pixel_buf);
}

void *GPU_texture_read(GPUTexture *texture, eGPUDataFormat data_format, int mip_level)
{
  BLI_assert_msg(
      GPU_texture_usage(texture) & GPU_TEXTURE_USAGE_HOST_READ,
      "The host-read usage flag must be specified up-front. Only textures which require data "
      "reads should be flagged, allowing the backend to make certain optimizations.");
  return unwrap(texture)->read(mip_level, data_format);
}

void GPU_texture_clear(GPUTexture *tex, eGPUDataFormat data_format, const void *data)
{
  BLI_assert(data != nullptr); /* Do not accept nullptr as parameter. */
  unwrap(tex)->clear(data_format, data);
}

void GPU_texture_update(GPUTexture *tex, eGPUDataFormat data_format, const void *data)
{
  unwrap(tex)->update(data_format, data);
}

void GPU_unpack_row_length_set(uint len)
{
  Context::get()->state_manager->texture_unpack_row_length_set(len);
}

/* ------ Binding ------ */

void GPU_texture_bind_ex(GPUTexture *texture, GPUSamplerState state, int unit)
{
  Texture *tex = unwrap(texture);
  state = (state.type == GPU_SAMPLER_STATE_TYPE_INTERNAL) ? tex->sampler_state : state;
  Context::get()->state_manager->texture_bind(tex, state, unit);
}

void GPU_texture_bind(GPUTexture *texture, int unit)
{
  Texture *tex = unwrap(texture);
  Context::get()->state_manager->texture_bind(tex, tex->sampler_state, unit);
}

void GPU_texture_unbind(GPUTexture *texture)
{
  Texture *tex = unwrap(texture);
  Context::get()->state_manager->texture_unbind(tex);
}

void GPU_texture_unbind_all()
{
  Context::get()->state_manager->texture_unbind_all();
}

void GPU_texture_image_bind(GPUTexture *tex, int unit)
{
  Context::get()->state_manager->image_bind(unwrap(tex), unit);
}

void GPU_texture_image_unbind(GPUTexture *tex)
{
  Context::get()->state_manager->image_unbind(unwrap(tex));
}

void GPU_texture_image_unbind_all()
{
  Context::get()->state_manager->image_unbind_all();
}

void GPU_texture_update_mipmap_chain(GPUTexture *tex)
{
  unwrap(tex)->generate_mipmap();
}

void GPU_texture_copy(GPUTexture *dst_, GPUTexture *src_)
{
  Texture *src = unwrap(src_);
  Texture *dst = unwrap(dst_);
  src->copy_to(dst);
}

void GPU_texture_compare_mode(GPUTexture *texture, bool use_compare)
{
  Texture *tex = unwrap(texture);
  /* Only depth formats does support compare mode. */
  BLI_assert(!(use_compare) || (tex->format_flag_get() & GPU_FORMAT_DEPTH));

  tex->sampler_state.type = use_compare ? GPU_SAMPLER_STATE_TYPE_CUSTOM :
                                          GPU_SAMPLER_STATE_TYPE_PARAMETERS;
  tex->sampler_state.custom_type = GPU_SAMPLER_CUSTOM_COMPARE;
}

void GPU_texture_filter_mode(GPUTexture *texture, bool use_filter)
{
  Texture *tex = unwrap(texture);
  /* Stencil and integer format does not support filtering. */
  BLI_assert(!(use_filter) ||
             !(tex->format_flag_get() & (GPU_FORMAT_STENCIL | GPU_FORMAT_INTEGER)));
  tex->sampler_state.set_filtering_flag_from_test(GPU_SAMPLER_FILTERING_LINEAR, use_filter);
}

void GPU_texture_mipmap_mode(GPUTexture *texture, bool use_mipmap, bool use_filter)
{
  Texture *tex = unwrap(texture);
  /* Stencil and integer format does not support filtering. */
  BLI_assert(!(use_filter || use_mipmap) ||
             !(tex->format_flag_get() & (GPU_FORMAT_STENCIL | GPU_FORMAT_INTEGER)));
  tex->sampler_state.set_filtering_flag_from_test(GPU_SAMPLER_FILTERING_MIPMAP, use_mipmap);
  tex->sampler_state.set_filtering_flag_from_test(GPU_SAMPLER_FILTERING_LINEAR, use_filter);
}

void GPU_texture_anisotropic_filter(GPUTexture *texture, bool use_aniso)
{
  Texture *tex = unwrap(texture);
  /* Stencil and integer format does not support filtering. */
  BLI_assert(!(use_aniso) ||
             !(tex->format_flag_get() & (GPU_FORMAT_STENCIL | GPU_FORMAT_INTEGER)));
  tex->sampler_state.set_filtering_flag_from_test(GPU_SAMPLER_FILTERING_ANISOTROPIC, use_aniso);
}

void GPU_texture_extend_mode_x(GPUTexture *texture, GPUSamplerExtendMode extend_mode)
{
  unwrap(texture)->sampler_state.extend_x = extend_mode;
}

void GPU_texture_extend_mode_y(GPUTexture *texture, GPUSamplerExtendMode extend_mode)
{
  unwrap(texture)->sampler_state.extend_yz = extend_mode;
}

void GPU_texture_extend_mode(GPUTexture *texture, GPUSamplerExtendMode extend_mode)
{
  unwrap(texture)->sampler_state.extend_x = extend_mode;
  unwrap(texture)->sampler_state.extend_yz = extend_mode;
}

void GPU_texture_swizzle_set(GPUTexture *texture, const char swizzle[4])
{
  unwrap(texture)->swizzle_set(swizzle);
}

void GPU_texture_free(GPUTexture *texture)
{
  Texture *tex = unwrap(texture);
  tex->refcount--;

  if (tex->refcount < 0) {
    fprintf(stderr, "GPUTexture: negative refcount\n");
  }

  if (tex->refcount == 0) {
    delete tex;
  }
}

void GPU_texture_ref(GPUTexture *texture)
{
  unwrap(texture)->refcount++;
}

int GPU_texture_dimensions(const GPUTexture *texture)
{
  eGPUTextureType type = unwrap(texture)->type_get();
  if (type & GPU_TEXTURE_1D) {
    return 1;
  }
  if (type & GPU_TEXTURE_2D) {
    return 2;
  }
  if (type & GPU_TEXTURE_3D) {
    return 3;
  }
  if (type & GPU_TEXTURE_CUBE) {
    return 2;
  }
  /* GPU_TEXTURE_BUFFER */
  return 1;
}

int GPU_texture_width(const GPUTexture *texture)
{
  return unwrap(texture)->width_get();
}

int GPU_texture_height(const GPUTexture *texture)
{
  return unwrap(texture)->height_get();
}

int GPU_texture_depth(const GPUTexture *texture)
{
  return unwrap(texture)->depth_get();
}

int GPU_texture_layer_count(const GPUTexture *texture)
{
  return unwrap(texture)->layer_count();
}

int GPU_texture_mip_count(const GPUTexture *texture)
{
  return unwrap(texture)->mip_count();
}

int GPU_texture_original_width(const GPUTexture *texture)
{
  return unwrap(texture)->src_w;
}

int GPU_texture_original_height(const GPUTexture *texture)
{
  return unwrap(texture)->src_h;
}

void GPU_texture_original_size_set(GPUTexture *texture, int w, int h)
{
  unwrap(texture)->src_w = w;
  unwrap(texture)->src_h = h;
}

eGPUTextureFormat GPU_texture_format(const GPUTexture *texture)
{
  return unwrap(texture)->format_get();
}

const char *GPU_texture_format_name(eGPUTextureFormat texture_format)
{
  switch (texture_format) {
    /* Formats texture & render-buffer */
    case GPU_RGBA8UI:
      return "RGBA8UI";
    case GPU_RGBA8I:
      return "RGBA8I";
    case GPU_RGBA8:
      return "RGBA8";
    case GPU_RGBA32UI:
      return "RGBA32UI";
    case GPU_RGBA32I:
      return "RGBA32I";
    case GPU_RGBA32F:
      return "RGBA32F";
    case GPU_RGBA16UI:
      return "RGBA16UI";
    case GPU_RGBA16I:
      return "RGBA16I";
    case GPU_RGBA16F:
      return "RGBA16F";
    case GPU_RGBA16:
      return "RGBA16";
    case GPU_RG8UI:
      return "RG8UI";
    case GPU_RG8I:
      return "RG8I";
    case GPU_RG8:
      return "RG8";
    case GPU_RG32UI:
      return "RG32UI";
    case GPU_RG32I:
      return "RG32I";
    case GPU_RG32F:
      return "RG32F";
    case GPU_RG16UI:
      return "RG16UI";
    case GPU_RG16I:
      return "RG16I";
    case GPU_RG16F:
      return "RG16F";
    case GPU_RG16:
      return "RG16";
    case GPU_R8UI:
      return "R8UI";
    case GPU_R8I:
      return "R8I";
    case GPU_R8:
      return "R8";
    case GPU_R32UI:
      return "R32UI";
    case GPU_R32I:
      return "R32I";
    case GPU_R32F:
      return "R32F";
    case GPU_R16UI:
      return "R16UI";
    case GPU_R16I:
      return "R16I";
    case GPU_R16F:
      return "R16F";
    case GPU_R16:
      return "R16";
    /* Special formats texture & render-buffer */
    case GPU_RGB10_A2:
      return "RGB10_A2";
    case GPU_RGB10_A2UI:
      return "RGB10_A2UI";
    case GPU_R11F_G11F_B10F:
      return "R11F_G11F_B10F";
    case GPU_DEPTH32F_STENCIL8:
      return "DEPTH32F_STENCIL8";
    case GPU_DEPTH24_STENCIL8:
      return "DEPTH24_STENCIL8";
    case GPU_SRGB8_A8:
      return "SRGB8_A8";
    /* Texture only formats. */
    case GPU_RGB16F:
      return "RGB16F";
    case GPU_RGB16_SNORM:
      return "RGB16_SNORM";
    case GPU_RGB16I:
      return "RGB16I";
    case GPU_RGB16UI:
      return "RGB16UI";
    case GPU_RGB16:
      return "RGB16";
    case GPU_RGBA16_SNORM:
      return "RGBA16_SNORM";
    case GPU_RGBA8_SNORM:
      return "RGBA8_SNORM";
    case GPU_RGB32F:
      return "RGB32F";
    case GPU_RGB32I:
      return "RGB32I";
    case GPU_RGB32UI:
      return "RGB32UI";
    case GPU_RGB8_SNORM:
      return "RGB8_SNORM";
    case GPU_RGB8:
      return "RGB8";
    case GPU_RGB8I:
      return "RGB8I";
    case GPU_RGB8UI:
      return "RGB8UI";
    case GPU_RG16_SNORM:
      return "RG16_SNORM";
    case GPU_RG8_SNORM:
      return "RG8_SNORM";
    case GPU_R16_SNORM:
      return "R16_SNORM";
    case GPU_R8_SNORM:
      return "R8_SNORM";
    /* Special formats, texture only. */
    case GPU_SRGB8_A8_DXT1:
      return "SRGB8_A8_DXT1";
    case GPU_SRGB8_A8_DXT3:
      return "SRGB8_A8_DXT3";
    case GPU_SRGB8_A8_DXT5:
      return "SRGB8_A8_DXT5";
    case GPU_RGBA8_DXT1:
      return "RGBA8_DXT1";
    case GPU_RGBA8_DXT3:
      return "RGBA8_DXT3";
    case GPU_RGBA8_DXT5:
      return "RGBA8_DXT5";
    case GPU_SRGB8:
      return "SRGB8";
    case GPU_RGB9_E5:
      return "RGB9_E5";
    /* Depth Formats. */
    case GPU_DEPTH_COMPONENT32F:
      return "DEPTH_COMPONENT32F";
    case GPU_DEPTH_COMPONENT24:
      return "DEPTH_COMPONENT24";
    case GPU_DEPTH_COMPONENT16:
      return "DEPTH_COMPONENT16";
  }
  BLI_assert_unreachable();
  return "";
}

bool GPU_texture_has_depth_format(const GPUTexture *texture)
{
  return (unwrap(texture)->format_flag_get() & GPU_FORMAT_DEPTH) != 0;
}

bool GPU_texture_has_stencil_format(const GPUTexture *texture)
{
  return (unwrap(texture)->format_flag_get() & GPU_FORMAT_STENCIL) != 0;
}

bool GPU_texture_has_integer_format(const GPUTexture *texture)
{
  return (unwrap(texture)->format_flag_get() & GPU_FORMAT_INTEGER) != 0;
}

bool GPU_texture_has_float_format(const GPUTexture *texture)
{
  return (unwrap(texture)->format_flag_get() & GPU_FORMAT_FLOAT) != 0;
}

bool GPU_texture_has_normalized_format(const GPUTexture *texture)
{
  return (unwrap(texture)->format_flag_get() & GPU_FORMAT_NORMALIZED_INTEGER) != 0;
}

bool GPU_texture_has_signed_format(const GPUTexture *texture)
{
  return (unwrap(texture)->format_flag_get() & GPU_FORMAT_SIGNED) != 0;
}

bool GPU_texture_is_cube(const GPUTexture *texture)
{
  return (unwrap(texture)->type_get() & GPU_TEXTURE_CUBE) != 0;
}

bool GPU_texture_is_array(const GPUTexture *texture)
{
  return (unwrap(texture)->type_get() & GPU_TEXTURE_ARRAY) != 0;
}

#ifndef GPU_NO_USE_PY_REFERENCES
void **GPU_texture_py_reference_get(GPUTexture *texture)
{
  return unwrap(texture)->py_ref;
}

void GPU_texture_py_reference_set(GPUTexture *texture, void **py_ref)
{
  BLI_assert(py_ref == nullptr || unwrap(texture)->py_ref == nullptr);
  unwrap(texture)->py_ref = py_ref;
}
#endif

/* TODO: remove. */
int GPU_texture_opengl_bindcode(const GPUTexture *texture)
{
  return unwrap(texture)->gl_bindcode_get();
}

void GPU_texture_get_mipmap_size(GPUTexture *texture, int mip_level, int *r_size)
{
  unwrap(texture)->mip_size_get(mip_level, r_size);
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name GPU Pixel Buffer
 *
 * Pixel buffer utility functions.
 * \{ */

GPUPixelBuffer *GPU_pixel_buffer_create(size_t size)
{
  /* Ensure buffer satisfies the alignment of 256 bytes for copying
   * data between buffers and textures. As specified in:
   * https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
   *
   * Ensuring minimal size across all platforms handles cases for small-sized
   * textures and avoids issues with zero-sized buffers. */
  size = ceil_to_multiple_ul(size, 256);
  PixelBuffer *pixbuf = GPUBackend::get()->pixelbuf_alloc(size);
  return wrap(pixbuf);
}

void GPU_pixel_buffer_free(GPUPixelBuffer *pixel_buf)
{
  PixelBuffer *handle = unwrap(pixel_buf);
  delete handle;
}

void *GPU_pixel_buffer_map(GPUPixelBuffer *pixel_buf)
{
  return unwrap(pixel_buf)->map();
}

void GPU_pixel_buffer_unmap(GPUPixelBuffer *pixel_buf)
{
  unwrap(pixel_buf)->unmap();
}

size_t GPU_pixel_buffer_size(GPUPixelBuffer *pixel_buf)
{
  return unwrap(pixel_buf)->get_size();
}

GPUPixelBufferNativeHandle GPU_pixel_buffer_get_native_handle(GPUPixelBuffer *pixel_buf)
{
  return unwrap(pixel_buf)->get_native_handle();
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name GPU Sampler Objects
 *
 * Simple wrapper around opengl sampler objects.
 * Override texture sampler state for one sampler unit only.
 * \{ */

void GPU_samplers_update()
{
  /* Backend may not exist when we are updating preferences from background mode. */
  GPUBackend *backend = GPUBackend::get();
  if (backend) {
    backend->samplers_update();
  }
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name GPU texture utilities
 * \{ */

size_t GPU_texture_component_len(eGPUTextureFormat tex_format)
{
  return to_component_len(tex_format);
}

size_t GPU_texture_dataformat_size(eGPUDataFormat data_format)
{
  return to_bytesize(data_format);
}

/** \} */
