/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef WEBGLTYPES_H_
#define WEBGLTYPES_H_

#include <limits>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <vector>

#include "GLDefs.h"
#include "GLVendor.h"
#include "ImageContainer.h"
#include "mozilla/Casting.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Range.h"
#include "mozilla/RefCounted.h"
#include "mozilla/Result.h"
#include "mozilla/ResultVariant.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/BuildConstants.h"
#include "mozilla/gfx/Logging.h"
#include "mozilla/gfx/Point.h"
#include "mozilla/gfx/Rect.h"
#include "mozilla/ipc/Shmem.h"
#include "mozilla/layers/LayersSurfaces.h"
#include "gfxTypes.h"

#include "nsTArray.h"
#include "nsString.h"
#include "mozilla/dom/WebGLRenderingContextBinding.h"
#include "mozilla/ipc/SharedMemoryBasic.h"
#include "TiedFields.h"

// Manual reflection of WebIDL typedefs that are different from their
// OpenGL counterparts.
using WebGLsizeiptr = int64_t;
using WebGLintptr = int64_t;
using WebGLboolean = bool;

// -

namespace mozilla {
namespace gl {
class GLContext;  // This is going to be needed a lot.
}  // namespace gl

// -
// Prevent implicit conversions into calloc and malloc. (mozilla namespace
// only!)

template <typename DestT>
class ForbidNarrowing final {
  DestT mVal;

 public:
  template <typename SrcT>
  MOZ_IMPLICIT ForbidNarrowing(SrcT val) : mVal(val) {
    static_assert(
        std::numeric_limits<SrcT>::min() >= std::numeric_limits<DestT>::min(),
        "SrcT must be narrower than DestT.");
    static_assert(
        std::numeric_limits<SrcT>::max() <= std::numeric_limits<DestT>::max(),
        "SrcT must be narrower than DestT.");
  }

