// 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 _ALIAS_GL_H_
#define _ALIAS_GL_H_

#include <tabula.h>
#include <alias/memory.h>
#include <alias/cpp.h>

#include "glad.h"

#define ALIAS_GL_MAX_ATTRIBUTES 16
#define ALIAS_GL_MAX_BINDINGS 2
#define ALIAS_GL_MAX_UNIFORMS 16
#define ALIAS_GL_MAX_IMAGES 16
#define ALIAS_GL_MAX_BUFFERS 16

#define ALIAS_GL_VERTEX_BIT 0x01
#define ALIAS_GL_GEOMETRY_BIT 0x02
#define ALIAS_GL_TESSC_BIT 0x04
#define ALIAS_GL_TESSE_BIT 0x08
#define ALIAS_GL_FRAGMENT_BIT 0x10

#define ALIAS_GL_LOCAL_GROUP_SIZE_SUBGROUP_SIZE 0xFFFFFFFF

#define ALIAS_GL_SUBGROUP_ARITHMETIC (1 << 0)
#define ALIAS_GL_ARITHMETIC_INT64    (1 << 1)

enum alias_gl_Type {
  alias_gl_Type_unused,
  alias_gl_Type_float,
  alias_gl_Type_float2,
  alias_gl_Type_float3,
  alias_gl_Type_float4,
  alias_gl_Type_double,
  alias_gl_Type_double2,
  alias_gl_Type_double3,
  alias_gl_Type_double4,
  alias_gl_Type_int,
  alias_gl_Type_int2,
  alias_gl_Type_int3,
  alias_gl_Type_int4,
  alias_gl_Type_uint,
  alias_gl_Type_uint2,
  alias_gl_Type_uint3,
  alias_gl_Type_uint4,
  alias_gl_Type_bool,
  alias_gl_Type_bool2,
  alias_gl_Type_bool3,
  alias_gl_Type_bool4,
  alias_gl_Type_float2x2,
  alias_gl_Type_float3x3,
  alias_gl_Type_float4x4,
  alias_gl_Type_float2x3,
  alias_gl_Type_float2x4,
  alias_gl_Type_float3x2,
  alias_gl_Type_float3x4,
  alias_gl_Type_float4x2,
  alias_gl_Type_float4x3,
  alias_gl_Type_double2x2,
  alias_gl_Type_double3x3,
  alias_gl_Type_double4x4,
  alias_gl_Type_double2x3,
  alias_gl_Type_double2x4,
  alias_gl_Type_double3x2,
  alias_gl_Type_double3x4,
  alias_gl_Type_double4x2,
  alias_gl_Type_double4x3,
  alias_gl_Type_sampler1D,
  alias_gl_Type_sampler2D,
  alias_gl_Type_sampler3D,
  alias_gl_Type_samplerCube,
  alias_gl_Type_sampler1DShadow,
  alias_gl_Type_sampler2DShadow,
  alias_gl_Type_sampler1DArray,
  alias_gl_Type_sampler2DArray,
  alias_gl_Type_samplerCubeArray,
  alias_gl_Type_sampler1DArrayShadow,
  alias_gl_Type_sampler2DArrayShadow,
  alias_gl_Type_sampler2DMultisample,
  alias_gl_Type_sampler2DMultisampleArray,
  alias_gl_Type_samplerCubeShadow,
  alias_gl_Type_samplerCubeArrayShadow,
  alias_gl_Type_samplerBuffer,
  alias_gl_Type_sampler2DRect,
  alias_gl_Type_sampler2DRectShadow,
  alias_gl_Type_intSampler1D,
  alias_gl_Type_intSampler2D,
  alias_gl_Type_intSampler3D,
  alias_gl_Type_intSamplerCube,
  alias_gl_Type_intSampler1DArray,
  alias_gl_Type_intSampler2DArray,
  alias_gl_Type_intSamplerCubeMapArray,
  alias_gl_Type_intSampler2DMultisample,
  alias_gl_Type_intSampler2DMultisampleArray,
  alias_gl_Type_intSamplerBuffer,
  alias_gl_Type_intSampler2DRect,
  alias_gl_Type_uintSampler1D,
  alias_gl_Type_uintSampler2D,
  alias_gl_Type_uintSampler3D,
  alias_gl_Type_uintSamplerCube,
  alias_gl_Type_uintSampler1DArray,
  alias_gl_Type_uintSampler2DArray,
  alias_gl_Type_uintSamplerCubeMapArray,
  alias_gl_Type_uintSampler2DMultisample,
  alias_gl_Type_uintSampler2DMultisampleArray,
  alias_gl_Type_uintSamplerBuffer,
  alias_gl_Type_uintSampler2DRect,
  alias_gl_Type_image1D,
  alias_gl_Type_image2D,
  alias_gl_Type_image3D,
  alias_gl_Type_image2DRect,
  alias_gl_Type_imageCube,
  alias_gl_Type_imageBuffer,
  alias_gl_Type_image1DArray,
  alias_gl_Type_image2DArray,
  alias_gl_Type_imageCubeArray,
  alias_gl_Type_image2DMultisample,
  alias_gl_Type_image2DMultisampleArray,
  alias_gl_Type_intImage1D,
  alias_gl_Type_intImage2D,
  alias_gl_Type_intImage3D,
  alias_gl_Type_intImage2DRect,
  alias_gl_Type_intImageCube,
  alias_gl_Type_intImageBuffer,
  alias_gl_Type_intImage1DArray,
  alias_gl_Type_intImage2DArray,
  alias_gl_Type_intImageCubeArray,
  alias_gl_Type_intImage2DMultisample,
  alias_gl_Type_intImage2DMultisampleArray,
  alias_gl_Type_uintImage1D,
  alias_gl_Type_uintImage2D,
  alias_gl_Type_uintImage3D,
  alias_gl_Type_uintImage2DRect,
  alias_gl_Type_uintImageCube,
  alias_gl_Type_uintImageBuffer,
  alias_gl_Type_uintImage1DArray,
  alias_gl_Type_uintImage2DArray,
  alias_gl_Type_uintImageCubeArray,
  alias_gl_Type_uintImage2DMultisample,
  alias_gl_Type_uintImage2DMultisampleArray,
  alias_gl_Type_atomicUint,
  alias_gl_Type_inlineStructure,
  alias_gl_Type_shaderStorageBuffer
};

