text: // Copyright (c) 2021 Tobias Bohnen
  //
  // This software is released under the MIT License.
  // https://opensource.org/licenses/MIT
  
  #include "GLTexture.hpp"
  
  #include <cassert>
  
  #include <glad/gl45.h>
  
  namespace tcob::gfx::gl45 {
  constexpr auto convert_enum(TextureFormat format) -> std::pair<GLenum, GLenum>
  {
      switch (format) {
      case TextureFormat::R8:
          return { GL_R8, GL_RED };
      case TextureFormat::RGB8:
          return { GL_RGB8, GL_RGB };
      case TextureFormat::RGBA8:
          return { GL_RGBA8, GL_RGBA };
      default:
          return { GL_INVALID_ENUM, GL_INVALID_ENUM };
      }
  }
  
  constexpr auto convert_enum(TextureFiltering filtering) -> GLenum
  {
      switch (filtering) {
      case TextureFiltering::Linear:
          return GL_LINEAR;
      case TextureFiltering::NearestNeighbor:
          return GL_NEAREST;
      default:
          return GL_INVALID_ENUM;
      }
  }
  
  constexpr auto convert_enum(TextureWrap wrap) -> GLenum
  {
      switch (wrap) {
      case TextureWrap::ClampToEdge:
          return GL_CLAMP_TO_EDGE;
      case TextureWrap::ClampToBorder:
          return GL_CLAMP_TO_BORDER;
      case TextureWrap::MirroredRepeat:
          return GL_MIRRORED_REPEAT;
      case TextureWrap::Repeat:
          return GL_REPEAT;
      case TextureWrap::MirrorClampToEdge:
          return GL_MIRROR_CLAMP_TO_EDGE;
      default:
          return GL_INVALID_ENUM;
      }
  }
  
  ////////////////////////////////////////////////////////////
  
  GLTexture::~GLTexture()
  {
      destroy();
  }
  
  auto GLTexture::get_properties() const -> TextureProperties
  {
      assert(ID);
  
      TextureProperties retValue {};
  
      GLint filtering { 0 };
      glGetTextureParameterIiv(ID, GL_TEXTURE_MAG_FILTER, &filtering);
  
      switch (filtering) {
      case GL_LINEAR:
          retValue.Filtering = TextureFiltering::Linear;
          break;
      case GL_NEAREST:
          retValue.Filtering = TextureFiltering::NearestNeighbor;
          break;
      }
  
      GLint wrapS { 0 };
      glGetTextureParameterIiv(ID, GL_TEXTURE_WRAP_S, &wrapS);
      GLint wrapT { 0 };
      glGetTextureParameterIiv(ID, GL_TEXTURE_WRAP_T, &wrapT);
  
      auto convertWrap {
          [](GLint wrap) {
              switch (wrap) {
              case GL_CLAMP_TO_EDGE:
                  return TextureWrap::ClampToEdge;
              case GL_CLAMP_TO_BORDER:
                  return TextureWrap::ClampToBorder;
              case GL_MIRRORED_REPEAT:
                  return TextureWrap::MirroredRepeat;
              case GL_REPEAT:
                  return TextureWrap::Repeat;
              case GL_MIRROR_CLAMP_TO_EDGE:
                  return TextureWrap::MirrorClampToEdge;
              }
              return TextureWrap::Repeat;
          }
      };
      retValue.WrapS = convertWrap(wrapS);
      retValue.WrapT = convertWrap(wrapT);
  
      return retValue;
  }
  
  void GLTexture::set_properties(const TextureProperties& props) const
  {
      assert(ID);
      const GLenum filtering { convert_enum(props.Filtering) };
  
      glTextureParameteri(ID, GL_TEXTURE_MIN_FILTER, filtering);
      glTextureParameteri(ID, GL_TEXTURE_MAG_FILTER, filtering);
  
      glTextureParameteri(ID, GL_TEXTURE_WRAP_S, convert_enum(props.WrapS));
      glTextureParameteri(ID, GL_TEXTURE_WRAP_T, convert_enum(props.WrapT));
  }
  
  void GLTexture::bind_texture_unit(u32 tu) const
  {
      assert(ID);
      glBindTextureUnit(tu, ID);
  }
  
  auto GLTexture::copy_to_image(const SizeU& size) const -> Image
  {
      std::vector<ubyte> buffer;
      buffer.reserve(size.Width * size.Height * 4);
  
      glGetTextureImage(ID, 0, GL_RGBA, GL_UNSIGNED_BYTE, static_cast<GLsizei>(buffer.capacity()), buffer.data());
  
      return Image::CreateFromBuffer(size, 4, buffer.data());
  }
  
  void GLTexture::create_or_resize_1D(u32 texsize)
  {
      if (ID) {
          do_destroy();
      }
      create(GL_TEXTURE_1D);
      glTextureStorage1D(ID, 1, GL_RGBA8, texsize);
  }
  
  void GLTexture::update_1D(i32 offsetX, i32 width, const void* data) const
  {
      assert(ID);
      glTextureSubImage1D(ID, 0, offsetX, width, GL_RGBA, GL_UNSIGNED_BYTE, data);
  }
  
  void GLTexture::create_or_resize_2D(const SizeU& texsize, TextureFormat format)
  {
      if (ID) {
          do_destroy();
      }
  
      create(GL_TEXTURE_2D);
  
      const auto [iform, _] { convert_enum(format) };
      glTextureStorage2D(ID, 1, iform, texsize.Width, texsize.Height);
  }
  
  void GLTexture::update_2D(const PointU& origin, const SizeU& size, const void* data, TextureFormat format, i32 rowLength, i32 alignment) const
  {
      assert(ID);
      glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
      glPixelStorei(GL_UNPACK_ROW_LENGTH, rowLength);
      const auto [_, form] { convert_enum(format) };
      glTextureSubImage2D(ID, 0, origin.X, origin.Y, size.Width, size.Height, form, GL_UNSIGNED_BYTE, data);
      glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
  }
  
  void GLTexture::create_or_resize_2DArray(const SizeU& texsize, u32 depth, TextureFormat format)
  {
      if (ID) {
          do_destroy();
      }
  
      create(GL_TEXTURE_2D_ARRAY);
  
      const auto [iform, _] { convert_enum(format) };
      glTextureStorage3D(ID, 1, iform, texsize.Width, texsize.Height, depth);
  }
  
  void GLTexture::update_2DArray(const PointU& origin, const SizeU& size, const void* data, u32 depth, TextureFormat format, i32 rowLength, i32 alignment) const
  {
      assert(ID);
      glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
      glPixelStorei(GL_UNPACK_ROW_LENGTH, rowLength);
      const auto [_, form] { convert_enum(format) };
      glTextureSubImage3D(ID, 0, origin.X, origin.Y, depth, size.Width, size.Height, 1, form, GL_UNSIGNED_BYTE, data);
      glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
  }
  
  auto GLTexture::copy_to_image_2DArray(const SizeU& size, u32 depth) const -> Image
  {
      std::vector<ubyte> buffer;
      buffer.reserve(size.Width * size.Height * 4 * depth);
  
      glGetTextureImage(ID, 0, GL_RGBA, GL_UNSIGNED_BYTE, static_cast<GLsizei>(buffer.capacity()), buffer.data());
  
      return Image::CreateFromBuffer({ size.Width, size.Height * depth }, 4, buffer.data());
  }
  
  void GLTexture::do_destroy()
  {
      glDeleteTextures(1, &ID);
  }
  
  void GLTexture::create(int type)
  {
      glCreateTextures(type, 1, &ID);
      set_properties({});
  }
  }