  explicit operator DestT() const { return mVal; }
};

inline void* malloc(const ForbidNarrowing<size_t> s) {
  return ::malloc(size_t(s));
}

inline void* calloc(const ForbidNarrowing<size_t> n,
                    const ForbidNarrowing<size_t> size) {
  return ::calloc(size_t(n), size_t(size));
}

// -

// TODO: Remove this now-mere-alias.
template <typename From>
inline auto AutoAssertCast(const From val) {
  return LazyAssertedCast(val);
}

const char* GetEnumName(GLenum val, const char* defaultRet = "<unknown>");
std::string EnumString(GLenum val);

namespace webgl {
template <typename T>
struct QueueParamTraits;
class TexUnpackBytes;
class TexUnpackImage;
class TexUnpackSurface;
}  // namespace webgl

class ClientWebGLContext;
struct WebGLTexPboOffset;
class WebGLTexture;
class WebGLBuffer;
class WebGLFramebuffer;
class WebGLProgram;
class WebGLQuery;
class WebGLRenderbuffer;
class WebGLSampler;
class WebGLShader;
class WebGLSync;
class WebGLTexture;
class WebGLTransformFeedback;
class WebGLVertexArray;

// -

class VRefCounted : public RefCounted<VRefCounted> {
 public:
  virtual ~VRefCounted() = default;

#ifdef MOZ_REFCOUNTED_LEAK_CHECKING
  virtual const char* typeName() const = 0;
  virtual size_t typeSize() const = 0;
#endif
};

// -

/*
 * Implementing WebGL (or OpenGL ES 2.0) on top of desktop OpenGL requires
 * emulating the vertex attrib 0 array when it's not enabled. Indeed,
 * OpenGL ES 2.0 allows drawing without vertex attrib 0 array enabled, but
 * desktop OpenGL does not allow that.
 */
enum class WebGLVertexAttrib0Status : uint8_t {
  Default,                     // default status - no emulation needed
  EmulatedUninitializedArray,  // need an artificial attrib 0 array, but
                               // contents may be left uninitialized
  EmulatedInitializedArray  // need an artificial attrib 0 array, and contents
                            // must be initialized
};

/*
 * The formats that may participate, either as source or destination formats,
 * in WebGL texture conversions. This includes:
 *  - all the formats accepted by WebGL.texImage2D, e.g. RGBA4444
 *  - additional formats provided by extensions, e.g. RGB32F
 *  - additional source formats, depending on browser details, used when
 * uploading textures from DOM elements. See gfxImageSurface::Format().
 */
enum class WebGLTexelFormat : uint8_t {
  // returned by SurfaceFromElementResultToImageSurface to indicate absence of
  // image data
  None,
  // common value for formats for which format conversions are not supported
  FormatNotSupportingAnyConversion,
  // dummy pseudo-format meaning "use the other format".
  // For example, if SrcFormat=Auto and DstFormat=RGB8, then the source
  // is implicitly treated as being RGB8 itself.
  Auto,
  // 1-channel formats
  A8,
  A16F,  // OES_texture_half_float
  A32F,  // OES_texture_float
  R8,
  R16F,  // OES_texture_half_float
  R32F,  // OES_texture_float
  // 2-channel formats
  RA8,
  RA16F,  // OES_texture_half_float
  RA32F,  // OES_texture_float
  RG8,
  RG16F,
  RG32F,
  // 3-channel formats
  RGB8,
  RGB565,
  RGB11F11F10F,
  RGB16F,  // OES_texture_half_float
  RGB32F,  // OES_texture_float
  // 4-channel formats
  RGBA8,
  RGBA5551,
  RGBA4444,
  RGBA16F,  // OES_texture_half_float
  RGBA32F,  // OES_texture_float
  // DOM element source only formats.
  RGBX8,
  BGRX8,
  BGRA8
};

enum class WebGLTexImageFunc : uint8_t {
  TexImage,
  TexSubImage,
  CopyTexImage,
  CopyTexSubImage,
  CompTexImage,
  CompTexSubImage,
};

enum class WebGLTexDimensions : uint8_t { Tex2D, Tex3D };

// Please keep extensions in alphabetic order.
enum class WebGLExtensionID : uint8_t {
  ANGLE_instanced_arrays,
  EXT_blend_minmax,
  EXT_color_buffer_float,
  EXT_color_buffer_half_float,
  EXT_disjoint_timer_query,
  EXT_float_blend,
  EXT_frag_depth,
  EXT_shader_texture_lod,
  EXT_sRGB,
  EXT_texture_compression_bptc,
  EXT_texture_compression_rgtc,
  EXT_texture_filter_anisotropic,
  EXT_texture_norm16,
  MOZ_debug,
  OES_draw_buffers_indexed,
  OES_element_index_uint,
  OES_fbo_render_mipmap,
  OES_standard_derivatives,
  OES_texture_float,
  OES_texture_float_linear,
  OES_texture_half_float,
  OES_texture_half_float_linear,
  OES_vertex_array_object,
  OVR_multiview2,
  WEBGL_color_buffer_float,
  WEBGL_compressed_texture_astc,
  WEBGL_compressed_texture_etc,
  WEBGL_compressed_texture_etc1,
  WEBGL_compressed_texture_pvrtc,
  WEBGL_compressed_texture_s3tc,
  WEBGL_compressed_texture_s3tc_srgb,
  WEBGL_debug_renderer_info,
  WEBGL_debug_shaders,
  WEBGL_depth_texture,
  WEBGL_draw_buffers,
  WEBGL_explicit_present,
  WEBGL_lose_context,
  WEBGL_provoking_vertex,
  Max
};

class UniqueBuffer final {
  // Like UniquePtr<>, but for void* and malloc/calloc/free.
  void* mBuffer = nullptr;

 public:
  static inline UniqueBuffer Take(void* buffer) {
    UniqueBuffer ret;
    ret.mBuffer = buffer;
    return ret;
  }

  UniqueBuffer() = default;

  ~UniqueBuffer() { reset(); }

  UniqueBuffer(UniqueBuffer&& rhs) { *this = std::move(rhs); }

  UniqueBuffer& operator=(UniqueBuffer&& rhs) {
    reset();
    this->mBuffer = rhs.mBuffer;
    rhs.mBuffer = nullptr;
    return *this;
  }

  explicit operator bool() const { return bool(mBuffer); }

  void* get() const { return mBuffer; }

  void reset() {
    // Believe it or not, when `free` unconditional, it was showing up
    // in profiles, nearly 20% of time spent in MethodDispatcther<UniformData>
    // on Aquarium.
    if (mBuffer) {
      free(mBuffer);
      mBuffer = nullptr;
    }
  }
};

namespace webgl {
struct FormatUsageInfo;

static constexpr GLenum kErrorPerfWarning = 0x10001;

struct SampleableInfo final {
  const char* incompleteReason = nullptr;
  uint32_t levels = 0;
  const webgl::FormatUsageInfo* usage = nullptr;
  bool isDepthTexCompare = false;