struct alias_gl_UniformData {
  const void *pointer;
  bool transpose;
  const struct alias_gl_Buffer *buffer;
  union {
    float _float;
    float vec[4];
    float mat[16];

    int32_t _int;
    int32_t ivec[4];
    int32_t imat[16];

    uint32_t uint;
    uint32_t uvec[4];
    uint32_t umat[16];
  };
};

struct alias_gl_ShaderSnippet {
  const struct alias_gl_ShaderSnippet *
    requires[8];
  uint32_t flags;
  const char *code;

  /* internal */
  uint32_t structure_size;
  uint32_t emit_index;
};

struct alias_gl_Shader {
  const struct alias_gl_ShaderSnippet *
    requires[8];
  const char *code;

  /* internal */
  uint32_t object;
};

struct alias_gl_ShaderResource {
  enum alias_gl_Type type;
  const char *name;
  ssize_t count;
  union {
    struct {
      void (*prepare)(void);
      uint32_t prepare_draw_index;
      struct alias_gl_UniformData data;
    } uniform;
    struct {
      struct alias_gl_ShaderSnippet *snippet;
      union {
        struct alias_gl_Buffer *buffer;
        struct alias_gl_Buffer *buffers[ALIAS_GL_MAX_BUFFERS];
      };
    } block;
  };
};

// --------------------------------------------------------------------------------------------------------------------
// state setup for draw/compute
// - interface defined in C ensures GLSL and C expect the same thing

#define ALIAS_GL_PIPILINE_STATE_FIELDS                                                                                 \
  struct {                                                                                                             \
    uint32_t stage_bits;                                                                                               \
    enum alias_gl_Type type;                                                                                           \
    const char *name;                                                                                                  \
    ssize_t count;                                                                                                     \
    const struct alias_gl_ShaderSnippet *struture;                                                                     \
  } uniform[ALIAS_GL_MAX_UNIFORMS];                                                                                    \
  struct {                                                                                                             \
    uint32_t stage_bits;                                                                                               \
    struct alias_gl_ShaderResource *resource;                                                                          \
  } global[ALIAS_GL_MAX_UNIFORMS];                                                                                     \
  struct {                                                                                                             \
    uint32_t stage_bits;                                                                                               \
    enum alias_gl_Type type;                                                                                           \
    const char *name;                                                                                                  \
  } image[ALIAS_GL_MAX_IMAGES];                                                                                        \
  /* internal */                                                                                                       \
  uint32_t program_object;

struct alias_gl_PipelineState {
  ALIAS_GL_PIPILINE_STATE_FIELDS
};

#define ALIAS_GL_STAGE_FIELDS                                                                                          \
  const char *source;                                                                                                  \
  /* internal */                                                                                                       \
  uint32_t shader_object;

struct alias_gl_VertexStage {
  ALIAS_GL_STAGE_FIELDS

  int32_t primitive;

  struct {
    int32_t binding;
    alias_memory_Format format;
    int32_t size;
    const char *name;
    uint32_t offset;
  } attribute[ALIAS_GL_MAX_ATTRIBUTES];