  bool IsComplete() const { return bool(levels); }
};

enum class AttribBaseType : uint8_t {
  Boolean,  // Can convert from anything.
  Float,    // Also includes NormU?Int
  Int,
  Uint,
};
webgl::AttribBaseType ToAttribBaseType(GLenum);
const char* ToString(AttribBaseType);

enum class UniformBaseType : uint8_t {
  Float,
  Int,
  Uint,
};
const char* ToString(UniformBaseType);

using ObjectId = uint64_t;

enum class BufferKind : uint8_t {
  Undefined,
  Index,
  NonIndex,
};

}  // namespace webgl

// -

struct FloatOrInt final  // For TexParameter[fi] and friends.
{
  bool isFloat = false;
  uint8_t padding[3] = {};
  GLfloat f = 0;
  GLint i = 0;

  explicit FloatOrInt(GLint x = 0) : isFloat(false), f(x), i(x) {}

  explicit FloatOrInt(GLfloat x) : isFloat(true), f(x), i(roundf(x)) {}

  auto MutTiedFields() { return std::tie(isFloat, padding, f, i); }
};

// -

struct WebGLContextOptions final {
  bool alpha = true;
  bool depth = true;
  bool stencil = false;
  bool premultipliedAlpha = true;

  bool antialias = true;
  bool preserveDrawingBuffer = false;
  bool failIfMajorPerformanceCaveat = false;
  bool xrCompatible = false;

  dom::WebGLPowerPreference powerPreference =
      dom::WebGLPowerPreference::Default;
  bool ignoreColorSpace = true;
  dom::PredefinedColorSpace colorSpace = dom::PredefinedColorSpace::Srgb;
  bool shouldResistFingerprinting = true;

  bool enableDebugRendererInfo = false;

  auto MutTiedFields() {
    // clang-format off
    return std::tie(
      alpha,
      depth,
      stencil,
      premultipliedAlpha,

      antialias,
      preserveDrawingBuffer,
      failIfMajorPerformanceCaveat,
      xrCompatible,

      powerPreference,
      colorSpace,
      ignoreColorSpace,
      shouldResistFingerprinting,

      enableDebugRendererInfo);
    // clang-format on
  }

  // -

  WebGLContextOptions();
  WebGLContextOptions(const WebGLContextOptions&) = default;

  using Self = WebGLContextOptions;
  friend bool operator==(const Self& a, const Self& b) {
    return TiedFields(a) == TiedFields(b);
  }
  friend bool operator!=(const Self& a, const Self& b) { return !(a == b); }
};

namespace gfx {

inline ColorSpace2 ToColorSpace2(const dom::PredefinedColorSpace cs) {
  switch (cs) {
    case dom::PredefinedColorSpace::Srgb:
      return ColorSpace2::SRGB;
    case dom::PredefinedColorSpace::Display_p3:
      return ColorSpace2::DISPLAY_P3;
    case dom::PredefinedColorSpace::EndGuard_:
      break;
  }
  MOZ_CRASH("Exhaustive switch");
}

}  // namespace gfx

// -

template <typename _T>
struct avec2 {
  using T = _T;

  T x = T();
  T y = T();

  auto MutTiedFields() { return std::tie(x, y); }

  template <typename U, typename V>
  static Maybe<avec2> From(const U _x, const V _y) {
    const auto x = CheckedInt<T>(_x);
    const auto y = CheckedInt<T>(_y);
    if (!x.isValid() || !y.isValid()) return {};
    return Some(avec2(x.value(), y.value()));
  }

  template <typename U>
  static auto From(const U& val) {
    return From(val.x, val.y);
  }
  template <typename U>
  static auto FromSize(const U& val) {
    return From(val.width, val.height);
  }

  avec2() = default;
  avec2(const T _x, const T _y) : x(_x), y(_y) {}

  bool operator==(const avec2& rhs) const { return x == rhs.x && y == rhs.y; }
  bool operator!=(const avec2& rhs) const { return !(*this == rhs); }

#define _(OP)                                 \
  avec2 operator OP(const avec2& rhs) const { \
    return {x OP rhs.x, y OP rhs.y};          \
  }                                           \
  avec2 operator OP(const T rhs) const { return {x OP rhs, y OP rhs}; }

  _(+)
  _(-)
  _(*)
  _(/)

#undef _

  avec2 Clamp(const avec2& min, const avec2& max) const {
    return {mozilla::Clamp(x, min.x, max.x), mozilla::Clamp(y, min.y, max.y)};
  }

  // mozilla::Clamp doesn't work on floats, so be clear that this is a min+max
  // helper.
  avec2 ClampMinMax(const avec2& min, const avec2& max) const {
    const auto ClampScalar = [](const T v, const T min, const T max) {
      return std::max(min, std::min(v, max));
    };
    return {ClampScalar(x, min.x, max.x), ClampScalar(y, min.y, max.y)};
  }

  template <typename U>
  U StaticCast() const {
    return {static_cast<typename U::T>(x), static_cast<typename U::T>(y)};
  }
};

template <typename T>
avec2<T> MinExtents(const avec2<T>& a, const avec2<T>& b) {
  return {std::min(a.x, b.x), std::min(a.y, b.y)};
}

template <typename T>
avec2<T> MaxExtents(const avec2<T>& a, const avec2<T>& b) {
  return {std::max(a.x, b.x), std::max(a.y, b.y)};
}

// -

template <typename _T>
struct avec3 {
  using T = _T;

  T x = T();
  T y = T();
  T z = T();

  auto MutTiedFields() { return std::tie(x, y, z); }

  template <typename U, typename V>
  static Maybe<avec3> From(const U _x, const V _y, const V _z) {
    const auto x = CheckedInt<T>(_x);
    const auto y = CheckedInt<T>(_y);
    const auto z = CheckedInt<T>(_z);
    if (!x.isValid() || !y.isValid() || !z.isValid()) return {};
    return Some(avec3(x.value(), y.value(), z.value()));
  }

  template <typename U>
  static auto From(const U& val) {
    return From(val.x, val.y, val.z);
  }

  avec3() = default;
  avec3(const T _x, const T _y, const T _z) : x(_x), y(_y), z(_z) {}

  bool operator==(const avec3& rhs) const {
    return x == rhs.x && y == rhs.y && z == rhs.z;
  }
  bool operator!=(const avec3& rhs) const { return !(*this == rhs); }
};

using ivec2 = avec2<int32_t>;
using ivec3 = avec3<int32_t>;
using uvec2 = avec2<uint32_t>;
using uvec3 = avec3<uint32_t>;

inline ivec2 AsVec(const gfx::IntSize& s) { return {s.width, s.height}; }

// -

namespace webgl {

struct PackingInfo final {
  GLenum format = 0;
  GLenum type = 0;

  auto MutTiedFields() { return std::tie(format, type); }

  using Self = PackingInfo;
  friend bool operator<(const Self& a, const Self& b) {
    return TiedFields(a) < TiedFields(b);
  }
  friend bool operator==(const Self& a, const Self& b) {
    return TiedFields(a) == TiedFields(b);
  }

  template <class T>
  friend T& operator<<(T& s, const PackingInfo& pi) {
    s << "PackingInfo{format: " << EnumString(pi.format)
      << ", type: " << EnumString(pi.type) << "}";
    return s;
  }
};

struct DriverUnpackInfo final {
  GLenum internalFormat = 0;
  GLenum unpackFormat = 0;
  GLenum unpackType = 0;

  PackingInfo ToPacking() const { return {unpackFormat, unpackType}; }
};

// -

template <typename E>
class EnumMask {
 public:
  uint64_t mBits = 0;

 private:
  struct BitRef final {
    EnumMask& bits;
    const uint64_t mask;

    explicit operator bool() const { return bits.mBits & mask; }

    auto& operator=(const bool val) {
      if (val) {
        bits.mBits |= mask;
      } else {
        bits.mBits &= ~mask;
      }
      return *this;
    }
  };

  uint64_t Mask(const E i) const {
    return uint64_t{1} << static_cast<uint64_t>(i);
  }

 public:
  BitRef operator[](const E i) { return {*this, Mask(i)}; }
  bool operator[](const E i) const { return mBits & Mask(i); }
};

class ExtensionBits : public EnumMask<WebGLExtensionID> {};

// -

enum class ContextLossReason : uint8_t {
  None,
  Manual,
  Guilty,
};

inline bool ReadContextLossReason(const uint8_t val,
                                  ContextLossReason* const out) {
  if (val > static_cast<uint8_t>(ContextLossReason::Guilty)) {
    return false;
  }
  *out = static_cast<ContextLossReason>(val);
  return true;
}

// -

struct InitContextDesc final {
  bool isWebgl2 = false;
  bool resistFingerprinting = false;
  uvec2 size = {};
  WebGLContextOptions options;
  uint32_t principalKey = 0;
};

constexpr uint32_t kMaxTransformFeedbackSeparateAttribs = 4;

struct Limits final {
  ExtensionBits supportedExtensions;