  struct {
    ssize_t stride;
    uint32_t divisor;
  } binding[ALIAS_GL_MAX_BINDINGS];

  // internal
  uint32_t vertex_array_object;
};

struct alias_gl_DrawState {
  ALIAS_GL_PIPILINE_STATE_FIELDS

  uint32_t primitive;

  struct {
    uint32_t binding;
    alias_memory_Format format;
    int32_t size;
    const char *name;
    uint32_t offset;
  } attribute[ALIAS_GL_MAX_ATTRIBUTES];

  struct {
    ssize_t stride;
    uint32_t divisor;
  } binding[ALIAS_GL_MAX_BINDINGS];

  const struct alias_gl_Shader *vertex_shader;

  bool depth_test_enable;
  bool depth_mask;
  float depth_range_min;
  float depth_range_max;

  const struct alias_gl_Shader *fragment_shader;

  bool blend_enable;
  uint32_t blend_src_factor;
  uint32_t blend_dst_factor;

  /* internal */
  uint32_t vertex_shader_object;
  uint32_t fragment_shader_object;
  uint32_t vertex_array_object;
};

struct alias_gl_ComputeState {
  ALIAS_GL_PIPILINE_STATE_FIELDS

  const struct alias_gl_Shader *shader;

  uint32_t local_group_x;
  uint32_t local_group_y;
  uint32_t local_group_z;

  /* internal */
  uint32_t shader_object;
};

// --------------------------------------------------------------------------------------------------------------------
// asset

struct alias_gl_Buffer {
  enum {
    alias_gl_Buffer_static,    // never changes, lives on the GPU
    alias_gl_Buffer_temporary, // The buffer used to send information from the CPU to GPU once
    alias_gl_Buffer_gpu,       // only lives on the GPU
    alias_gl_Buffer_cpu,       // A buffer persantly mapped and bound, updated by the CPU many times
  } kind;
  uint32_t buffer;
  ssize_t size;
  void *mapping;
  uint32_t offset;
  bool dirty;
};

// --------------------------------------------------------------------------------------------------------------------
// asset reference collection needed for a draw/compute

#define ALIAS_GL_PIPILINE_ASSETS_FIELDS                                                                                \
  uint32_t image[ALIAS_GL_MAX_IMAGES];                                                                                 \
  struct alias_gl_UniformData uniforms[ALIAS_GL_MAX_UNIFORMS];

struct alias_gl_PipelineAssets {
  ALIAS_GL_PIPILINE_ASSETS_FIELDS
};

struct alias_gl_DrawAssets {
  ALIAS_GL_PIPILINE_ASSETS_FIELDS

  const struct alias_gl_Buffer *element_buffer;
  uint32_t element_buffer_offset;

  const struct alias_gl_Buffer *vertex_buffers[ALIAS_GL_MAX_BINDINGS];
};

struct alias_gl_ComputeAssets {
  ALIAS_GL_PIPILINE_ASSETS_FIELDS
};

// --------------------------------------------------------------------------------------------------------------------
// draw
void alias_gl_initializeDrawState(const struct alias_gl_DrawState *state);
uint32_t alias_gl_applyDrawState(const struct alias_gl_DrawState *state);
uint32_t alias_gl_applyDrawAssets(const struct alias_gl_DrawState *state, const struct alias_gl_DrawAssets *assets);

void alias_gl_drawArrays(const struct alias_gl_DrawState *state, const struct alias_gl_DrawAssets *assets,
                         int32_t first, ssize_t count, ssize_t instancecount, uint32_t baseinstance);

void alias_gl_drawElements(const struct alias_gl_DrawState *state, const struct alias_gl_DrawAssets *assets,
                           ssize_t count, ssize_t instancecount, int32_t basevertex, uint32_t baseinstance);

void alias_gl_drawElementsIndirect(const struct alias_gl_DrawState *state, const struct alias_gl_DrawAssets *assets,
                                   const struct alias_gl_Buffer *indirect, ssize_t indirect_offset);

// --------------------------------------------------------------------------------------------------------------------
// compute
void alias_gl_initializeComputeState(const struct alias_gl_ComputeState *state);
uint32_t alias_gl_applyComputeState(const struct alias_gl_ComputeState *state);
uint32_t alias_gl_applyComputeAssets(const struct alias_gl_ComputeState *state,
                                     const struct alias_gl_ComputeAssets *assets);

void alias_gl_compute(const struct alias_gl_ComputeState *state, const struct alias_gl_ComputeAssets *assets,
                      uint32_t num_groups_x, uint32_t num_groups_y, uint32_t num_groups_z);

void alias_gl_computeIndirect(const struct alias_gl_ComputeState *state, const struct alias_gl_ComputeAssets *assets,
                              const struct alias_gl_Buffer *indirect, ssize_t indirect_offset);

// --------------------------------------------------------------------------------------------------------------------
// resource data
struct alias_gl_Buffer alias_gl_allocateStaticBuffer(uint32_t type, ssize_t size, const void *data);

struct alias_gl_Buffer alias_gl_allocateTemporaryBuffer(uint32_t type, ssize_t size);

struct alias_gl_Buffer alias_gl_allocateTemporaryBufferFrom(uint32_t type, ssize_t size, const void *data);

void *alias_gl_updateBufferBegin(const struct alias_gl_Buffer *buffer, intptr_t offset, ssize_t size);
void alias_gl_updateBufferEnd(const struct alias_gl_Buffer *buffer, intptr_t offset, ssize_t size);

uint32_t alias_gl_flushBuffer(const struct alias_gl_Buffer *buffer, uint32_t read_barrier_bits);

void alias_gl_freeBuffer(const struct alias_gl_Buffer *buffer);

void alias_gl_resetTemporaryBuffers(void);

void alias_gl_temporaryBufferStats(uint32_t type, uint32_t *total_allocated, uint32_t *used);

void alias_gl_destroyBuffer(const struct alias_gl_Buffer *buffer);

// --------------------------------------------------------------------------------------------------------------------
// resource
void alias_gl_ShaderResource_prepare(const struct alias_gl_ShaderResource *resource);

// --------------------------------------------------------------------------------------------------------------------
#define ALIAS_GL_SNIPPET_REQUIRE_require(NAME) &alias_gl_##NAME##_snippet,
#define ALIAS_GL_SNIPPET_REQUIRE_flag(FLAG)
#define ALIAS_GL_SNIPPET_REQUIRE_code(CODE)
#define ALIAS_GL_SNIPPET_REQUIRE_string(CODE)
#define ALIAS_GL_SNIPPET_REQUIRE_(ITEM) ALIAS_CPP_CAT(ALIAS_GL_SNIPPET_REQUIRE_, ITEM)

#define ALIAS_GL_SNIPPET_CODE_require(NAME)
#define ALIAS_GL_SNIPPET_CODE_flag(FLAG)
#define ALIAS_GL_SNIPPET_CODE_code(CODE) #CODE
#define ALIAS_GL_SNIPPET_CODE_string(CODE) CODE
#define ALIAS_GL_SNIPPET_CODE_(ITEM) ALIAS_CPP_CAT(ALIAS_GL_SNIPPET_CODE_, ITEM)

#define ALIAS_GL_SNIPPET_FLAG_require(NAME)
#define ALIAS_GL_SNIPPET_FLAG_flag(FLAG) | (ALIAS_GL_ ## FLAG)
#define ALIAS_GL_SNIPPET_FLAG_code(CODE) 
#define ALIAS_GL_SNIPPET_FLAG_string(CODE) 
#define ALIAS_GL_SNIPPET_FLAG_(ITEM) ALIAS_CPP_CAT(ALIAS_GL_SNIPPET_FLAG_, ITEM)

#define ALIAS_GL_DECLARE_SNIPPET(NAME, ...) extern struct alias_gl_ShaderSnippet alias_gl_##NAME##_snippet;

#define ALIAS_GL_IMPL_SNIPPET(NAME, ...)                                                    \
  struct alias_gl_ShaderSnippet alias_gl_##NAME##_snippet = {                               \
      .requires = {ALIAS_CPP_EVAL(ALIAS_CPP_MAP(ALIAS_GL_SNIPPET_REQUIRE_, __VA_ARGS__))},  \
      .flags = 0 ALIAS_CPP_EVAL(ALIAS_CPP_MAP(ALIAS_GL_SNIPPET_FLAG_, __VA_ARGS__)),        \
      .code = ALIAS_CPP_EVAL(ALIAS_CPP_MAP(ALIAS_GL_SNIPPET_CODE_, __VA_ARGS__))};

#define ALIAS_GL_SNIPPET(NAME, ...) ALIAS_GL_IMPL_SNIPPET(NAME, __VA_ARGS__)

// --------------------------------------------------------------------------------------------------------------------
#define ALIAS_GL_SHADER_REQUIRE_require(NAME) &alias_gl_##NAME##_snippet,
#define ALIAS_GL_SHADER_REQUIRE_code(CODE)
#define ALIAS_GL_SHADER_REQUIRE_string(CODE)
#define ALIAS_GL_SHADER_REQUIRE_main(CODE)
#define ALIAS_GL_SHADER_REQUIRE_(ITEM) ALIAS_CPP_CAT(ALIAS_GL_SHADER_REQUIRE_, ITEM)