  // WebGL 1
  uint32_t maxTexUnits = 0;
  uint32_t maxTex2dSize = 0;
  uint32_t maxTexCubeSize = 0;
  uint32_t maxVertexAttribs = 0;
  uint32_t maxViewportDim = 0;
  std::array<float, 2> pointSizeRange = {{1, 1}};
  std::array<float, 2> lineWidthRange = {{1, 1}};

  // WebGL 2
  uint32_t maxTexArrayLayers = 0;
  uint32_t maxTex3dSize = 0;
  uint32_t maxUniformBufferBindings = 0;
  uint32_t uniformBufferOffsetAlignment = 0;

  // Exts
  bool astcHdr = false;
  uint32_t maxColorDrawBuffers = 1;
  uint64_t queryCounterBitsTimeElapsed = 0;
  uint64_t queryCounterBitsTimestamp = 0;
  uint32_t maxMultiviewLayers = 0;
};

struct InitContextResult final {
  std::string error;
  WebGLContextOptions options;
  webgl::Limits limits;
  EnumMask<layers::SurfaceDescriptor::Type> uploadableSdTypes;
  gl::GLVendor vendor;
};

// -

struct ErrorInfo final {
  GLenum type;
  std::string info;
};

struct ShaderPrecisionFormat final {
  GLint rangeMin = 0;
  GLint rangeMax = 0;
  GLint precision = 0;
};

// -

enum class LossStatus {
  Ready,

  Lost,
  LostForever,
  LostManually,
};

// -

struct CompileResult final {
  bool pending = true;
  nsCString log;
  nsCString translatedSource;
  bool success = false;
};

// -

struct OpaqueFramebufferOptions final {
  bool depthStencil = true;
  bool antialias = true;
  uint32_t width = 0;
  uint32_t height = 0;
};

// -

struct SwapChainOptions final {
  layers::RemoteTextureId remoteTextureId;
  layers::RemoteTextureOwnerId remoteTextureOwnerId;
  bool bgra = false;
  bool forceAsyncPresent = false;
  // Pad to sizeof(u64):
  uint16_t padding1 = 0;
  uint32_t padding2 = 0;

  auto MutTiedFields() {
    return std::tie(remoteTextureId, remoteTextureOwnerId, bgra,
                    forceAsyncPresent, padding1, padding2);
  }
};

// -

struct ActiveInfo {
  GLenum elemType = 0;     // `type`
  uint32_t elemCount = 0;  // `size`
  std::string name;
};

struct ActiveAttribInfo final : public ActiveInfo {
  int32_t location = -1;
  AttribBaseType baseType = AttribBaseType::Float;
};

struct ActiveUniformInfo final : public ActiveInfo {
  std::unordered_map<uint32_t, uint32_t>
      locByIndex;  // Uniform array locations are sparse.
  int32_t block_index = -1;
  int32_t block_offset = -1;  // In block, offset.
  int32_t block_arrayStride = -1;
  int32_t block_matrixStride = -1;
  bool block_isRowMajor = false;
};

struct ActiveUniformBlockInfo final {
  std::string name;
  // BLOCK_BINDING is dynamic state
  uint32_t dataSize = 0;
  std::vector<uint32_t> activeUniformIndices;
  bool referencedByVertexShader = false;
  bool referencedByFragmentShader = false;
};

struct LinkActiveInfo final {
  std::vector<ActiveAttribInfo> activeAttribs;
  std::vector<ActiveUniformInfo> activeUniforms;
  std::vector<ActiveUniformBlockInfo> activeUniformBlocks;
  std::vector<ActiveInfo> activeTfVaryings;
};

struct LinkResult final {
  bool pending = true;
  nsCString log;
  bool success = false;
  LinkActiveInfo active;
  GLenum tfBufferMode = 0;
};

// -

/// 4x32-bit primitives, with a type tag.
struct TypedQuad final {
  alignas(alignof(float)) std::array<uint8_t, 4 * sizeof(float)> data = {};
  webgl::AttribBaseType type = webgl::AttribBaseType::Float;
  uint8_t padding[3] = {};