#define ALIAS_GL_SHADER_CODE_require(NAME)
#define ALIAS_GL_SHADER_CODE_code(CODE) #CODE
#define ALIAS_GL_SHADER_CODE_string(CODE) CODE
#define ALIAS_GL_SHADER_CODE_main(CODE) "void main() {" #CODE "}"
#define ALIAS_GL_SHADER_CODE_(ITEM) ALIAS_CPP_CAT(ALIAS_GL_SHADER_CODE_, ITEM)

#define ALIAS_GL_SHADER(NAME, ...)                                                                                      \
  static struct alias_gl_Shader NAME##_shader = {                                                                            \
      .requires = {ALIAS_CPP_EVAL(ALIAS_CPP_MAP(ALIAS_GL_SHADER_REQUIRE_, __VA_ARGS__))},                               \
      .code = ALIAS_CPP_EVAL(ALIAS_CPP_MAP(ALIAS_GL_SHADER_CODE_, __VA_ARGS__))};

// --------------------------------------------------------------------------------------------------------------------
#define ALIAS_GL_ALIGNED(X) __attribute__((aligned(X)))

#define ALIAS_GL_STRUCT_TO_C_require(NAME)
#define ALIAS_GL_STRUCT_TO_C_unorm8(NAME) uint8_t NAME[4] ALIAS_GL_ALIGNED(4);     // uint (4, 4) -> float
#define ALIAS_GL_STRUCT_TO_C_unorm8x2(NAME) uint8_t NAME[4] ALIAS_GL_ALIGNED(4);   // uint (4, 4) -> vec2
#define ALIAS_GL_STRUCT_TO_C_unorm8x3(NAME) uint8_t NAME[4] ALIAS_GL_ALIGNED(4);   // uint (4, 4) -> vec3
#define ALIAS_GL_STRUCT_TO_C_unorm8x4(NAME) uint8_t NAME[4] ALIAS_GL_ALIGNED(4);   // uint (4, 4) -> vec4
#define ALIAS_GL_STRUCT_TO_C_unorm16(NAME) uint16_t NAME[2] ALIAS_GL_ALIGNED(4);   // uint (4, 4) -> float
#define ALIAS_GL_STRUCT_TO_C_unorm16x2(NAME) uint16_t NAME[2] ALIAS_GL_ALIGNED(4); // uint (4, 4) -> vec2
#define ALIAS_GL_STRUCT_TO_C_unorm16x3(NAME) uint16_t NAME[4] ALIAS_GL_ALIGNED(8); // uvec2 (8, 8) -> vec3
#define ALIAS_GL_STRUCT_TO_C_unorm16x4(NAME) uint16_t NAME[4] ALIAS_GL_ALIGNED(8); // uvec2 (8, 8) -> vec4
#define ALIAS_GL_STRUCT_TO_C_snorm16(NAME) int16_t NAME[2] ALIAS_GL_ALIGNED(4);    // uint (4, 4) -> float
#define ALIAS_GL_STRUCT_TO_C_snorm16x2(NAME) int16_t NAME[2] ALIAS_GL_ALIGNED(4);  // uint (4, 4) -> vec2
#define ALIAS_GL_STRUCT_TO_C_snorm16x3(NAME) int16_t NAME[4] ALIAS_GL_ALIGNED(8);  // uvec2 (8, 8) -> vec3
#define ALIAS_GL_STRUCT_TO_C_snorm16x4(NAME) int16_t NAME[4] ALIAS_GL_ALIGNED(8);  // uvec2 (8, 8) -> vec4
#define ALIAS_GL_STRUCT_TO_C_uint32(NAME) uint32_t NAME ALIAS_GL_ALIGNED(4);       // uint (4, 4) -> uint
#define ALIAS_GL_STRUCT_TO_C_int32(NAME) int32_t NAME ALIAS_GL_ALIGNED(4);         // int (4, 4) -> int
#define ALIAS_GL_STRUCT_TO_C_float32(NAME) float NAME ALIAS_GL_ALIGNED(4);         // float (4, 4) -> float
#define ALIAS_GL_STRUCT_TO_C_float32x2(NAME) float NAME[2] ALIAS_GL_ALIGNED(8);    // vec2 (8, 8) -> vec2
#define ALIAS_GL_STRUCT_TO_C_float32x3(NAME) float NAME[3] ALIAS_GL_ALIGNED(16);   // vec3 (16, 16) -> vec3
#define ALIAS_GL_STRUCT_TO_C_float32x4(NAME) float NAME[4] ALIAS_GL_ALIGNED(16);   // vec4 (16, 16) -> vec4
#define ALIAS_GL_STRUCT_TO_C_struct(TYPE, NAME) struct alias_gl_##TYPE NAME;
#define ALIAS_GL_STRUCT_TO_C_unsized_array(TYPE)
#define ALIAS_GL_STRUCT_TO_C_(ITEM) ALIAS_CPP_CAT(ALIAS_GL_STRUCT_TO_C_, ITEM)
#define ALIAS_GL_STRUCT_TO_C(NAME, ...)                                                                                 \
  struct alias_gl_##NAME {                                                                                                   \
    ALIAS_CPP_EVAL(ALIAS_CPP_MAP(ALIAS_GL_STRUCT_TO_C_, __VA_ARGS__))                                                   \
  };

#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_require(NAME) "//"
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_unorm8(NAME) "uint " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_unorm8x2(NAME) "uint " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_unorm8x3(NAME) "uint " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_unorm8x4(NAME) "uint " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_unorm16(NAME) "uint " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_unorm16x2(NAME) "uint " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_unorm16x3(NAME) "uvec2 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_unorm16x4(NAME) "uvec2 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_snorm16(NAME) "uint " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_snorm16x2(NAME) "uint " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_snorm16x3(NAME) "uvec2 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_snorm16x4(NAME) "uvec2 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_uint32(NAME) "uint " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_int32(NAME) "int " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_float32(NAME) "float " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_float32x2(NAME) "vec2 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_float32x3(NAME) "vec3 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_float32x4(NAME) "vec4 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_struct(TYPE, NAME) #TYPE "Packed " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACKED_(ITEM) "  " ALIAS_CPP_CAT(ALIAS_GL_STRUCT_TO_GLSL_PACKED_, ITEM) ";\n"

#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_require(NAME) "//"
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_unorm8(NAME) "float " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_unorm8x2(NAME) "vec2 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_unorm8x3(NAME) "vec3 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_unorm8x4(NAME) "vec4 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_unorm16(NAME) "float " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_unorm16x2(NAME) "vec2 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_unorm16x3(NAME) "vec3 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_unorm16x4(NAME) "vec4 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_snorm16(NAME) "float " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_snorm16x2(NAME) "vec2 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_snorm16x3(NAME) "vec3 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_snorm16x4(NAME) "vec4 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_uint32(NAME) "uint " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_int32(NAME) "int " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_float32(NAME) "float " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_float32x2(NAME) "vec2 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_float32x3(NAME) "vec3 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_float32x4(NAME) "vec4 " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_struct(TYPE, NAME) #TYPE " " #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_(ITEM) "  " ALIAS_CPP_CAT(ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_, ITEM) ";\n"

#define ALIAS_GL_STRUCT_TO_GLSL_PACK_require(NAME) "//"
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_unorm8(NAME) "packUnorm4x8(vec4(unpack." #NAME ", 0, 0, 0))"
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_unorm8x2(NAME) "packUnorm4x8(vec4(unpack." #NAME ", 0, 0))"
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_unorm8x3(NAME) "packUnorm4x8(vec4(unpack." #NAME ", 0))"
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_unorm8x4(NAME) "packUnorm4x8(unpack." #NAME ")"
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_unorm16(NAME) "packUnorm2x16(vec2(unpack." #NAME ", 0))"
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_unorm16x2(NAME) "packUnorm2x16(unpack." #NAME ")"
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_unorm16x3(NAME)                                                                    \
  "uvec2(packUnorm2x16(unpack." #NAME ".xy), packUnorm2x16(vec2(unpack." #NAME ".z, 0)))"
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_unorm16x4(NAME)                                                                    \
  "uvec2(packUnorm2x16(unpack." #NAME ".xy), packUnorm2x16(unpack." #NAME ".zw))"
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_snorm16(NAME) "packSnorm2x16(vec2(unpack." #NAME ", 0))"
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_snorm16x2(NAME) "packSnorm2x16(unpack." #NAME ")"
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_snorm16x3(NAME)                                                                    \
  "uvec2(packSnorm2x16(unpack." #NAME ".xy), packSnorm2x16(vec2(unpack." #NAME ".z, 0)))"
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_snorm16x4(NAME)                                                                    \
  "uvec2(packSnorm2x16(unpack." #NAME ".xy), packSnorm2x16(unpack." #NAME ".zw))"
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_uint32(NAME) "unpack." #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_int32(NAME) "unpack." #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_float32(NAME) "unpack." #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_float32x2(NAME) "unpack." #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_float32x3(NAME) "unpack." #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_float32x4(NAME) "unpack." #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_struct(TYPE, NAME) #TYPE "_pack(unpack." #NAME ")"
#define ALIAS_GL_STRUCT_TO_GLSL_PACK_(ITEM) ",\n  " ALIAS_CPP_CAT(ALIAS_GL_STRUCT_TO_GLSL_PACK_, ITEM)