  constexpr auto MutTiedFields() { return std::tie(data, type, padding); }
};

/// [1-16]x32-bit primitives, with a type tag.
struct GetUniformData final {
  alignas(alignof(float)) uint8_t data[4 * 4 * sizeof(float)] = {};
  GLenum type = 0;
};

struct FrontBufferSnapshotIpc final {
  uvec2 surfSize = {};
  Maybe<mozilla::ipc::Shmem> shmem = {};
};

struct ReadPixelsResult {
  gfx::IntRect subrect = {};
  size_t byteStride = 0;
};

struct ReadPixelsResultIpc final : public ReadPixelsResult {
  Maybe<mozilla::ipc::Shmem> shmem = {};
};

struct VertAttribPointerDesc final {
  bool intFunc = false;
  uint8_t channels = 4;
  bool normalized = false;
  uint8_t byteStrideOrZero = 0;
  GLenum type = LOCAL_GL_FLOAT;
  uint64_t byteOffset = 0;

  auto MutTiedFields() {
    return std::tie(intFunc, channels, normalized, byteStrideOrZero, type,
                    byteOffset);
  }
};

struct VertAttribPointerCalculated final {
  uint8_t byteSize = 4 * 4;
  uint8_t byteStride = 4 * 4;  // at-most 255
  webgl::AttribBaseType baseType = webgl::AttribBaseType::Float;
};

}  // namespace webgl

/**
 * Represents a block of memory that it may or may not own.  The
 * inner data type must be trivially copyable by memcpy.
 */
template <typename T = uint8_t>
class RawBuffer final {
  const T* mBegin = nullptr;
  size_t mLen = 0;
  UniqueBuffer mOwned;

 public:
  using ElementType = T;

  /**
   * If aTakeData is true, RawBuffer will delete[] the memory when destroyed.
   */
  explicit RawBuffer(const Range<const T>& data, UniqueBuffer&& owned = {})
      : mBegin(data.begin().get()),
        mLen(data.length()),
        mOwned(std::move(owned)) {}

  explicit RawBuffer(const size_t len) : mLen(len) {}

  ~RawBuffer() = default;

  Range<const T> Data() const { return {mBegin, mLen}; }
  const auto& begin() const { return mBegin; }
  const auto& size() const { return mLen; }

  void Shrink(const size_t newLen) {
    if (mLen <= newLen) return;
    mLen = newLen;
  }

  RawBuffer() = default;

  RawBuffer(const RawBuffer&) = delete;
  RawBuffer& operator=(const RawBuffer&) = delete;

  RawBuffer(RawBuffer&&) = default;
  RawBuffer& operator=(RawBuffer&&) = default;
};

template <class T>
inline Range<T> ShmemRange(const mozilla::ipc::Shmem& shmem) {
  return {shmem.get<T>(), shmem.Size<T>()};
}

// -

template <typename C, typename K>
inline auto MaybeFind(C& container, const K& key)
    -> decltype(&(container.find(key)->second)) {
  const auto itr = container.find(key);
  if (itr == container.end()) return nullptr;
  return &(itr->second);
}

template <typename C, typename K>
inline typename C::mapped_type Find(
    const C& container, const K& key,
    const typename C::mapped_type notFound = {}) {
  const auto itr = container.find(key);
  if (itr == container.end()) return notFound;
  return itr->second;
}

// -

template <typename T, typename U>
inline Maybe<T> MaybeAs(const U val) {
  const auto checked = CheckedInt<T>(val);
  if (!checked.isValid()) return {};
  return Some(checked.value());
}

// -

inline GLenum IsTexImageTarget(const GLenum imageTarget) {
  switch (imageTarget) {
    case LOCAL_GL_TEXTURE_2D:
    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
    case LOCAL_GL_TEXTURE_3D:
    case LOCAL_GL_TEXTURE_2D_ARRAY:
      return true;
  }
  return false;
}

inline GLenum ImageToTexTarget(const GLenum imageTarget) {
  switch (imageTarget) {
    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
      return LOCAL_GL_TEXTURE_CUBE_MAP;
  }
  if (IsTexImageTarget(imageTarget)) {
    return imageTarget;
  }
  return 0;
}

inline bool IsTexTarget3D(const GLenum texTarget) {
  switch (texTarget) {
    case LOCAL_GL_TEXTURE_2D_ARRAY:
    case LOCAL_GL_TEXTURE_3D:
      return true;

    default:
      return false;
  }
}

// -

namespace dom {
class Element;
class ImageBitmap;
class ImageData;
class OffscreenCanvas;
}  // namespace dom

struct TexImageSource {
  const dom::ArrayBufferView* mView = nullptr;
  GLuint mViewElemOffset = 0;
  GLuint mViewElemLengthOverride = 0;

  const WebGLintptr* mPboOffset = nullptr;

  const dom::ImageBitmap* mImageBitmap = nullptr;
  const dom::ImageData* mImageData = nullptr;