#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_require(NAME) "//"
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_unorm8(NAME) "unpackUnorm4x8(pack." #NAME ").x"
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_unorm8x2(NAME) "unpackUnorm4x8(pack." #NAME ").xy"
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_unorm8x3(NAME) "unpackUnorm4x8(pack." #NAME ").xyz"
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_unorm8x4(NAME) "unpackUnorm4x8(pack." #NAME ")"
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_unorm16(NAME) "unpackUnorm2x16(pack." #NAME ").x"
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_unorm16x2(NAME) "unpackUnorm2x16(pack." #NAME ")"
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_unorm16x3(NAME)                                                                  \
  "vec3(unpackUnorm2x16(pack." #NAME ".x), unpackUnorm2x16(pack." #NAME ".y).x)"
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_unorm16x4(NAME)                                                                  \
  "vec4(unpackUnorm2x16(pack." #NAME ".x), unpackUnorm2x16(pack." #NAME ".y))"
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_snorm16(NAME) "unpackSnorm2x16(pack." #NAME ")"
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_snorm16x2(NAME) "unpackSnorm2x16(pack." #NAME ")"
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_snorm16x3(NAME)                                                                  \
  "vec3(unpackSnorm2x16(pack." #NAME ".x), unpackSnorm2x16(pack." #NAME ".y).x)"
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_snorm16x4(NAME)                                                                  \
  "vec4(unpackSnorm2x16(pack." #NAME ".x), unpackSnorm2x16(pack." #NAME ".y))"
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_uint32(NAME) "pack." #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_int32(NAME) "pack." #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_float32(NAME) "pack." #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_float32x2(NAME) "pack." #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_float32x3(NAME) "pack." #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_float32x4(NAME) "pack." #NAME
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_struct(TYPE, NAME) #TYPE "_unpack(pack." #NAME ")"
#define ALIAS_GL_STRUCT_TO_GLSL_UNPACK_(ITEM) ",\n  " ALIAS_CPP_CAT(ALIAS_GL_STRUCT_TO_GLSL_UNPACK_, ITEM)

// clang-format off
#define ALIAS_GL_STRUCT_TO_GLSL(NAME, ...)                                                                              \
  static const char alias_gl_##NAME##_glsl[] =                                                                               \
    "struct " #NAME " {\n" ALIAS_CPP_EVAL(ALIAS_CPP_MAP(ALIAS_GL_STRUCT_TO_GLSL_UNPACKED_, __VA_ARGS__)) "};\n"         \
    "struct " #NAME "Packed {\n" ALIAS_CPP_EVAL(ALIAS_CPP_MAP(ALIAS_GL_STRUCT_TO_GLSL_PACKED_, __VA_ARGS__)) "};\n"     \
    #NAME "Packed " #NAME "_pack(in " #NAME " unpack) {\n"                                                             \
    "  return " #NAME "Packed(//"                                                                                      \
    ALIAS_CPP_EVAL(ALIAS_CPP_MAP(ALIAS_GL_STRUCT_TO_GLSL_PACK_, __VA_ARGS__))                                           \
    "  );\n"                                                                                                           \
    "}\n"                                                                                                              \
    #NAME " " #NAME "_unpack(in " #NAME "Packed pack) {\n"                                                             \
    "  return " #NAME "(//"                                                                                            \
    ALIAS_CPP_EVAL(ALIAS_CPP_MAP(ALIAS_GL_STRUCT_TO_GLSL_UNPACK_, __VA_ARGS__))                                         \
    "  );\n"                                                                                                           \
    "}\n";
// clang-format onX