  const dom::OffscreenCanvas* mOffscreenCanvas = nullptr;

  const dom::Element* mDomElem = nullptr;
  ErrorResult* mOut_error = nullptr;
};

namespace webgl {

template <class DerivedT>
struct DeriveNotEq {
  bool operator!=(const DerivedT& rhs) const {
    const auto self = reinterpret_cast<const DerivedT*>(this);
    return !(*self == rhs);
  }
};

struct PixelPackingState : public DeriveNotEq<PixelPackingState> {
  uint32_t alignmentInTypeElems = 4;  // ALIGNMENT isn't naive byte alignment!
  uint32_t rowLength = 0;
  uint32_t imageHeight = 0;
  uint32_t skipPixels = 0;
  uint32_t skipRows = 0;
  uint32_t skipImages = 0;

  auto MutTiedFields() {
    return std::tie(alignmentInTypeElems, rowLength, imageHeight, skipPixels,
                    skipRows, skipImages);
  }

  using Self = PixelPackingState;
  friend bool operator==(const Self& a, const Self& b) {
    return TiedFields(a) == TiedFields(b);
  }

  static void AssertDefaultUnpack(gl::GLContext& gl, const bool isWebgl2) {
    PixelPackingState{}.AssertCurrentUnpack(gl, isWebgl2);
  }

  void ApplyUnpack(gl::GLContext&, bool isWebgl2,
                   const uvec3& uploadSize) const;
  bool AssertCurrentUnpack(gl::GLContext&, bool isWebgl2) const;
};

struct PixelUnpackStateWebgl final : public PixelPackingState {
  GLenum colorspaceConversion =
      dom::WebGLRenderingContext_Binding::BROWSER_DEFAULT_WEBGL;
  bool flipY = false;
  bool premultiplyAlpha = false;
  bool requireFastPath = false;
  uint8_t padding = {};

  auto MutTiedFields() {
    return std::tuple_cat(PixelPackingState::MutTiedFields(),
                          std::tie(colorspaceConversion, flipY,
                                   premultiplyAlpha, requireFastPath, padding));
  }
};

struct ExplicitPixelPackingState final {
  struct Metrics final {
    uvec3 usedSize = {};
    size_t bytesPerPixel = 0;

    // (srcStrideAndRowOverride.x, otherwise ROW_LENGTH != 0, otherwise size.x)
    // ...aligned to ALIGNMENT.
    size_t bytesPerRowStride = 0;

    // structuredSrcSize.y, otherwise IMAGE_HEIGHT*(SKIP_IMAGES+size.z)
    size_t totalRows = 0;

    // This ensures that no one else needs to do CheckedInt math.
    size_t totalBytesUsed = 0;
    size_t totalBytesStrided = 0;
  };

  // It's so important that these aren't modified once evaluated.
  const PixelPackingState state;
  const Metrics metrics;

  static Result<ExplicitPixelPackingState, std::string> ForUseWith(
      const PixelPackingState&, GLenum target, const uvec3& subrectSize,
      const webgl::PackingInfo&, const Maybe<size_t> bytesPerRowStrideOverride);
};

struct ReadPixelsDesc final {
  ivec2 srcOffset;
  uvec2 size;
  PackingInfo pi = {LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE};
  PixelPackingState packState;

  auto MutTiedFields() { return std::tie(srcOffset, size, pi, packState); }
};

}  // namespace webgl

namespace webgl {

struct TexUnpackBlobDesc final {
  GLenum imageTarget = LOCAL_GL_TEXTURE_2D;
  uvec3 size;
  gfxAlphaType srcAlphaType = gfxAlphaType::NonPremult;

  Maybe<RawBuffer<>> cpuData;
  Maybe<uint64_t> pboOffset;

  Maybe<uvec2> structuredSrcSize;
  RefPtr<layers::Image> image;
  Maybe<layers::SurfaceDescriptor> sd;
  RefPtr<gfx::DataSourceSurface> dataSurf;

  webgl::PixelUnpackStateWebgl unpacking;
  bool applyUnpackTransforms = true;

  // -

  auto ExplicitUnpacking(const webgl::PackingInfo& pi,
                         const Maybe<size_t> bytesPerRowStrideOverride) const {
    return ExplicitPixelPackingState::ForUseWith(this->unpacking,
                                                 this->imageTarget, this->size,
                                                 pi, bytesPerRowStrideOverride);
  }

  void Shrink(const webgl::PackingInfo&);
};

}  // namespace webgl

// ---------------------------------------
// MakeRange

template <typename T, size_t N>
inline Range<const T> MakeRange(T (&arr)[N]) {
  return {arr, N};
}

template <typename T>
inline Range<const T> MakeRange(const dom::Sequence<T>& seq) {
  return {seq.Elements(), seq.Length()};
}

template <typename T>
inline Range<const T> MakeRange(const RawBuffer<T>& from) {
  return from.Data();
}

// abv = ArrayBufferView
template <typename T>
inline auto MakeRangeAbv(const T& abv)
    -> Range<const typename T::element_type> {
  abv.ComputeState();
  return {abv.Data(), abv.Length()};
}

// -

constexpr auto kUniversalAlignment = alignof(std::max_align_t);

template <typename T>
inline size_t AlignmentOffset(const size_t alignment, const T posOrPtr) {
  MOZ_ASSERT(alignment);
  const auto begin = reinterpret_cast<uintptr_t>(posOrPtr);
  const auto wholeMultiples = (begin + (alignment - 1)) / alignment;
  const auto aligned = wholeMultiples * alignment;
  return aligned - begin;
}

template <typename T>
inline size_t ByteSize(const Range<T>& range) {
  return range.length() * sizeof(T);
}

Maybe<Range<const uint8_t>> GetRangeFromView(const dom::ArrayBufferView& view,
                                             GLuint elemOffset,
                                             GLuint elemCountOverride);

// -

template <typename T>
RawBuffer<T> RawBufferView(const Range<T>& range) {
  return RawBuffer<T>{range};
}

// -

Maybe<webgl::ErrorInfo> CheckBindBufferRange(
    const GLenum target, const GLuint index, const bool isBuffer,
    const uint64_t offset, const uint64_t size, const webgl::Limits& limits);

Maybe<webgl::ErrorInfo> CheckFramebufferAttach(const GLenum bindImageTarget,
                                               const GLenum curTexTarget,
                                               const uint32_t mipLevel,
                                               const uint32_t zLayerBase,
                                               const uint32_t zLayerCount,
                                               const webgl::Limits& limits);

Result<webgl::VertAttribPointerCalculated, webgl::ErrorInfo>
CheckVertexAttribPointer(bool isWebgl2, const webgl::VertAttribPointerDesc&);

uint8_t ElemTypeComponents(GLenum elemType);

inline std::string ToString(const nsACString& text) {
  return {text.BeginReading(), text.Length()};
}

inline void Memcpy(const RangedPtr<uint8_t>& destBytes,
                   const RangedPtr<const uint8_t>& srcBytes,
                   const size_t byteSize) {
  // Trigger range asserts
  (void)(srcBytes + byteSize);
  (void)(destBytes + byteSize);

  memcpy(destBytes.get(), srcBytes.get(), byteSize);
}

template <class T, class U>
inline void Memcpy(const Range<T>* const destRange,
                   const RangedPtr<U>& srcBegin) {
  Memcpy(destRange->begin(), srcBegin, destRange->length());
}
template <class T, class U>
inline void Memcpy(const RangedPtr<T>* const destBegin,
                   const Range<U>& srcRange) {
  Memcpy(destBegin, srcRange->begin(), srcRange->length());
}

// -

inline bool StartsWith(const std::string_view str,
                       const std::string_view part) {
  return str.find(part) == 0;
}

// -

namespace webgl {

// In theory, this number can be unbounded based on the driver. However, no
// driver appears to expose more than 8. We might as well stop there too, for
// now.
// (http://opengl.gpuinfo.org/gl_stats_caps_single.php?listreportsbycap=GL_MAX_COLOR_ATTACHMENTS)
inline constexpr size_t kMaxDrawBuffers = 8;

union UniformDataVal {
  float f32;
  int32_t i32;
  uint32_t u32;
};

enum class ProvokingVertex : GLenum {
  FirstVertex = LOCAL_GL_FIRST_VERTEX_CONVENTION,
  LastVertex = LOCAL_GL_LAST_VERTEX_CONVENTION,
};
inline constexpr bool IsEnumCase(const ProvokingVertex raw) {
  switch (raw) {
    case ProvokingVertex::FirstVertex:
    case ProvokingVertex::LastVertex:
      return true;
  }
  return false;
}

template <class E>
inline constexpr std::optional<E> AsEnumCase(
    const std::underlying_type_t<E> raw) {
  const auto ret = static_cast<E>(raw);
  if (!IsEnumCase(ret)) return {};
  return ret;
}

}  // namespace webgl

}  // namespace mozilla

#endif