#define ALIAS_GL_STRUCT_TO_DESC_require(NAME) &alias_gl_##NAME##_snippet,
#define ALIAS_GL_STRUCT_TO_DESC_unorm8(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_unorm8x2(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_unorm8x3(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_unorm8x4(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_unorm16(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_unorm16x2(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_unorm16x3(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_unorm16x4(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_snorm16(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_snorm16x2(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_snorm16x3(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_snorm16x4(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_uint32(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_int32(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_float32(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_float32x2(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_float32x3(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_float32x4(NAME)
#define ALIAS_GL_STRUCT_TO_DESC_struct(TYPE, NAME)
#define ALIAS_GL_STRUCT_TO_DESC_unsized_array(TYPE)
#define ALIAS_GL_STRUCT_TO_DESC_(ITEM) ALIAS_CPP_CAT(ALIAS_GL_STRUCT_TO_DESC_, ITEM)
#define ALIAS_GL_STRUCT_TO_DESC(NAME, ...)                                                                              \
  struct alias_gl_ShaderSnippet alias_gl_##NAME##_snippet = {                                                                      \
      .requires = {ALIAS_CPP_EVAL(ALIAS_CPP_MAP(ALIAS_GL_STRUCT_TO_DESC_, __VA_ARGS__)) NULL},                          \
      .code = alias_gl_##NAME##_glsl,                                                                                        \
      .structure_size = sizeof(struct alias_gl_##NAME)};

#define ALIAS_GL_DECLARE_STRUCT(NAME, ...)                                                                              \
  ALIAS_GL_STRUCT_TO_C(NAME, __VA_ARGS__)                                                                               \
  extern struct alias_gl_ShaderSnippet alias_gl_##NAME##_snippet;

#define ALIAS_GL_IMPL_STRUCT(NAME, ...)                                                                                 \
  ALIAS_GL_STRUCT_TO_GLSL(NAME, __VA_ARGS__)                                                                            \
  ALIAS_GL_STRUCT_TO_DESC(NAME, __VA_ARGS__)

#define ALIAS_GL_STRUCT(NAME, ...)                                                                                      \
  ALIAS_GL_STRUCT_TO_C(NAME, __VA_ARGS__)                                                                               \
  ALIAS_GL_STRUCT_TO_GLSL(NAME, __VA_ARGS__)                                                                            \
  ALIAS_GL_STRUCT_TO_DESC(NAME, __VA_ARGS__)

ALIAS_GL_DECLARE_STRUCT(DrawArraysIndirectCommand, uint32(count), uint32(instance_count), uint32(first),
                       uint32(base_instance))

ALIAS_GL_DECLARE_STRUCT(DrawElementsIndirectCommand, uint32(count), uint32(instance_count), uint32(first_index),
                       uint32(base_vertex), uint32(base_instance))

ALIAS_GL_DECLARE_STRUCT(DispatchIndirectCommand, uint32(num_groups_x), uint32(num_groups_y), uint32(num_groups_z))

#define ALIAS_GL_BLOCK_TO_C(NAME, ...) ALIAS_GL_STRUCT_TO_C(NAME, __VA_ARGS__)
#define ALIAS_GL_BLOCK_TO_DESC(NAME, ...) ALIAS_GL_STRUCT_TO_DESC(NAME, __VA_ARGS__)

#define ALIAS_GL_CAT(A, ...) ALIAS_GL_CAT_(A, ##__VA_ARGS__)
#define ALIAS_GL_CAT_(A, ...) A##__VA_ARGS__

#define ALIAS_GL_BLOCK_TO_GLSL_require(NAME) "  //"
#define ALIAS_GL_BLOCK_TO_GLSL_uint32(NAME) "  uint " #NAME
#define ALIAS_GL_BLOCK_TO_GLSL_int32(NAME) "  int " #NAME
#define ALIAS_GL_BLOCK_TO_GLSL_float32(NAME) "  float " #NAME
#define ALIAS_GL_BLOCK_TO_GLSL_float32x2(NAME) "  vec2 " #NAME
#define ALIAS_GL_BLOCK_TO_GLSL_float32x3(NAME) "  vec3 " #NAME
#define ALIAS_GL_BLOCK_TO_GLSL_float32x4(NAME) "  vec4 " #NAME
#define ALIAS_GL_BLOCK_TO_GLSL_struct(TYPE, NAME) "  " #TYPE " " #NAME
#define ALIAS_GL_BLOCK_TO_GLSL_unsized_array(TYPE) ALIAS_GL_CAT(ALIAS_GL_BLOCK_TO_GLSL_, TYPE) "[]"
#define ALIAS_GL_BLOCK_TO_GLSL_(ITEM) ALIAS_CPP_CAT(ALIAS_GL_BLOCK_TO_GLSL_, ITEM) ";\n"

#define ALIAS_GL_BLOCK_TO_GLSL(NAME, ...)                                                                               \
  static const char alias_gl_##NAME##_glsl[] =                                                                               \
      "buffer " #NAME " {\n" ALIAS_CPP_EVAL(ALIAS_CPP_MAP(ALIAS_GL_BLOCK_TO_GLSL_, __VA_ARGS__)) "} ";

#define ALIAS_GL_DECLARE_BLOCK(NAME, ...)                                                                               \
  ALIAS_GL_BLOCK_TO_C(NAME, __VA_ARGS__)                                                                                \
  extern struct alias_gl_ShaderSnippet alias_gl_##NAME##_snippet;

#define ALIAS_GL_IMPL_BLOCK(NAME, ...)                                                                                  \
  ALIAS_GL_BLOCK_TO_GLSL(NAME, __VA_ARGS__)                                                                             \
  ALIAS_GL_BLOCK_TO_DESC(NAME, __VA_ARGS__)

#define ALIAS_GL_BLOCK(NAME, ...)                                                                                       \
  ALIAS_GL_BLOCK_TO_C(NAME, __VA_ARGS__)                                                                                \
  ALIAS_GL_BLOCK_TO_GLSL(NAME, __VA_ARGS__)                                                                             \
  ALIAS_GL_BLOCK_TO_DESC(NAME, __VA_ARGS__)

#endif