#include "qjs_sdl_gpu.h"
#include "jsffi.h"
#include "qjs_macros.h"

#include <SDL3/SDL_gpu.h>
#include "render.h"
#include "cell.h"
#include "sprite.h"

#include "stb_dxt.h"

// Global GPU device and window
static SDL_GPUDevice *global_gpu;
static SDL_Window *global_window;

// GPU Free functions
void SDL_GPUDevice_free(JSRuntime *rt, SDL_GPUDevice *d)
{
  SDL_DestroyGPUDevice(d);
}

void SDL_GPUCommandBuffer_free(JSRuntime *rt, SDL_GPUCommandBuffer *c)
{
}

void SDL_GPUComputePass_free(JSRuntime *rt, SDL_GPUComputePass *c) { }
void SDL_GPUCopyPass_free(JSRuntime *rt, SDL_GPUCopyPass *c) {  }
void SDL_GPURenderPass_free(JSRuntime *rt, SDL_GPURenderPass *c) {  }


// GPU Class definitions
#define GPURELEASECLASS(NAME) \
void SDL_GPU##NAME##_free(JSRuntime *rt, SDL_GPU##NAME *c) { \
  SDL_ReleaseGPU##NAME(global_gpu, c); } \
QJSCLASS(SDL_GPU##NAME,) \

QJSCLASS(SDL_GPUDevice,)

GPURELEASECLASS(Buffer)
GPURELEASECLASS(ComputePipeline)
GPURELEASECLASS(GraphicsPipeline)
GPURELEASECLASS(Sampler)
GPURELEASECLASS(Shader)
GPURELEASECLASS(Texture)
GPURELEASECLASS(TransferBuffer)
GPURELEASECLASS(Fence)

QJSCLASS(SDL_GPUCommandBuffer,)
QJSCLASS(SDL_GPUComputePass,)
QJSCLASS(SDL_GPUCopyPass,)
QJSCLASS(SDL_GPURenderPass,)

SDL_GPUGraphicsPipelineTargetInfo js2SDL_GPUGraphicsPipelineTargetInfo(JSContext *js, JSValue v)
{
  SDL_GPUGraphicsPipelineTargetInfo info = {0};
  return info;
}

SDL_GPUSampleCount js2SDL_GPUSampleCount(JSContext *js, JSValue v)
{
  int n = js2number(js,v);
  switch(n) {
    case 1: return SDL_GPU_SAMPLECOUNT_1;
    case 2: return SDL_GPU_SAMPLECOUNT_2;
    case 4: return SDL_GPU_SAMPLECOUNT_4;
    case 8: return SDL_GPU_SAMPLECOUNT_8;
  }
  return -1;
}

#define JS2ENUM(NAME, RETS, VALS)                            \
int js2##NAME(JSContext *js, JSValue v) {                    \
  if (JS_IsNull(v)) return 0;                           \
  const char *str = JS_ToCString(js, v);                     \
  int *rets = (RETS);                                        \
  const char **vals = (VALS);                                \
  /* Compute how many entries are in the arrays */           \
  int n = (int)(sizeof((RETS)) / sizeof((RETS)[0]));         \
  for(int i = 0; i < n; i++)                                 \
    if(!strcmp(vals[i], str)) {                              \
      JS_FreeCString(js, str);                               \
      return rets[i];                                        \
    }                                                        \
  JS_FreeCString(js, str);                                   \
  return 0;                                                 \
}

static int rets_SDL_GPUSwapchainComposition[] = {
  SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
  SDL_GPU_SWAPCHAINCOMPOSITION_SDR_LINEAR,
  SDL_GPU_SWAPCHAINCOMPOSITION_HDR_EXTENDED_LINEAR,
//  SDL_GPU_SWAPCHAINCOMPOSITION_HDR10_ST2084
};

static const char *vals_SDL_GPUSwapchainComposition[] = {
  "sdr",
  "linear",
  "hdr",
//  "hdr10"
};

JS2ENUM(SDL_GPUSwapchainComposition, rets_SDL_GPUSwapchainComposition, vals_SDL_GPUSwapchainComposition)

static int rets_SDL_FlipMode[] = {
  SDL_FLIP_NONE,
  SDL_FLIP_HORIZONTAL,
  SDL_FLIP_VERTICAL
};

static const char *vals_SDL_FlipMode[] = {
  "none",
  "horizontal",
  "vertical"
};

SDL_FColor js2SDL_FColor(JSContext *js, JSValue v)
{
  colorf color = js2color(js,v);
  return (SDL_FColor){color.r,color.g,color.b,color.a};
}

JS2ENUM(SDL_FlipMode, rets_SDL_FlipMode, vals_SDL_FlipMode)

/* -------------------------------------------------------
   1) SDL_GPUBlendFactor
   ------------------------------------------------------- */

static int rets_SDL_GPUBlendFactor[] = {
  SDL_GPU_BLENDFACTOR_INVALID,
  SDL_GPU_BLENDFACTOR_ZERO,
  SDL_GPU_BLENDFACTOR_ONE,
  SDL_GPU_BLENDFACTOR_SRC_COLOR,
  SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_COLOR,
  SDL_GPU_BLENDFACTOR_DST_COLOR,
  SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_COLOR,
  SDL_GPU_BLENDFACTOR_SRC_ALPHA,
  SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
  SDL_GPU_BLENDFACTOR_DST_ALPHA,
  SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_ALPHA,
  SDL_GPU_BLENDFACTOR_CONSTANT_COLOR,
  SDL_GPU_BLENDFACTOR_ONE_MINUS_CONSTANT_COLOR,
  SDL_GPU_BLENDFACTOR_SRC_ALPHA_SATURATE
};
static const char *vals_SDL_GPUBlendFactor[] = {
  "invalid",
  "zero",
  "one",
  "src_color",
  "one_minus_src_color",
  "dst_color",
  "one_minus_dst_color",
  "src_alpha",
  "one_minus_src_alpha",
  "dst_alpha",
  "one_minus_dst_alpha",
  "constant_color",
  "one_minus_constant_color",
  "src_alpha_saturate"
};

JS2ENUM(SDL_GPUBlendFactor, rets_SDL_GPUBlendFactor, vals_SDL_GPUBlendFactor)

/* -------------------------------------------------------
   2) SDL_GPUBlendOp
   ------------------------------------------------------- */
static int rets_SDL_GPUBlendOp[] = {
  SDL_GPU_BLENDOP_INVALID,
  SDL_GPU_BLENDOP_ADD,
  SDL_GPU_BLENDOP_SUBTRACT,
  SDL_GPU_BLENDOP_REVERSE_SUBTRACT,
  SDL_GPU_BLENDOP_MIN,
  SDL_GPU_BLENDOP_MAX
};
static const char *vals_SDL_GPUBlendOp[] = {
  "invalid",
  "add",
  "subtract",
  "reverse_subtract",
  "min",
  "max"
};

JS2ENUM(SDL_GPUBlendOp, rets_SDL_GPUBlendOp, vals_SDL_GPUBlendOp)

/* -------------------------------------------------------
   3) SDL_GPUCompareOp
   ------------------------------------------------------- */
static int rets_SDL_GPUCompareOp[] = {
  SDL_GPU_COMPAREOP_INVALID,
  SDL_GPU_COMPAREOP_NEVER,
  SDL_GPU_COMPAREOP_LESS,
  SDL_GPU_COMPAREOP_EQUAL,
  SDL_GPU_COMPAREOP_LESS_OR_EQUAL,
  SDL_GPU_COMPAREOP_GREATER,
  SDL_GPU_COMPAREOP_NOT_EQUAL,
  SDL_GPU_COMPAREOP_GREATER_OR_EQUAL,
  SDL_GPU_COMPAREOP_ALWAYS
};
static const char *vals_SDL_GPUCompareOp[] = {
  "invalid",
  "never",
  "less",
  "equal",
  "less_or_equal",
  "greater",
  "not_equal",
  "greater_or_equal",
  "always"
};

JS2ENUM(SDL_GPUCompareOp, rets_SDL_GPUCompareOp, vals_SDL_GPUCompareOp)

/* -------------------------------------------------------
   4) SDL_GPUCullMode
   ------------------------------------------------------- */
static int rets_SDL_GPUCullMode[] = {
  SDL_GPU_CULLMODE_NONE,
  SDL_GPU_CULLMODE_FRONT,
  SDL_GPU_CULLMODE_BACK
};
static const char *vals_SDL_GPUCullMode[] = {
  "none",
  "front",
  "back"
};

JS2ENUM(SDL_GPUCullMode, rets_SDL_GPUCullMode, vals_SDL_GPUCullMode)

/* -------------------------------------------------------
   5) SDL_GPUFillMode
   ------------------------------------------------------- */
static int rets_SDL_GPUFillMode[] = {
  SDL_GPU_FILLMODE_FILL,
  SDL_GPU_FILLMODE_LINE
};
static const char *vals_SDL_GPUFillMode[] = {
  "fill",
  "line"
};

JS2ENUM(SDL_GPUFillMode, rets_SDL_GPUFillMode, vals_SDL_GPUFillMode)

/* -------------------------------------------------------
   6) SDL_GPUFilter
   ------------------------------------------------------- */
static int rets_SDL_GPUFilter[] = {
  SDL_GPU_FILTER_NEAREST,
  SDL_GPU_FILTER_LINEAR
};
static const char *vals_SDL_GPUFilter[] = {
  "nearest",
  "linear"
};

JS2ENUM(SDL_GPUFilter, rets_SDL_GPUFilter, vals_SDL_GPUFilter)

/* -------------------------------------------------------
   7) SDL_GPUFrontFace
   ------------------------------------------------------- */
static int rets_SDL_GPUFrontFace[] = {
  SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE,
  SDL_GPU_FRONTFACE_CLOCKWISE
};
static const char *vals_SDL_GPUFrontFace[] = {
  "counter_clockwise",
  "clockwise"
};

JS2ENUM(SDL_GPUFrontFace, rets_SDL_GPUFrontFace, vals_SDL_GPUFrontFace)

/* -------------------------------------------------------
   8) SDL_GPULoadOp
   ------------------------------------------------------- */
static int rets_SDL_GPULoadOp[] = {
  SDL_GPU_LOADOP_LOAD,
  SDL_GPU_LOADOP_CLEAR,
  SDL_GPU_LOADOP_DONT_CARE
};
static const char *vals_SDL_GPULoadOp[] = {
  "load",
  "clear",
  "dont_care"
};

JS2ENUM(SDL_GPULoadOp, rets_SDL_GPULoadOp, vals_SDL_GPULoadOp)

/* -------------------------------------------------------
   9) SDL_GPUPresentMode
   ------------------------------------------------------- */
static int rets_SDL_GPUPresentMode[] = {
  SDL_GPU_PRESENTMODE_VSYNC,
  SDL_GPU_PRESENTMODE_IMMEDIATE,
  SDL_GPU_PRESENTMODE_MAILBOX
};
static const char *vals_SDL_GPUPresentMode[] = {
  "vsync",
  "immediate",
  "mailbox"
};

JS2ENUM(SDL_GPUPresentMode, rets_SDL_GPUPresentMode, vals_SDL_GPUPresentMode)

/* -------------------------------------------------------
   10) SDL_GPUPrimitiveType
   ------------------------------------------------------- */
static int rets_SDL_GPUPrimitiveType[] = {
  SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
  SDL_GPU_PRIMITIVETYPE_TRIANGLESTRIP,
  SDL_GPU_PRIMITIVETYPE_LINELIST,
  SDL_GPU_PRIMITIVETYPE_LINESTRIP,
  SDL_GPU_PRIMITIVETYPE_POINTLIST
};
static const char *vals_SDL_GPUPrimitiveType[] = {
  "triangle",
  "trianglestrip",
  "line",
  "linestrip",
  "point"
};

JS2ENUM(SDL_GPUPrimitiveType, rets_SDL_GPUPrimitiveType, vals_SDL_GPUPrimitiveType)

/* -------------------------------------------------------
   11) SDL_GPUSamplerAddressMode
   ------------------------------------------------------- */
static int rets_SDL_GPUSamplerAddressMode[] = {
  SDL_GPU_SAMPLERADDRESSMODE_REPEAT,
  SDL_GPU_SAMPLERADDRESSMODE_MIRRORED_REPEAT,
  SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE
};
static const char *vals_SDL_GPUSamplerAddressMode[] = {
  "repeat",
  "mirrored_repeat",
  "clamp"
};

JS2ENUM(SDL_GPUSamplerAddressMode, rets_SDL_GPUSamplerAddressMode, vals_SDL_GPUSamplerAddressMode)

/* -------------------------------------------------------
   12) SDL_GPUSamplerMipmapMode
   ------------------------------------------------------- */
static int rets_SDL_GPUSamplerMipmapMode[] = {
  SDL_GPU_SAMPLERMIPMAPMODE_NEAREST,
  SDL_GPU_SAMPLERMIPMAPMODE_LINEAR
};
static const char *vals_SDL_GPUSamplerMipmapMode[] = {
  "nearest",
  "linear"
};

JS2ENUM(SDL_GPUSamplerMipmapMode, rets_SDL_GPUSamplerMipmapMode, vals_SDL_GPUSamplerMipmapMode)

/* -------------------------------------------------------
   13) SDL_GPUStencilOp
   ------------------------------------------------------- */
static int rets_SDL_GPUStencilOp[] = {
  SDL_GPU_STENCILOP_INVALID,
  SDL_GPU_STENCILOP_KEEP,
  SDL_GPU_STENCILOP_ZERO,
  SDL_GPU_STENCILOP_REPLACE,
  SDL_GPU_STENCILOP_INCREMENT_AND_CLAMP,
  SDL_GPU_STENCILOP_DECREMENT_AND_CLAMP,
  SDL_GPU_STENCILOP_INVERT,
  SDL_GPU_STENCILOP_INCREMENT_AND_WRAP,
  SDL_GPU_STENCILOP_DECREMENT_AND_WRAP
};
static const char *vals_SDL_GPUStencilOp[] = {
  "invalid",
  "keep",
  "zero",
  "replace",
  "increment_and_clamp",
  "decrement_and_clamp",
  "invert",
  "increment_and_wrap",
  "decrement_and_wrap"
};

JS2ENUM(SDL_GPUStencilOp, rets_SDL_GPUStencilOp, vals_SDL_GPUStencilOp)

static int rets_SDL_GPUTextureType[] = {
    SDL_GPU_TEXTURETYPE_2D,         /**< The texture is a 2-dimensional image. */
    SDL_GPU_TEXTURETYPE_2D_ARRAY,   /**< The texture is a 2-dimensional array image. */
    SDL_GPU_TEXTURETYPE_3D,         /**< The texture is a 3-dimensional image. */
    SDL_GPU_TEXTURETYPE_CUBE,       /**< The texture is a cube image. */
    SDL_GPU_TEXTURETYPE_CUBE_ARRAY  /**< The texture is a cube array image. */
};

static const char *vals_SDL_GPUTextureType[] = {
  "2d",
  "2d array",
  "3d",
  "cube",
  "cube array"
};

JS2ENUM(SDL_GPUTextureType, rets_SDL_GPUTextureType, vals_SDL_GPUTextureType)

/* -------------------------------------------------------
   14) SDL_GPUStoreOp
   ------------------------------------------------------- */
static int rets_SDL_GPUStoreOp[] = {
  SDL_GPU_STOREOP_STORE,
  SDL_GPU_STOREOP_DONT_CARE,
  SDL_GPU_STOREOP_RESOLVE,
  SDL_GPU_STOREOP_RESOLVE_AND_STORE
};

static const char *vals_SDL_GPUStoreOp[] = {
  "store",
  "dont_care",
  "resolve",
  "resolve_and_store"
};

JS2ENUM(SDL_GPUStoreOp, rets_SDL_GPUStoreOp, vals_SDL_GPUStoreOp)

/* -------------------------------------------------------
   X) SDL_GPUTextureType
   ------------------------------------------------------- */
static int rets_SDL_GPUTextureFormat[] = {
    SDL_GPU_TEXTUREFORMAT_INVALID,

    /* Unsigned Normalized Float Color Formats */
    SDL_GPU_TEXTUREFORMAT_A8_UNORM,
    SDL_GPU_TEXTUREFORMAT_R8_UNORM,
    SDL_GPU_TEXTUREFORMAT_R8G8_UNORM,
    SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
    SDL_GPU_TEXTUREFORMAT_R16_UNORM,
    SDL_GPU_TEXTUREFORMAT_R16G16_UNORM,
    SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM,
    SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM,
    SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM,
    SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM,
    SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM,
    SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM,
    /* Compressed Unsigned Normalized Float Color Formats */
    SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM,
    SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM,
    SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM,
    SDL_GPU_TEXTUREFORMAT_BC4_R_UNORM,
    SDL_GPU_TEXTUREFORMAT_BC5_RG_UNORM,
    SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM,
    /* Compressed Signed Float Color Formats */
    SDL_GPU_TEXTUREFORMAT_BC6H_RGB_FLOAT,
    /* Compressed Unsigned Float Color Formats */
    SDL_GPU_TEXTUREFORMAT_BC6H_RGB_UFLOAT,
    /* Signed Normalized Float Color Formats  */
    SDL_GPU_TEXTUREFORMAT_R8_SNORM,
    SDL_GPU_TEXTUREFORMAT_R8G8_SNORM,
    SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM,
    SDL_GPU_TEXTUREFORMAT_R16_SNORM,
    SDL_GPU_TEXTUREFORMAT_R16G16_SNORM,
    SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM,
    /* Signed Float Color Formats */
    SDL_GPU_TEXTUREFORMAT_R16_FLOAT,
    SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT,
    SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT,
    SDL_GPU_TEXTUREFORMAT_R32_FLOAT,
    SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT,
    SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT,
    /* Unsigned Float Color Formats */
    SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT,
    /* Unsigned Integer Color Formats */
    SDL_GPU_TEXTUREFORMAT_R8_UINT,
    SDL_GPU_TEXTUREFORMAT_R8G8_UINT,
    SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT,
    SDL_GPU_TEXTUREFORMAT_R16_UINT,
    SDL_GPU_TEXTUREFORMAT_R16G16_UINT,
    SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT,
    SDL_GPU_TEXTUREFORMAT_R32_UINT,
    SDL_GPU_TEXTUREFORMAT_R32G32_UINT,
    SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT,
    /* Signed Integer Color Formats */
    SDL_GPU_TEXTUREFORMAT_R8_INT,
    SDL_GPU_TEXTUREFORMAT_R8G8_INT,
    SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT,
    SDL_GPU_TEXTUREFORMAT_R16_INT,
    SDL_GPU_TEXTUREFORMAT_R16G16_INT,
    SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT,
    SDL_GPU_TEXTUREFORMAT_R32_INT,
    SDL_GPU_TEXTUREFORMAT_R32G32_INT,
    SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT,
    /* SRGB Unsigned Normalized Color Formats */
    SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB,
    SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB,
    /* Compressed SRGB Unsigned Normalized Color Formats */
    SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB,
    SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB,
    SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB,
    SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB,
    /* Depth Formats */
    SDL_GPU_TEXTUREFORMAT_D16_UNORM,
    SDL_GPU_TEXTUREFORMAT_D24_UNORM,
    SDL_GPU_TEXTUREFORMAT_D32_FLOAT,
    SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT,
    SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT,
    /* Compressed ASTC Normalized Float Color Formats*/
    SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM,
    SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM,
    SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM,
    SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM,
    SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM,
    SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM,
    SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM,
    SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM,
    SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM,
    SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM,
    SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM,
    SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM,
    SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM,
    SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM,
    /* Compressed SRGB ASTC Normalized Float Color Formats*/
    SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM_SRGB,
    SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM_SRGB,
    SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM_SRGB,
    SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM_SRGB,
    SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM_SRGB,
    SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM_SRGB,
    SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM_SRGB,
    SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM_SRGB,
    SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM_SRGB,
    SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM_SRGB,
    SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM_SRGB,
    SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM_SRGB,
    SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM_SRGB,
    SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM_SRGB,
    /* Compressed ASTC Signed Float Color Formats*/
    SDL_GPU_TEXTUREFORMAT_ASTC_4x4_FLOAT,
    SDL_GPU_TEXTUREFORMAT_ASTC_5x4_FLOAT,
    SDL_GPU_TEXTUREFORMAT_ASTC_5x5_FLOAT,
    SDL_GPU_TEXTUREFORMAT_ASTC_6x5_FLOAT,
    SDL_GPU_TEXTUREFORMAT_ASTC_6x6_FLOAT,
    SDL_GPU_TEXTUREFORMAT_ASTC_8x5_FLOAT,
    SDL_GPU_TEXTUREFORMAT_ASTC_8x6_FLOAT,
    SDL_GPU_TEXTUREFORMAT_ASTC_8x8_FLOAT,
    SDL_GPU_TEXTUREFORMAT_ASTC_10x5_FLOAT,
    SDL_GPU_TEXTUREFORMAT_ASTC_10x6_FLOAT,
    SDL_GPU_TEXTUREFORMAT_ASTC_10x8_FLOAT,
    SDL_GPU_TEXTUREFORMAT_ASTC_10x10_FLOAT,
    SDL_GPU_TEXTUREFORMAT_ASTC_12x10_FLOAT,
    SDL_GPU_TEXTUREFORMAT_ASTC_12x12_FLOAT
};

static const char *vals_SDL_GPUTextureFormat[] = {
  "invalid",
  "a8",
  "r8",
  "rg8",
  "rgba8",
  "r16",
  "rg16",
  "rgba16",
  "r10g10b10a2",
  "b5g6r5",
  "b5g5r5a1",
  "b4g4r4a4",
  "b8g8r8a8",

  "bc1",
  "bc2",
  "bc3",
  "bc4",
  "bc5",
  "bc7",
  "bc6h float",
  "bc6h ufloat",

  "r8 snorm",
  "rg8 snorm",
  "rgba8 snorm",
  "r16 snorm",
  "rg16 snorm",
  "rgba16 snorm",

  "r16 float",
  "rg16 float",
  "rgba16 float",
  "r32 float",
  "rg32 float",
  "rgba32 float",

  "r11g11b10",

  "r8 uint",
  "rg8 uint",
  "rgba8 uint",
  "r16 uint",
  "rg16 uint",
  "rgba16 uint",
  "r32 uint",
  "rg32 uint",
  "rgba32 uint",

  "r8 int",
  "rg8 int",
  "rgba8 int",
  "r16 int",
  "rg16 int",
  "rgba16 int",
  "r32 int",
  "rg32 int",
  "rgba32 int",

  "rgba8 srgb",
  "b8g8r8a8 srgb",
  "bc1 srgb",
  "bc2 srgb",
  "bc3 srgb",
  "bc7 srgb",

  "d16",
  "d24",
  "d32 float",
  "d24 s8",
  "d32 float s8",

  "astc 4x4",
  "astc 5x4",
  "astc 5x5",
  "astc 6x5",
  "astc 6x6",
  "astc 8x5",
  "astc 8x6",
  "astc 8x8",
  "astc 10x5",
  "astc 10x6",
  "astc 10x8",
  "astc 10x10",
  "astc 12x10",
  "astc 12x12",

  "astc 4x4 srgb",
  "astc 5x4 srgb",
  "astc 5x5 srgb",
  "astc 6x5 srgb",
  "astc 6x6 srgb",
  "astc 8x5 srgb",
  "astc 8x6 srgb",
  "astc 8x8 srgb",
  "astc 10x5 srgb",
  "astc 10x6 srgb",
  "astc 10x8 srgb",
  "astc 10x10 srgb",
  "astc 12x10 srgb",
  "astc 12x12 srgb",

  "astc 4x4 float",
  "astc 5x4 float",
  "astc 5x5 float",
  "astc 6x5 float",
  "astc 6x6 float",
  "astc 8x5 float",
  "astc 8x6 float",
  "astc 8x8 float",
  "astc 10x5 float",
  "astc 10x6 float",
  "astc 10x8 float",
  "astc 10x10 float",
  "astc 12x10 float",
  "astc 12x12 float"
};

SDL_GPUColorTargetInfo js2SDL_GPUColorTargetInfo(JSContext *js, JSValue v)
{
  SDL_GPUColorTargetInfo info = {0};
  JS_GETPROP(js, info.texture, v, texture, SDL_GPUTexture)
  JS_GETPROP(js,info.mip_level,v,mip_level,number)
  JS_GETPROP(js,info.layer_or_depth_plane, v, layer, number)
  JS_GETPROP(js,info.load_op,v,load,SDL_GPULoadOp)
  JS_GETPROP(js,info.store_op,v,store,SDL_GPUStoreOp)
  JS_GETPROP(js,info.resolve_mip_level,v,resolve_mip_level,number)
  JS_GETPROP(js,info.resolve_layer,v,resolve_layer,number)
  JS_GETPROP(js,info.clear_color,v,clear,SDL_FColor)
  return info;
}

SDL_GPUTextureSamplerBinding js2SDL_GPUTextureSamplerBinding(JSContext *js, JSValue v)
{
  SDL_GPUTextureSamplerBinding b;
  JS_GETPROP(js, b.texture, v, texture, SDL_GPUTexture)
  JS_GETPROP(js, b.sampler, v, sampler, SDL_GPUSampler)
  return b;
}

JS2ENUM(SDL_GPUTextureFormat, rets_SDL_GPUTextureFormat, vals_SDL_GPUTextureFormat)

SDL_GPUColorTargetBlendState js2SDL_GPUColorTargetBlendState(JSContext *js, JSValue v)
{
  SDL_GPUColorTargetBlendState state = {0};
  JS_GETPROP(js,state.src_color_blendfactor,v,src_rgb,SDL_GPUBlendFactor);
  JS_GETPROP(js,state.dst_color_blendfactor,v,dst_rgb,SDL_GPUBlendFactor);
  JS_GETPROP(js,state.src_alpha_blendfactor,v,src_alpha,SDL_GPUBlendFactor);
  JS_GETPROP(js,state.dst_alpha_blendfactor,v,src_alpha,SDL_GPUBlendFactor);
  JS_GETPROP(js,state.color_blend_op,v,op_rgb,SDL_GPUBlendOp)
  JS_GETPROP(js,state.alpha_blend_op,v,op_alpha,SDL_GPUBlendOp)
  JS_GETPROP(js,state.enable_blend,v,enabled,bool)
  return state;
}

SDL_GPUShaderFormat js2SDL_GPUShaderFormat(JSContext *js, JSValue v)
{
  const char *format_str = JS_ToCString(js, v);
  SDL_GPUShaderFormat format = SDL_GPU_SHADERFORMAT_SPIRV; // default to SPIR-V
  if (format_str) {
    if (!strcmp(format_str, "private")) format = SDL_GPU_SHADERFORMAT_PRIVATE;
    else if (!strcmp(format_str, "spv")) format = SDL_GPU_SHADERFORMAT_SPIRV;
    else if (!strcmp(format_str, "dxbc")) format = SDL_GPU_SHADERFORMAT_DXBC;
    else if (!strcmp(format_str, "dxil")) format = SDL_GPU_SHADERFORMAT_DXIL;
    else if (!strcmp(format_str, "msl")) format = SDL_GPU_SHADERFORMAT_MSL;
    else if (!strcmp(format_str, "metallib")) format = SDL_GPU_SHADERFORMAT_METALLIB;
    JS_FreeCString(js, format_str);
  }
  return format;
}

SDL_GPUColorTargetDescription js2SDL_GPUColorTargetDescription(JSContext *js, JSValue v)
{
  SDL_GPUColorTargetDescription dsc = {0};
  JS_GETPROP(js,dsc.format,v,format,SDL_GPUTextureFormat)
  JS_GETPROP(js,dsc.blend_state,v,blend,SDL_GPUColorTargetBlendState)
  return dsc;
}

SDL_GPUStencilOpState js2SDL_GPUStencilOpState(JSContext *js, JSValue v)
{
  SDL_GPUStencilOpState state;
  memset(&state, 0, sizeof(state));

  JSValue compare_val = JS_GetPropertyStr(js, v, "compare");
  if(!JS_IsNull(compare_val)) state.compare_op = js2SDL_GPUCompareOp(js, compare_val);
  JS_FreeValue(js, compare_val);

  JSValue fail_val = JS_GetPropertyStr(js, v, "fail");
  if(!JS_IsNull(fail_val)) state.fail_op = js2SDL_GPUStencilOp(js, fail_val);
  JS_FreeValue(js, fail_val);

  JSValue depth_fail_val = JS_GetPropertyStr(js, v, "depth_fail");
  if(!JS_IsNull(depth_fail_val)) state.depth_fail_op = js2SDL_GPUStencilOp(js, depth_fail_val);
  JS_FreeValue(js, depth_fail_val);

  JSValue pass_val = JS_GetPropertyStr(js, v, "pass");
  if(!JS_IsNull(pass_val)) state.pass_op = js2SDL_GPUStencilOp(js, pass_val);
  JS_FreeValue(js, pass_val);

  return state;
}


// Unpacks a typed array javascript object. If it has a gpu property, returns it, too. Otherwise, if requested, makes one.
void *gpu_buffer_unpack(JSContext *js, SDL_GPUDevice *device, JSValue buffer, size_t *size, void **send_data, SDL_GPUBuffer **send_gpu)
{
  size_t o, len, bytes, msize;
  JSValue buf = JS_GetTypedArrayBuffer(js, buffer, &o, &len, &bytes);
  void *data = js_get_blob_data(js, &msize, buf);
  JS_FreeValue(js,buf); 
  if (size) *size = msize;
  if (send_gpu) {
    JSValue gpu = JS_GetPropertyStr(js,buffer,"gpu");
    *send_gpu = js2SDL_GPUBuffer(js,gpu);
    if (!*send_gpu) {
      JSValue idx = JS_GetPropertyStr(js,buffer, "index");
      Uint32 usage = JS_ToBool(js,idx) ? SDL_GPU_BUFFERUSAGE_INDEX : SDL_GPU_BUFFERUSAGE_VERTEX;
      JS_FreeValue(js,idx);      
      *send_gpu = SDL_CreateGPUBuffer(device, &(SDL_GPUBufferCreateInfo) { .usage=usage,.size=msize});
      if (!*send_gpu) printf("COULDN'T MAKE GPU BUFFER: %s\n", SDL_GetError());
      JS_SetPropertyStr(js, buffer, "gpu", SDL_GPUBuffer2js(js,*send_gpu));
    }
    JS_FreeValue(js,gpu);
  }

  if (send_data)
    *send_data = data;
  return data;
}


// GPU buffer management functions
void free_gpu_buffer(JSRuntime *rt, void *opaque, void *ptr)
{
  TracyCFree(ptr);
}

// GPU API
JSC_CCALL(gpu_claim_window,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
  SDL_Window *win = js2SDL_Window(js, argv[0]);
  SDL_ClaimWindowForGPUDevice(gpu,win);
)

JSC_CCALL(gpu_set_swapchain,
  if (!SDL_SetGPUSwapchainParameters(global_gpu,global_window, js2SDL_GPUSwapchainComposition(js,argv[0]), js2SDL_GPUPresentMode(js,argv[1])))
    return JS_ThrowReferenceError(js, "Could not set: %s\n", SDL_GetError());  
)

JSC_CCALL(cmd_acquire_swapchain,
  SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
  Uint32 w,h;
  SDL_GPUTexture *texture;
  SDL_AcquireGPUSwapchainTexture(cmds,global_window, &texture, &w, &h);
  if (!texture) return JS_NULL;
  JSValue swap = JS_NULL;

  JSValue *js_swapchains = ((cell_rt*)JS_GetContextOpaque(js))->js_swapchains;
  
  for (int i = 0; i < arrlen(js_swapchains); i++) {
    if (js2SDL_GPUTexture(js,js_swapchains[i]) == texture) {
      swap = js_swapchains[i];
      break;
    }
  }
  
  if (JS_IsNull(swap)) {
    swap = SDL_GPUTexture2js(js,texture);
    arrput(js_swapchains,swap);
  }

  JS_SetPropertyStr(js,swap,"width", number2js(js,w));
  JS_SetPropertyStr(js,swap,"height", number2js(js,h));
  return JS_DupValue(js,swap);
)

JSC_CCALL(cmd_swapchain_pass,
  SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
  SDL_GPUColorTargetInfo info = {0};
  Uint32 w, h;
  SDL_AcquireGPUSwapchainTexture(cmds, global_window, &info.texture, &w,&h);
  info.load_op = SDL_GPU_LOADOP_CLEAR;
  info.store_op = SDL_GPU_STOREOP_STORE;
  colorf c = js2color(js,argv[0]);
  info.clear_color = (SDL_FColor){c.r,c.g,c.b,c.a};
  SDL_GPURenderPass *pass = SDL_BeginGPURenderPass(
    cmds,
    &info,
    1,
    NULL
  );
  if (!pass) return JS_ThrowReferenceError(js, "Unable to create swapchain pass: %s", SDL_GetError());
  JSValue jspass = SDL_GPURenderPass2js(js,pass);
  JS_SetPropertyStr(js,jspass,"size", vec22js(js,(HMM_Vec2){w,h}));
  return jspass;
)

JSC_CCALL(gpu_load_texture,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, self);
  SDL_Surface *surf = js2SDL_Surface(js, argv[0]);
  if (!surf) return JS_ThrowReferenceError(js, "Surface was not a surface.");

  int compression_level = js2number(js,argv[1]);
  if (compression_level < 0) compression_level = 0;
  if (compression_level > 2) compression_level = 2;

  int dofree = 0;
  
  SDL_PixelFormat sfmt = surf->format;
  const SDL_PixelFormatDetails *pdetails = SDL_GetPixelFormatDetails(sfmt);

if (!pdetails) {
    // If we can't get pixel format details, fall back to converting to RGBA8888
    surf = SDL_ConvertSurface(surf, SDL_PIXELFORMAT_RGBA8888);
    dofree = 1;
    sfmt = SDL_PIXELFORMAT_RGBA8888;
    pdetails = SDL_GetPixelFormatDetails(sfmt);
    if (!pdetails) {
        // Should never happen with RGBA8888, but just in case
        return JS_ThrowReferenceError(js, "Unable to get pixel format details.");
    }
}

// Check if format has alpha
bool has_alpha = (pdetails->Amask != 0);

// Choose a GPU format that closely matches the surface's format
SDL_GPUTextureFormat chosen_format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;

switch (sfmt) {
    case SDL_PIXELFORMAT_RGBA32:
    case SDL_PIXELFORMAT_RGBX8888:
    case SDL_PIXELFORMAT_XRGB8888:
    case SDL_PIXELFORMAT_XBGR8888:
      chosen_format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
      break;

    case SDL_PIXELFORMAT_RGBA4444:
    case SDL_PIXELFORMAT_ABGR4444:
    case SDL_PIXELFORMAT_ARGB4444:
    case SDL_PIXELFORMAT_BGRA4444:
      // 4-4-4-4 format
      chosen_format = SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM;
      break;

    case SDL_PIXELFORMAT_RGB565:
    case SDL_PIXELFORMAT_BGR565:
      // 5-6-5 format
      chosen_format = SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM;
      break;

    case SDL_PIXELFORMAT_RGBA5551:
    case SDL_PIXELFORMAT_ARGB1555:
    case SDL_PIXELFORMAT_BGRA5551:
    case SDL_PIXELFORMAT_ABGR1555:
      // 5-5-5-1 format
      chosen_format = SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM;
      break;

    default:
        surf = SDL_ConvertSurface(surf, SDL_PIXELFORMAT_RGBA32);
        dofree = 1;
        sfmt = SDL_PIXELFORMAT_RGBA32;
        pdetails = SDL_GetPixelFormatDetails(sfmt);
        chosen_format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
        has_alpha = true;
        break;
}

// If compression_level > 0, we override format with BC1/BC3
bool compress = (compression_level > 0);
int stb_mode = STB_DXT_NORMAL;
SDL_GPUTextureFormat compressed_format = chosen_format;

if (compress) {
    if (has_alpha)
        compressed_format = SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM; // DXT5
    else
        compressed_format = SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM; // DXT1

    if (compression_level > 1) stb_mode = STB_DXT_HIGHQUAL;
}

SDL_GPUTexture *tex = SDL_CreateGPUTexture(gpu, &(SDL_GPUTextureCreateInfo) {
    .type = SDL_GPU_TEXTURETYPE_2D,
    .format = compress ? compressed_format : chosen_format,
    .width = surf->w,
    .height = surf->h,
    .layer_count_or_depth = 1,
    .num_levels = 1,
    .usage = SDL_GPU_TEXTUREUSAGE_SAMPLER
});

const void *pixel_data = surf->pixels;
size_t pixel_data_size = surf->pitch * surf->h;
unsigned char *upload_data = NULL;
size_t upload_size = pixel_data_size;

if (compress) {
    // Compress with stb_dxt
    int block_width = (surf->w + 3) / 4;
    int block_height = (surf->h + 3) / 4;
    int block_size = (compressed_format == SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM) ? 8 : 16;
    int blocks_count = block_width * block_height;

    unsigned char *compressed_data = (unsigned char*)malloc(blocks_count * block_size);
    if (!compressed_data) {
        if (dofree) SDL_DestroySurface(surf);
        return JS_ThrowOutOfMemory(js);
    }

    const unsigned char *src_pixels = (const unsigned char *)pixel_data;
    for (int by = 0; by < block_height; ++by) {
        for (int bx = 0; bx < block_width; ++bx) {
            unsigned char block_rgba[4*4*4];
            memset(block_rgba, 0, sizeof(block_rgba));

            for (int y = 0; y < 4; ++y) {
                for (int x = 0; x < 4; ++x) {
                    int sx = bx*4 + x;
                    int sy = by*4 + y;
                    if (sx < surf->w && sy < surf->h) {
                        const unsigned char *pixel = src_pixels + sy * surf->pitch + sx * pdetails->bytes_per_pixel;
                        Uint32 pixelValue = 0;
                        // Copy pixel data into a Uint32 for SDL_GetRGBA-like extraction
                        // We'll use masks/shifts from pdetails
                        memcpy(&pixelValue, pixel, pdetails->bytes_per_pixel);

                        // Extract RGBA
                        unsigned char r = (pixelValue & pdetails->Rmask) >> pdetails->Rshift;
                        unsigned char g = (pixelValue & pdetails->Gmask) >> pdetails->Gshift;
                        unsigned char b = (pixelValue & pdetails->Bmask) >> pdetails->Bshift;
                        unsigned char a = pdetails->Amask ? ((pixelValue & pdetails->Amask) >> pdetails->Ashift) : 255;

                        // If bits are not fully 8-bit, scale them:
                        // pdetails->Rbits (etc.) give how many bits each channel uses
                        int Rmax = (1 << pdetails->Rbits) - 1;
                        int Gmax = (1 << pdetails->Gbits) - 1;
                        int Bmax = (1 << pdetails->Bbits) - 1;
                        int Amax = pdetails->Abits ? ((1 << pdetails->Abits) - 1) : 255;

                        if (pdetails->Rbits < 8) r = (r * 255) / Rmax;
                        if (pdetails->Gbits < 8) g = (g * 255) / Gmax;
                        if (pdetails->Bbits < 8) b = (b * 255) / Bmax;
                        if (pdetails->Amask && pdetails->Abits < 8) a = (a * 255) / Amax;

                        // Set block pixel
                        block_rgba[(y*4+x)*4+0] = r;
                        block_rgba[(y*4+x)*4+1] = g;
                        block_rgba[(y*4+x)*4+2] = b;
                        block_rgba[(y*4+x)*4+3] = a;
                    }
                }
            }

            unsigned char *dest_block = compressed_data + (by * block_width + bx)*block_size;
            int alpha = (compressed_format == SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM) ? 1 : 0;
            stb_compress_dxt_block(dest_block, block_rgba, alpha, stb_mode);
        }
    }

    upload_data = compressed_data;
    upload_size = blocks_count * block_size;
  } else {
    // No compression, upload directly
    upload_data = (unsigned char*)pixel_data; 
    upload_size = pixel_data_size;
  }

  SDL_GPUTransferBuffer *tex_buffer = SDL_CreateGPUTransferBuffer(
    gpu,
    &(SDL_GPUTransferBufferCreateInfo) {
      .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
      .size = upload_size
    });
  void *tex_ptr = SDL_MapGPUTransferBuffer(gpu, tex_buffer, false);
  memcpy(tex_ptr, upload_data, upload_size);
  SDL_UnmapGPUTransferBuffer(gpu, tex_buffer);
  
  SDL_GPUCommandBuffer *uploadcmd = SDL_AcquireGPUCommandBuffer(gpu);
  SDL_GPUCopyPass *copypass = SDL_BeginGPUCopyPass(uploadcmd);
  SDL_UploadToGPUTexture(
    copypass,
    &(SDL_GPUTextureTransferInfo) {
      .transfer_buffer = tex_buffer, 
     .offset = 0
    },
    &(SDL_GPUTextureRegion) {
      .texture = tex,
      .w = surf->w,
      .h = surf->h,
      .d = 1
    },
    false
);

  SDL_EndGPUCopyPass(copypass);
  SDL_SubmitGPUCommandBuffer(uploadcmd);
  SDL_ReleaseGPUTransferBuffer(gpu, tex_buffer);

  if (compress) {
    free(upload_data);
  }

  ret = SDL_GPUTexture2js(js, tex);
  JS_SetPropertyStr(js, ret, "width", number2js(js, surf->w));
  JS_SetPropertyStr(js, ret, "height", number2js(js, surf->h));
  JS_SetPropertyStr(js,ret,"dim", vec22js(js,(HMM_Vec2){surf->w,surf->h}));

  if (dofree) SDL_DestroySurface(surf);

)

static JSValue js_gpu_make_pipeline(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
  if (argc < 1)
    return JS_ThrowTypeError(js, "gpu_pipeline requires a pipeline object");

  JSValue pipe = argv[0];
  if (!JS_IsObject(pipe))
    return JS_ThrowTypeError(js, "gpu_pipeline argument must be an object");

  SDL_GPUGraphicsPipelineCreateInfo info = {0};

  // ---------------------------------------------------
  // 2. Retrieve vertex buffer descriptions array
  // ---------------------------------------------------
  JSValue vbd_val = JS_GetPropertyStr(js, pipe, "vertex_buffer_descriptions");
  Uint32 vbd_len = JS_ArrayLength(js,vbd_val);
  SDL_GPUVertexBufferDescription vbd[vbd_len];
      for (Uint32 i = 0; i < vbd_len; i++) {
        JSValue elem = JS_GetPropertyUint32(js, vbd_val, i);
        if (JS_IsObject(elem)) {
          JSValue slot_val = JS_GetPropertyStr(js, elem, "slot");
          JSValue pitch_val = JS_GetPropertyStr(js, elem, "pitch");
          JSValue rate_val = JS_GetPropertyStr(js, elem, "input_rate");
          JSValue step_val = JS_GetPropertyStr(js, elem, "instance_step_rate");

          // Slot
          Uint32 slot = 0;
          JS_ToUint32(js, &slot, slot_val);
          JS_FreeValue(js, slot_val);

          // Pitch
          Uint32 pitch = 0;
          JS_ToUint32(js, &pitch, pitch_val);
          JS_FreeValue(js, pitch_val);

          // Input Rate
          const char *rate_str = JS_ToCString(js, rate_val);
          SDL_GPUVertexInputRate input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
          if (rate_str) {
            if (!strcmp(rate_str, "vertex")) input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
            else if (!strcmp(rate_str, "instance")) input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE;
            JS_FreeCString(js, rate_str);
          }
          JS_FreeValue(js, rate_val);

          // Instance Step Rate
          Uint32 step_rate = 0;
          JS_ToUint32(js, &step_rate, step_val);
          JS_FreeValue(js, step_val);

          vbd[i].slot               = slot;
          vbd[i].pitch              = pitch;
          vbd[i].input_rate         = input_rate;
          vbd[i].instance_step_rate = step_rate;
        }
        JS_FreeValue(js, elem);
      }
  JS_FreeValue(js, vbd_val);

  // ---------------------------------------------------
  // 3. Retrieve vertex attributes array
  // ---------------------------------------------------
  JSValue vat_val = JS_GetPropertyStr(js, pipe, "vertex_attributes");
  Uint32 vat_len = JS_ArrayLength(js,vat_val);
  
  SDL_GPUVertexAttribute vat[vat_len];
  for (Uint32 i = 0; i < vat_len; i++) {
    JSValue elem = JS_GetPropertyUint32(js, vat_val, i);
    if (JS_IsObject(elem)) {
      JSValue loc_val   = JS_GetPropertyStr(js, elem, "location");
      JSValue slot_val  = JS_GetPropertyStr(js, elem, "buffer_slot");
      JSValue fmt_val   = JS_GetPropertyStr(js, elem, "format");
      JSValue off_val   = JS_GetPropertyStr(js, elem, "offset");

          // location
          Uint32 location = 0;
          JS_ToUint32(js, &location, loc_val);
          JS_FreeValue(js, loc_val);

          // buffer_slot
          Uint32 buffer_slot = 0;
          JS_ToUint32(js, &buffer_slot, slot_val);
          JS_FreeValue(js, slot_val);

          // format
          const char *fmt_str = JS_ToCString(js, fmt_val);
          SDL_GPUVertexElementFormat format = -1; // TODO: Check for error (if this is still -1)
          if (fmt_str) {
            // Map from string to SDL_GPUVertexElementFormat
            if (!strcmp(fmt_str, "float2")) format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
            else if (!strcmp(fmt_str, "float3")) format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3;
            else if (!strcmp(fmt_str, "float4")) format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
            else if (!strcmp(fmt_str, "color"))
                format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
            JS_FreeCString(js, fmt_str);
          }
          JS_FreeValue(js, fmt_val);

          // offset
          Uint32 offset = 0;
          JS_ToUint32(js, &offset, off_val);
          JS_FreeValue(js, off_val);

          vat[i].location    = location;
          vat[i].buffer_slot = buffer_slot;
          vat[i].format      = format;
          vat[i].offset      = offset;
        }
        JS_FreeValue(js, elem);
      }
  JS_FreeValue(js, vat_val);

  info.vertex_input_state = (SDL_GPUVertexInputState){
    .vertex_buffer_descriptions = vbd,
    .num_vertex_buffers = vbd_len,
    .vertex_attributes = vat,
    .num_vertex_attributes = vat_len
  };

  // Vertex Shader
  JSValue vertex_val = JS_GetPropertyStr(js, pipe, "vertex");
  JSValue fragment_val = JS_GetPropertyStr(js,pipe,"fragment");
  JS_GETPROP(js,info.vertex_shader, vertex_val, gpu, SDL_GPUShader)
  JS_GETPROP(js, info.fragment_shader, fragment_val, gpu, SDL_GPUShader)
  
  JS_FreeValue(js, vertex_val);
  JS_FreeValue(js, fragment_val);

  // Primitive Type
  JS_GETPROP(js, info.primitive_type, pipe, primitive, SDL_GPUPrimitiveType)
  
  // Rasterizer State
  JS_GETPROP(js, info.rasterizer_state.fill_mode, pipe, fill, SDL_GPUFillMode)
  JS_GETPROP(js, info.rasterizer_state.cull_mode, pipe, cull, SDL_GPUCullMode)
  JS_GETPROP(js, info.rasterizer_state.front_face, pipe, face, SDL_GPUFrontFace)
  
  // Depth Stencil State
  JSValue depth_val = JS_GetPropertyStr(js, pipe, "depth");
  if (JS_IsObject(depth_val)) {
    JS_GETPROP(js,info.depth_stencil_state.compare_op, depth_val, compare, SDL_GPUCompareOp)
    JS_GETPROP(js,info.depth_stencil_state.enable_depth_test, depth_val, test, bool)
    JS_GETPROP(js,info.depth_stencil_state.enable_depth_write, depth_val, write, bool)
    JS_GETPROP(js,info.rasterizer_state.depth_bias_constant_factor, depth_val, bias, number)
    JS_GETPROP(js,info.rasterizer_state.depth_bias_slope_factor, depth_val,bias_slope_scale, number)
    JS_GETPROP(js,info.rasterizer_state.depth_bias_clamp, depth_val,bias_clamp_val, number)
  }
  JS_FreeValue(js, depth_val);

  // Stencil State
  JSValue stencil_val = JS_GetPropertyStr(js, pipe, "stencil");
  if (JS_IsObject(stencil_val)) {
    JS_GETPROP(js,info.depth_stencil_state.enable_stencil_test, stencil_val, enabled, bool)
    if (info.depth_stencil_state.enable_stencil_test) {
      JS_GETPROP(js, info.depth_stencil_state.front_stencil_state, stencil_val, front, SDL_GPUStencilOpState)
      JS_GETPROP(js, info.depth_stencil_state.back_stencil_state, stencil_val, back, SDL_GPUStencilOpState)

      JSValue compare_mask_val = JS_GetPropertyStr(js, stencil_val, "compare_mask");
      uint32_t tmp;
      JS_ToUint32(js, &tmp, compare_mask_val);
      info.depth_stencil_state.compare_mask = tmp;
      JS_FreeValue(js, compare_mask_val);

      // Write Mask
      JSValue write_mask_val = JS_GetPropertyStr(js, stencil_val, "write_mask");
      JS_ToUint32(js, &tmp, write_mask_val);
      info.depth_stencil_state.write_mask = tmp;
    }
  }
  JS_FreeValue(js, stencil_val);

  JSValue js_tar = JS_GetPropertyStr(js,pipe,"target");

  SDL_GPUGraphicsPipelineTargetInfo target_info = {0};
  JSValue color_tars = JS_GetPropertyStr(js,js_tar,"color_targets");
  target_info.num_color_targets = JS_ArrayLength(js,color_tars);
  SDL_GPUColorTargetDescription dsc[target_info.num_color_targets];
  target_info.color_target_descriptions = dsc;  

  for (int i = 0; i < target_info.num_color_targets; i++) {
    JSValue c = JS_GetPropertyUint32(js,color_tars,i);
    dsc[i] = js2SDL_GPUColorTargetDescription(js,c);
    JS_FreeValue(js,c);
  }
  JS_FreeValue(js,color_tars);    

  JS_GETPROP(js,target_info.depth_stencil_format,js_tar,depth,SDL_GPUTextureFormat);
  if (target_info.depth_stencil_format) target_info.has_depth_stencil_target = 1;

  info.target_info = target_info;

  JS_FreeValue(js, js_tar);

  // Create the pipeline
  SDL_GPUGraphicsPipeline *pipeline = SDL_CreateGPUGraphicsPipeline(gpu, &info);
  if (!pipeline) return JS_ThrowInternalError(js, "Failed to create GPU pipeline");

    return SDL_GPUGraphicsPipeline2js(js, pipeline);
}

// Now the function to create the sampler
static JSValue js_gpu_make_sampler(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
  SDL_GPUSamplerCreateInfo info = {0};
  JSValue sampler = argv[0];
  
  JS_GETPROP(js,info.min_filter,sampler,min_filter,SDL_GPUFilter)
  JS_GETPROP(js,info.mag_filter,sampler,mag_filter,SDL_GPUFilter)
  JS_GETPROP(js,info.mipmap_mode, sampler, mipmap, SDL_GPUSamplerMipmapMode)
  JS_GETPROP(js,info.address_mode_u, sampler, u, SDL_GPUSamplerAddressMode)
  JS_GETPROP(js,info.address_mode_v, sampler, v, SDL_GPUSamplerAddressMode)
  JS_GETPROP(js,info.address_mode_w, sampler, w, SDL_GPUSamplerAddressMode)
  JS_GETPROP(js,info.mip_lod_bias, sampler, mip_bias, number)
  JS_GETPROP(js,info.max_anisotropy, sampler, max_anisotropy, number)
  JS_GETPROP(js,info.compare_op,sampler,compare_op,SDL_GPUCompareOp)
  JS_GETPROP(js,info.min_lod,sampler,min_lod,number)
  JS_GETPROP(js,info.max_lod,sampler,max_lod,number)
  JS_GETPROP(js, info.enable_anisotropy, sampler, anistropy, bool)
  JS_GETPROP(js, info.enable_compare, sampler, compare, bool)
  
  // Create the sampler
  SDL_GPUSampler *sdl_sampler = SDL_CreateGPUSampler(gpu, &info);
  if (!sdl_sampler) return JS_ThrowInternalError(js, "Failed to create GPU sampler");

  return SDL_GPUSampler2js(js, sdl_sampler); // You need to implement SDL_GPUSampler2js similar to pipeline
}


JSC_CCALL(gpu_texture,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
  SDL_GPUTextureCreateInfo info = {0};
  JSValue fmt = argv[0];
  JS_GETPROP(js, info.width, fmt, width, number)
  JS_GETPROP(js, info.height, fmt, height, number)
  JS_GETPROP(js, info.layer_count_or_depth, fmt, layers, number)
  JS_GETPROP(js, info.num_levels, fmt, mip_levels, number)
  JS_GETPROP(js, info.sample_count, fmt, samples, number)
  JS_GETPROP(js, info.type, fmt, type, SDL_GPUTextureType)
  JS_GETPROP(js, info.format, fmt, format, SDL_GPUTextureFormat)
  if (JS_GETBOOL(js,fmt,"sampler"))
    info.usage |= SDL_GPU_TEXTUREUSAGE_SAMPLER;
  if (JS_GETBOOL(js,fmt,"color_target"))
    info.usage |= SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
  if (JS_GETBOOL(js,fmt,"depth_target"))
    info.usage |= SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET;
  if (JS_GETBOOL(js,fmt,"read"))
    info.usage |= (SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ | SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ);
  if (JS_GETBOOL(js,fmt,"write"))
    info.usage |= SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE;
  if (JS_GETBOOL(js,fmt,"readwrite"))
    info.usage |= SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE;

  SDL_GPUTexture *tex = SDL_CreateGPUTexture(gpu,&info);
  if (!tex) return JS_ThrowReferenceError(js, "Unable to create texture: %s", SDL_GetError());
  JSValue jstex = SDL_GPUTexture2js(js,tex);
  JS_SetPropertyStr(js,jstex,"width", number2js(js,info.width));
  JS_SetPropertyStr(js,jstex,"height", number2js(js,info.height));
  JS_SetPropertyStr(js,jstex,"dim", vec22js(js,(HMM_Vec2){info.width, info.height}));  
  return jstex;
)

typedef struct {
  text_vert vert[4];
} quad;

// Comparator inline for potential compiler inlining
static inline int sprite_compare(const sprite *a, const sprite *b) {
  if (a->layer != b->layer) return a->layer - b->layer;

  if (a->pos.Y != b->pos.Y)
    return (b->pos.Y - a->pos.Y);

  if (JS_VALUE_GET_PTR(a->image) != JS_VALUE_GET_PTR(b->image))
    return JS_VALUE_GET_PTR(a->image) < JS_VALUE_GET_PTR(b->image) ? -1 : 1;

  return 0;
}

// Merge two sorted sub-ranges [left..mid] and [mid+1..right]
static void merge_sprites(sprite *arr, sprite *temp, size_t left, size_t mid, size_t right) {
  size_t i = left, j = mid + 1, k = left;

  while (i <= mid && j <= right)
    if (sprite_compare(&arr[i], &arr[j]) <= 0) temp[k++] = arr[i++]; 
    else temp[k++] = arr[j++];

  while (i <= mid) temp[k++] = arr[i++];
  while (j <= right) temp[k++] = arr[j++];

  for (size_t p = left; p <= right; p++)
    arr[p] = temp[p];
}

// Recursive mergesort
static void merge_sort_recursive(sprite *arr, sprite *temp, size_t left, size_t right) {
  if (left >= right) return;
  size_t mid = (left + right) / 2;
  merge_sort_recursive(arr, temp, left, mid);
  merge_sort_recursive(arr, temp, mid + 1, right);
  merge_sprites(arr, temp, left, mid, right);
}

// Public function to sort the array of sprites with mergesort
void better_sort_sprites(sprite *arr, size_t length) {
  if (length < 2) return;
  sprite *temp = malloc(length * sizeof(sprite));
  if (!temp) return; // fallback or handle error
  merge_sort_recursive(arr, temp, 0, length - 1);
  free(temp);
}

int sort_sprite_backtofront(const sprite *a, const sprite *b)
{
  if (a->layer != b->layer) return a->layer - b->layer;
  if (JS_VALUE_GET_PTR(a->image) != JS_VALUE_GET_PTR(b->image))
    return JS_VALUE_GET_PTR(a->image) < JS_VALUE_GET_PTR(b->image) ? -1 : 1;  
  return 0;
}

int sort_sprite_fronttoback(const sprite *a, const sprite *b)
{
  if (a->layer != b->layer) return b->layer - a->layer;
  if (JS_VALUE_GET_PTR(a->image) != JS_VALUE_GET_PTR(b->image))
    return JS_VALUE_GET_PTR(a->image) < JS_VALUE_GET_PTR(b->image) ? -1 : 1;  
  return 0;
}

int sort_sprite_texture(const sprite *a, const sprite *b)
{
  if (JS_VALUE_GET_PTR(a->image) != JS_VALUE_GET_PTR(b->image))
    return JS_VALUE_GET_PTR(a->image) < JS_VALUE_GET_PTR(b->image) ? -1 : 1;
  return 0;
}

int sort_sprite(const sprite *a, const sprite *b)
{
  if (a->layer != b->layer) return a->layer - b->layer;
  
  if (a->pos.Y != b->pos.Y)
    return (b->pos.Y - a->pos.Y);
    
  if (JS_VALUE_GET_PTR(a->image) != JS_VALUE_GET_PTR(b->image))
    return JS_VALUE_GET_PTR(a->image) < JS_VALUE_GET_PTR(b->image) ? -1 : 1;  
  return 0;
}

JSC_CCALL(gpu_sort_sprite,
  JSValue a = argv[0];
  JSValue b = argv[1];
  
  int alayer, blayer;
  JS_GETATOM(js,alayer,a,layer,number)
  JS_GETATOM(js,blayer,b,layer,number)
  if (alayer != blayer) return number2js(js,alayer - blayer);

  rect ar, br;
  JS_GETATOM(js,ar,a,rect,rect)
  JS_GETATOM(js,br,b,rect,rect)
  if (ar.y != br.y) return number2js(js,br.y-ar.y);

  JSValue aimg,bimg;
  aimg = JS_GetPropertyStr(js,a,"image");
  bimg = JS_GetPropertyStr(js,b,"image");
  JS_FreeValue(js,aimg);
  JS_FreeValue(js,bimg);
  if (!JS_SameValue(js,aimg,bimg)) return number2js(js,JS_VALUE_GET_PTR(aimg) < JS_VALUE_GET_PTR(bimg) ? -1 : 1);
  return number2js(js,0);
)



JSC_CCALL(gpu_make_quad,
  size_t quads = 1;
  size_t verts = quads*4;
  size_t count = quads*6;

  // Prepare arrays on CPU
  HMM_Vec2 *posdata = malloc(sizeof(*posdata)*verts);
  HMM_Vec2 *uvdata = malloc(sizeof(*uvdata)*verts);
  HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts);
  HMM_Vec4 usecolor = (HMM_Vec4){1,1,1,1};
  posdata[0] = (HMM_Vec2){0,1};
  posdata[1] = (HMM_Vec2){1,1};
  posdata[2] = (HMM_Vec2){0,0};
  posdata[3] = (HMM_Vec2){1,0};
  
  uvdata[0] = (HMM_Vec2){0,0};
  uvdata[1] = (HMM_Vec2){1,0};
  uvdata[2] = (HMM_Vec2){0,1};
  uvdata[3] = (HMM_Vec2){1,1};
  
  colordata[0] = usecolor;
  colordata[1] = usecolor;
  colordata[2] = usecolor;
  colordata[3] = usecolor;

  ret = JS_NewObject(js);
  JSValue pos = make_gpu_buffer(js, posdata, sizeof(*posdata)*verts, JS_TYPED_ARRAY_FLOAT32, 2, 1,0);
  JSValue uv = make_gpu_buffer(js, uvdata, sizeof(*uvdata)*verts, JS_TYPED_ARRAY_FLOAT32, 2, 1,0);
  JSValue color = make_gpu_buffer(js, colordata, sizeof(*colordata)*verts, JS_TYPED_ARRAY_FLOAT32, 0, 1,0);

  JS_SetPropertyStr(js, ret, "pos", pos);
  JS_SetPropertyStr(js, ret, "uv", uv);
  JS_SetPropertyStr(js, ret, "color", color);
  JSValue indices = make_quad_indices_buffer(js, quads); 
  JS_SetPropertyStr(js, ret, "indices", indices);

  JS_SetPropertyStr(js, ret, "vertices", number2js(js, verts));
  JS_SetPropertyStr(js, ret, "count", number2js(js, count));
  JS_SetPropertyStr(js,ret,"num_indices", number2js(js,count));

  // Free temporary CPU arrays
  free(posdata);
  free(uvdata);
  free(colordata);

  return ret;
)


JSC_CCALL(gpu_driver,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
  ret = JS_NewString(js, SDL_GetGPUDeviceDriver(gpu));
)

static JSValue js_gpu_make_shader(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  if (argc < 1 || !JS_IsObject(argv[0]))
    return JS_ThrowTypeError(js, "make_shader expects an object with code, stage, num_samplers, num_textures, num_storage_buffers, num_uniform_buffers");

  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, self);

  JSValue obj = argv[0];
  
  SDL_GPUShaderCreateInfo info = {0};

  // code
  JSValue code_val = JS_GetPropertyStr(js, obj, "code");
  size_t code_size;
  void *code_data = js_get_blob_data(js, &code_size, code_val);
  JS_FreeValue(js, code_val);
  if (!code_data)
    return JS_ThrowTypeError(js, "shader.code must be an ArrayBuffer");

  // stage
  JSValue stage_val = JS_GetPropertyStr(js, obj, "stage");
  const char *stage_str = JS_ToCString(js, stage_val);
  if (stage_str) {
    if (!strcmp(stage_str, "vertex")) info.stage = SDL_GPU_SHADERSTAGE_VERTEX;
    else if (!strcmp(stage_str, "fragment")) info.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
    JS_FreeCString(js, stage_str);
  } // TODO: Make this its own function
  JS_FreeValue(js, stage_val);

  // num_samplers
  JS_GETPROP(js, info.num_samplers, obj, num_samplers, number)
  JS_GETPROP(js, info.num_storage_textures, obj, num_textures, number)
  JS_GETPROP(js, info.num_storage_buffers, obj, num_storage_buffers, number)
  JS_GETPROP(js, info.num_uniform_buffers, obj, num_uniform_buffers, number)
  JS_GETPROP(js, info.format, obj, format, SDL_GPUShaderFormat)
  
  JSValue entry_val = JS_GetPropertyStr(js,obj,"entrypoint");
  info.entrypoint = JS_ToCString(js,entry_val);
  JS_FreeValue(js,entry_val);

  info.code_size            = code_size;
  info.code                 = code_data;
  info.props                = 0; // No extension properties by default
  
  JS_FreeCString(js,info.entrypoint);

  SDL_GPUShader *shader = SDL_CreateGPUShader(gpu, &info);
  if (!shader)
    return JS_ThrowReferenceError(js, "Unable to create shader: %s", SDL_GetError());

  return SDL_GPUShader2js(js, shader);
}

JSC_CCALL(gpu_acquire_cmd_buffer,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, self);
  SDL_GPUCommandBuffer *cb = SDL_AcquireGPUCommandBuffer(gpu);
  if (!cb) return JS_ThrowReferenceError(js,"Unable to acquire command buffer: %s", SDL_GetError());
  // Wrap cb in a JS object
  return SDL_GPUCommandBuffer2js(js, cb);
)

/* takes argv
   0: a command buffer to write to
   1: a buffer or array of buffers to upload
   2: an optional transfer buffer to use; if undefined a temporary one is used
*/
JSC_CCALL(gpu_upload,
  JSValue js_cmd = argv[0];
  JSValue js_buffers = argv[1];
  JSValue js_transfer = argv[2];
  
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, self);
  if (!gpu)
    return JS_ThrowTypeError(js, "Invalid GPU device");

  SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, js_cmd);
  if (!cmds)
    return JS_ThrowTypeError(js, "Invalid command buffer");

  if (!JS_IsArray(js, js_buffers))
    return JS_ThrowTypeError(js, "buffers must be an array");

  size_t len = JS_ArrayLength(js, js_buffers);
  if (len == 0)
    return JS_DupValue(js, js_transfer); // No data to upload, just return existing transfer buffer

  struct {
    SDL_GPUBuffer *gpu_buffer;
    void *data;
    size_t size;
  } *items = malloc(sizeof(*items) * len);

  if (!items)
    return JS_ThrowOutOfMemory(js);

  size_t total_size_needed = 0;

  for (size_t i = 0; i < len; i++) {
    JSValue js_buf = JS_GetPropertyUint32(js, js_buffers, i);

    if (JS_IsNull(js_buf))
      continue;

    gpu_buffer_unpack(js, gpu, js_buf, &items[i].size, &items[i].data, &items[i].gpu_buffer);
    total_size_needed += items[i].size;
    JS_FreeValue(js, js_buf);
  }
 
  SDL_GPUTransferBuffer *transfer = js2SDL_GPUTransferBuffer(js,js_transfer);
  if (transfer) {
    // ensure it's large enough
    size_t transfer_size = js_getnum_str(js,js_transfer, "size");
    if (transfer_size < total_size_needed) {
      total_size_needed *= 1.5;
      transfer =  SDL_CreateGPUTransferBuffer( gpu, &(SDL_GPUTransferBufferCreateInfo){
                .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
                .size = total_size_needed
            }
        );
      ret = SDL_GPUTransferBuffer2js(js,transfer);
      JS_SetPropertyStr(js,ret,"size", number2js(js,total_size_needed));
    } else
      ret = JS_DupValue(js,js_transfer); // supplied transfer buffer is fine so we use it
  } else {
      transfer =  SDL_CreateGPUTransferBuffer( gpu,             &(SDL_GPUTransferBufferCreateInfo){
                .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
                .size = total_size_needed
            }
        );
      ret = SDL_GPUTransferBuffer2js(js,transfer);
      JS_SetPropertyStr(js,ret,"size", number2js(js,total_size_needed));
  }

    SDL_GPUCopyPass *copy_pass = SDL_BeginGPUCopyPass(cmds);
    if (!copy_pass) {
        free(items);
        return JS_ThrowReferenceError(js, "Failed to begin copy pass");
    }

    void *mapped_data = SDL_MapGPUTransferBuffer(gpu, transfer, false);
    if (!mapped_data) {
        SDL_EndGPUCopyPass(copy_pass);
        free(items);
        return JS_ThrowReferenceError(js, "Failed to map transfer buffer: %s", SDL_GetError());
    }

    // Copy all data into the mapped transfer buffer
    size_t current_offset = 0;
    for (size_t i = 0; i < len; i++) {
        memcpy((char*)mapped_data + current_offset, items[i].data, items[i].size);
        current_offset += items[i].size;
    }
    SDL_UnmapGPUTransferBuffer(gpu, transfer);

    // Issue uploads for each item
    current_offset = 0;
    for (size_t i = 0; i < len; i++) {
        SDL_UploadToGPUBuffer(
            copy_pass,
            &(SDL_GPUTransferBufferLocation){
                .transfer_buffer = transfer,
                .offset = current_offset
            },
            &(SDL_GPUBufferRegion){
                .buffer = items[i].gpu_buffer,
                .offset = 0,
                .size = items[i].size
            },
            false
        );
        current_offset += items[i].size;
    }

    SDL_EndGPUCopyPass(copy_pass);
    free(items);
)

JSC_CCALL(gpu_wait_for_fences,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
  int n = JS_ArrayLength(js,argv[0]);
  SDL_GPUFence *fences[n];
  for (int i = 0; i < n; i++) {
    JSValue a = JS_GetPropertyUint32(js,argv[0],i);
    fences[i] = js2SDL_GPUFence(js,a);
    JS_FreeValue(js,a);
  }

  int wait_all = JS_ToBool(js,argv[1]);
  return JS_NewBool(js,SDL_WaitForGPUFences(gpu,wait_all,fences,n));
)

JSC_CCALL(gpu_query_fence,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
  SDL_GPUFence *fence = js2SDL_GPUFence(js,argv[0]);
  return JS_NewBool(js,SDL_QueryGPUFence(gpu,fence));
)

JSC_CCALL(gpu_shader_format,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
  SDL_GPUShaderFormat fmt = SDL_GetGPUShaderFormats(gpu);
  if (!fmt) return JS_ThrowReferenceError(js, "Shader format available invalid.");
  
  JSValue arr = JS_NewArray(js);
  int i = 0;
  if (fmt & SDL_GPU_SHADERFORMAT_PRIVATE) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, ".private"));
  if (fmt & SDL_GPU_SHADERFORMAT_SPIRV) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, "spv"));
  if (fmt & SDL_GPU_SHADERFORMAT_DXBC) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, "dxbc"));
  if (fmt & SDL_GPU_SHADERFORMAT_DXIL) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, "dxil"));
  if (fmt & SDL_GPU_SHADERFORMAT_MSL) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, "msl"));
  if (fmt & SDL_GPU_SHADERFORMAT_METALLIB) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, "metallib"));
  return arr;
)

JSC_CCALL(gpu_compute_pipeline,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
  SDL_GPUComputePipelineCreateInfo info = {0};
  JSValue pipe = argv[0];
  JS_GETPROP(js, info.num_samplers, pipe, num_samplers, number)
  JS_GETPROP(js,info.num_readonly_storage_textures,pipe,num_readonly_storage_textures,number)
  JS_GETPROP(js,info.num_readonly_storage_buffers,pipe,num_readonly_storage_buffers,number)
  JS_GETPROP(js,info.num_readwrite_storage_textures,pipe,num_readwrite_storage_textures,number)
  JS_GETPROP(js,info.num_readwrite_storage_buffers,pipe,num_readwrite_storage_buffers,number)
  JS_GETPROP(js,info.threadcount_x,pipe,threadcount_x,number)
  JS_GETPROP(js,info.threadcount_y,pipe,threadcount_y,number)
  JS_GETPROP(js,info.threadcount_z,pipe,threadcount_z,number)
  JS_GETPROP(js,info.entrypoint,pipe,entrypoint,cstring)


  JSValue shader = JS_GetPropertyStr(js,pipe,"shader");
  info.code = js_get_blob_data(js,&info.code_size, shader);
  JS_FreeValue(js,shader);

  SDL_GPUComputePipeline *pipeline = SDL_CreateGPUComputePipeline(gpu, &info);
  JS_FreeCString(js,info.entrypoint);
  if (!pipeline) return JS_ThrowReferenceError(js,"Could not create compute pipeline: %s", SDL_GetError());
  return SDL_GPUComputePipeline2js(js,pipeline);
)

static const JSCFunctionListEntry js_SDL_GPUDevice_funcs[] = {
  MIST_FUNC_DEF(gpu, claim_window, 1),
  MIST_FUNC_DEF(gpu, make_pipeline, 1), // loads pipeline state into an object
  MIST_FUNC_DEF(gpu,compute_pipeline,1),
  MIST_FUNC_DEF(gpu, set_swapchain, 2),
  MIST_FUNC_DEF(gpu,sort_sprite,2),
  MIST_FUNC_DEF(gpu, make_sampler,1),
  MIST_FUNC_DEF(gpu, load_texture, 2),
  MIST_FUNC_DEF(gpu, texture, 1),
  MIST_FUNC_DEF(gpu, make_quad, 0),
  MIST_FUNC_DEF(gpu, driver, 0),
  MIST_FUNC_DEF(gpu, make_shader, 1),
  MIST_FUNC_DEF(gpu, acquire_cmd_buffer, 0),
  MIST_FUNC_DEF(gpu, upload, 3),
  MIST_FUNC_DEF(gpu, wait_for_fences, 2),
  MIST_FUNC_DEF(gpu, query_fence, 1),
  MIST_FUNC_DEF(gpu, shader_format, 0),
};

JSC_CCALL(renderpass_bind_pipeline,
  SDL_GPURenderPass *r = js2SDL_GPURenderPass(js,self);
  SDL_GPUGraphicsPipeline *pipe = js2SDL_GPUGraphicsPipeline(js,argv[0]);
  SDL_BindGPUGraphicsPipeline(r,pipe);
)

JSC_CCALL(renderpass_draw_indexed,
  SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
  SDL_DrawGPUIndexedPrimitives(pass, js2number(js,argv[0]), js2number(js,argv[1]), js2number(js,argv[2]), js2number(js,argv[3]), js2number(js,argv[4]));
)

JSC_CCALL(renderpass_draw,
  SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
  SDL_DrawGPUPrimitives(pass, js2number(js,argv[0]), js2number(js,argv[1]), js2number(js,argv[2]), js2number(js,argv[3]));
)

JSC_CCALL(renderpass_bind_buffers,
  SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
  int first = js2number(js,argv[0]);
  JSValue buffers = argv[1];
  int len = JS_ArrayLength(js,buffers);
  SDL_GPUBufferBinding bindings[len];
  for (int i = 0; i < len; i++) {
    JSValue buffer = JS_GetPropertyUint32(js,buffers,i);
    bindings[i].offset = 0;
    gpu_buffer_unpack(js,global_gpu, buffer, NULL, NULL,&bindings[i].buffer);
    JS_FreeValue(js,buffer);
  }

  SDL_BindGPUVertexBuffers(
    pass,
    first,
    bindings,
    len);
)

JSC_CCALL(renderpass_bind_index_buffer,
  SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
  SDL_GPUBufferBinding bind;
  bind.offset = 0;
  gpu_buffer_unpack(js,global_gpu,argv[0], NULL, NULL, &bind.buffer);
  int elen;
  JSValue b = argv[0];
  JS_GETPROP(js, elen, b, elen, number)
  
  SDL_BindGPUIndexBuffer(pass,&bind,elen == 2 ? SDL_GPU_INDEXELEMENTSIZE_16BIT : SDL_GPU_INDEXELEMENTSIZE_32BIT);
)

JSC_CCALL(renderpass_end,
  SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
  SDL_EndGPURenderPass(pass);
)

JSC_CCALL(renderpass_bind_samplers,
  SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
  int first_slot = js2number(js,argv[1]);
  JSValue arr = argv[2];
  int num = JS_ArrayLength(js,arr);
  SDL_GPUTextureSamplerBinding binds[num];
  for (int i = 0; i < num; i++) {
    JSValue val = JS_GetPropertyUint32(js,arr,i);
    JSValue tex = JS_GetPropertyStr(js,val,"texture");
    JSValue smp = JS_GetPropertyStr(js,val,"sampler");
    binds[i].texture = js2SDL_GPUTexture(js,tex);
    binds[i].sampler = js2SDL_GPUSampler(js,smp);
    JS_FreeValue(js,tex);
    JS_FreeValue(js,smp);
    JS_FreeValue(js,val);
  }
  int vertex = JS_ToBool(js,argv[0]);
  if (vertex)
    SDL_BindGPUVertexSamplers(pass, first_slot, binds, num);
  else
    SDL_BindGPUFragmentSamplers(pass, first_slot, binds, num);
)

JSC_CCALL(renderpass_bind_storage_buffers,

)

JSC_CCALL(renderpass_bind_storage_textures,

)

JSC_CCALL(renderpass_viewport,
  SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
  rect r = js2rect(js,argv[0]);
  SDL_GPUViewport vp ={0};
  vp.x = r.x;
  vp.y = r.y;
  vp.w = r.w;
  vp.h = r.h;
  vp.min_depth = 0;
  vp.max_depth = 1;
  SDL_SetGPUViewport(pass,&vp);
)

JSC_CCALL(renderpass_scissor,
  SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
  rect r = js2rect(js,argv[0]);
  SDL_Rect rr;
  rr.x = r.x;
  rr.y = r.y;
  rr.w = r.w;
  rr.h = r.h;
  SDL_SetGPUScissor(pass,&rr);
)

static const JSCFunctionListEntry js_SDL_GPURenderPass_funcs[] = {
  MIST_FUNC_DEF(renderpass, bind_pipeline, 1),
  MIST_FUNC_DEF(renderpass, viewport, 1),
  MIST_FUNC_DEF(renderpass, scissor, 1),
  MIST_FUNC_DEF(renderpass, draw, 4),
  MIST_FUNC_DEF(renderpass, draw_indexed, 5),
  MIST_FUNC_DEF(renderpass, end, 0),
  MIST_FUNC_DEF(renderpass, bind_index_buffer, 1),
  MIST_FUNC_DEF(renderpass, bind_buffers, 2),
  MIST_FUNC_DEF(renderpass, bind_samplers, 3),
  MIST_FUNC_DEF(renderpass, bind_storage_buffers, 2),
  MIST_FUNC_DEF(renderpass, bind_storage_textures, 2),
};

JSC_CCALL(cmd_render_pass,
  SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);

  if (!JS_IsObject(argv[0])) return JS_ThrowTypeError(js, "render_pass: Expected a render pass descriptor object");
  JSValue passObj = argv[0];

  JSValue colorTargetsVal = JS_GetPropertyStr(js, passObj, "color_targets");
  if (!JS_IsArray(js, colorTargetsVal))
    return JS_ThrowTypeError(js, "render_pass: colorTargets must be an array");

  uint32_t colorCount = JS_ArrayLength(js, colorTargetsVal);
  SDL_GPUColorTargetInfo colortars[colorCount];
  SDL_GPUDepthStencilTargetInfo depthtar;
  int has_depth = 0;

  // Fill colorInfos from JS array
  for (uint32_t i = 0; i < colorCount; i++) {
    JSValue ctargetVal = JS_GetPropertyUint32(js, colorTargetsVal, i);
    colortars[i] = js2SDL_GPUColorTargetInfo(js,ctargetVal);
    JS_FreeValue(js, ctargetVal);
  }

  // Optional depth_stencil
  JSValue depthval = JS_GetPropertyStr(js, passObj, "depth_stencil");
  if (!JS_IsNull(depthval)) {
    has_depth = 1;
    JS_GETPROP(js, depthtar.texture, depthval, texture, SDL_GPUTexture)
    JS_GETPROP(js, depthtar.load_op, depthval, load, SDL_GPULoadOp)
    JS_GETPROP(js, depthtar.store_op, depthval, store, SDL_GPUStoreOp)
    JS_GETPROP(js, depthtar.stencil_load_op, depthval, stencil_load, SDL_GPULoadOp)
    JS_GETPROP(js, depthtar.stencil_store_op, depthval, stencil_store, SDL_GPUStoreOp)
    JS_GETPROP(js,depthtar.clear_depth, depthval, clear, number)
    JS_GETPROP(js,depthtar.clear_stencil,depthval,clear_stencil,number)
  }
  JS_FreeValue(js, depthval);

  SDL_GPURenderPass *pass = SDL_BeginGPURenderPass(
    cmds,
    colortars,
    colorCount,
    has_depth ? &depthtar : NULL
  );
  
  JS_FreeValue(js, colorTargetsVal);  

  if (!pass) return JS_ThrowInternalError(js, "render_pass: Failed to begin render pass");

  return SDL_GPURenderPass2js(js, pass);
)

JSC_CCALL(cmd_bind_vertex_buffer,
  SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
  int slot;
  JS_ToInt32(js, &slot, argv[0]);
  SDL_GPUBuffer *buffer = js2SDL_GPUBuffer(js, argv[1]);
  size_t offset = 0;
  if (argc > 2) JS_ToIndex(js, &offset, argv[2]); 
  SDL_BindGPUVertexBuffers(cmds, slot, &(SDL_GPUBufferBinding){.buffer = buffer, .offset = offset}, 1);
)

JSC_CCALL(cmd_bind_index_buffer,
  SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
  SDL_GPUBuffer *buffer = js2SDL_GPUBuffer(js, argv[0]);
  size_t offset = 0;
  if (argc > 1) JS_ToIndex(js, &offset, argv[1]);
  SDL_BindGPUIndexBuffer(cmds, &(SDL_GPUBufferBinding){.buffer = buffer, .offset = offset}, SDL_GPU_INDEXELEMENTSIZE_16BIT);
)

JSC_CCALL(cmd_bind_fragment_sampler,
  SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
  int slot;
  JS_ToInt32(js, &slot, argv[0]);
  SDL_GPUTexture *tex = js2SDL_GPUTexture(js, argv[1]);
  SDL_GPUSampler *sampler = js2SDL_GPUSampler(js, argv[2]);
  SDL_BindGPUFragmentSamplers(cmds, slot, &(SDL_GPUTextureSamplerBinding){.texture = tex, .sampler = sampler}, 1);
)

JSC_CCALL(cmd_push_vertex_uniform_data,
  SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
  int slot;
  JS_ToInt32(js, &slot, argv[0]);
  size_t buf_size;
  void *data = js_get_blob_data(js, &buf_size, argv[1]);
  SDL_PushGPUVertexUniformData(cmds, slot, data, buf_size);
)

JSC_CCALL(cmd_push_fragment_uniform_data,
  SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
  int slot;
  JS_ToInt32(js, &slot, argv[0]);
  size_t buf_size;
  void *data = js_get_blob_data(js, &buf_size, argv[1]);
  SDL_PushGPUFragmentUniformData(cmds, slot, data, buf_size);
)

JSC_CCALL(cmd_push_compute_uniform_data,
  SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
  int slot;
  JS_ToInt32(js, &slot, argv[0]);
  size_t buf_size;
  void *data = js_get_blob_data(js, &buf_size, argv[1]);
  SDL_PushGPUComputeUniformData(cmds, slot, data, buf_size);
)

JSC_CCALL(cmd_submit,
  SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js,self);
  SDL_GPUFence *fence = SDL_SubmitGPUCommandBufferAndAcquireFence(cmds);
  return SDL_GPUFence2js(js,fence);
)

JSC_CCALL(cmd_hud,
  SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js,self);
  HMM_Vec2 size = js2vec2(js,argv[0]);
  HMM_Mat4 proj = HMM_Orthographic_RH_NO(0,size.x,0,size.y,-1,1);
  shader_globals data = {0};
  data.world_to_projection = proj;
  data.projection_to_world = HMM_InvGeneralM4(proj);
  data.viewport_min_z = -1,
  data.viewport_max_z = 1;
  data.render_size = size;
  data.view_to_projection = proj;
  data.viewport_size = (HMM_Vec2){1,1};
  data.viewport_offset = (HMM_Vec2){0,0};
  data.time = SDL_GetTicksNS() / 1000000000.0f;
  SDL_PushGPUVertexUniformData(cmds, js2number(js,argv[1]), &data, sizeof(data));  
)

JSC_CCALL(cmd_camera,
//  SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
//  shader_globals data = camera_globals(js, argv[0]);
//  SDL_PushGPUVertexUniformData(cmds, js2number(js,argv[1]), &data, sizeof(data));
)

JSC_SCALL(cmd_push_debug_group,
  SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self);
  SDL_PushGPUDebugGroup(cmd,str);
)

JSC_CCALL(cmd_pop_debug_group,
  SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self);
  SDL_PopGPUDebugGroup(cmd);
)

JSC_SCALL(cmd_debug_label,
  SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self);
  SDL_InsertGPUDebugLabel(cmd, str);
)

SDL_GPUBlitRegion js2SDL_GPUBlitRegion(JSContext *js, JSValue v)
{
  SDL_GPUBlitRegion info = {0};
  if (JS_GetClassID(v) == js_SDL_GPUTexture_id) {
    // texture path
    JSValue tex = v;
    info.texture = js2SDL_GPUTexture(js,tex);
    info.mip_level = 0,
    info.layer_or_depth_plane = 0;
    info.x = 0;
    info.y = 0;
    JS_GETPROP(js,info.w,tex,width,number)
    JS_GETPROP(js,info.h,tex,height,number)
    return info;
  }
  JSValue tex = JS_GetPropertyStr(js, v, "texture");
  info.texture = js2SDL_GPUTexture(js,tex);
  JS_GETPROP(js,info.mip_level,v,mip_level, number)
  JS_GETPROP(js,info.mip_level,v,layer,number)
  JS_GETPROP(js,info.x,v,x,number)
  JS_GETPROP(js,info.y,v,y,number)
  JS_GETPROP(js,info.w,v,width,number)
  JS_GETPROP(js,info.h,v,height,number)

  if (!info.w) JS_GETPROP(js,info.w,tex,width,number)
  if (!info.h) JS_GETPROP(js,info.h,tex,height,number)

  JS_FreeValue(js,tex);
  return info;
}

JSC_CCALL(cmd_blit,
  SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self);
  SDL_GPUBlitInfo info = {0};
  JSValue v = argv[0];
  JS_GETPROP(js,info.source,v,src,SDL_GPUBlitRegion)
  JS_GETPROP(js,info.destination,v,dst,SDL_GPUBlitRegion)
  JS_GETPROP(js,info.load_op,v,load,SDL_GPULoadOp)
  JS_GETPROP(js,info.flip_mode,v,flip,SDL_FlipMode)
  JS_GETPROP(js,info.filter,v,filter,SDL_GPUFilter)
  JS_GETPROP(js,info.clear_color,v,color,SDL_FColor)
  SDL_BlitGPUTexture(cmd,&info);
)

JSC_CCALL(cmd_cancel,
  SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self);
  SDL_CancelGPUCommandBuffer(cmd);
)

JSC_CCALL(cmd_compute_pass,
  SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self);
  JSValue textures = argv[0];
  JSValue buffers = argv[1];

  int t_n = JS_ArrayLength(js,textures);
  SDL_GPUStorageTextureReadWriteBinding t_bind[t_n];
  for (int i = 0; i < t_n; i++) {
    JSValue T = JS_GetPropertyUint32(js,textures,i);
    JS_GETPROP(js, t_bind[i].texture, T, texture, SDL_GPUTexture)
    JS_GETPROP(js,t_bind[i].mip_level,T,mip, number)
    JS_GETPROP(js,t_bind[i].layer,T,layer, number)
    JS_FreeValue(js,T);
  }

  int b_n = JS_ArrayLength(js,buffers);
  SDL_GPUStorageBufferReadWriteBinding b_bind[b_n];
  for (int i = 0; i < b_n; i++) {
    JSValue T = JS_GetPropertyUint32(js,buffers,i);
    JS_GETPROP(js,b_bind[i].buffer, T,buffer,SDL_GPUBuffer)
  }

  SDL_GPUComputePass *pass = SDL_BeginGPUComputePass(cmd,t_bind,t_n,b_bind,b_n);
  if (!pass) return JS_ThrowReferenceError(js, "Unable to begin compute pass: %s", SDL_GetError());
  return SDL_GPUComputePass2js(js,pass);
)

static const JSCFunctionListEntry js_SDL_GPUCommandBuffer_funcs[] = {
  MIST_FUNC_DEF(cmd, render_pass, 1),
  MIST_FUNC_DEF(cmd, compute_pass, 2),
  MIST_FUNC_DEF(cmd, swapchain_pass, 1),
  MIST_FUNC_DEF(cmd, acquire_swapchain,0),
  MIST_FUNC_DEF(cmd, bind_vertex_buffer, 2),
  MIST_FUNC_DEF(cmd, bind_index_buffer, 1),
  MIST_FUNC_DEF(cmd, bind_fragment_sampler, 3),
  MIST_FUNC_DEF(cmd, push_vertex_uniform_data, 2),
  MIST_FUNC_DEF(cmd, push_fragment_uniform_data, 2),
  MIST_FUNC_DEF(cmd, push_compute_uniform_data, 2),
  MIST_FUNC_DEF(cmd, submit, 0),
  MIST_FUNC_DEF(cmd, cancel, 0),
  MIST_FUNC_DEF(cmd, camera, 2),
  MIST_FUNC_DEF(cmd, hud, 2),
  MIST_FUNC_DEF(cmd, push_debug_group, 1),
  MIST_FUNC_DEF(cmd, pop_debug_group, 0),
  MIST_FUNC_DEF(cmd, debug_label, 1),
  MIST_FUNC_DEF(cmd, blit, 1),
};

JSC_SCALL(buffer_name,
  SDL_GPUBuffer *buffer = js2SDL_GPUBuffer(js,self);
  SDL_GPUDevice *gpu;
  JS_GETPROP(js, gpu, self, gpu, SDL_GPUDevice)
  SDL_SetGPUBufferName(gpu,buffer,str);
)

static const JSCFunctionListEntry js_SDL_GPUBuffer_funcs[] = {
  MIST_FUNC_DEF(buffer, name, 1),
};

JSC_CCALL(compute_dispatch,
  SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self);
  SDL_DispatchGPUCompute(pass,js2number(js,argv[0]), js2number(js,argv[1]), js2number(js,argv[2]));
)

JSC_CCALL(compute_end,
  SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self);
  SDL_EndGPUComputePass(pass);
)

JSC_CCALL(compute_pipeline,
  SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self);
  SDL_GPUComputePipeline *pipeline = js2SDL_GPUComputePipeline(js,argv[0]);
  SDL_BindGPUComputePipeline(pass,pipeline);
)

JSC_CCALL(compute_samplers,
  SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self);
  JSValue samplers = argv[0];
  int n = JS_ArrayLength(js,samplers);
  SDL_GPUTextureSamplerBinding b[n];
  for (int i = 0; i < n; i++) {
    JSValue s = JS_GetPropertyUint32(js,samplers,i);
    b[i] = js2SDL_GPUTextureSamplerBinding(js,s);
    JS_FreeValue(js,s);
  }
  SDL_BindGPUComputeSamplers(pass,js2number(js,argv[1]),b,n);
)

JSC_CCALL(compute_storage_textures,
  SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self);
  JSValue textures = argv[0];
  int n = JS_ArrayLength(js,textures);
  SDL_GPUTexture *b[n];
  for (int i = 0; i < n; i++) {
    JSValue s = JS_GetPropertyUint32(js,textures,i);
    b[i] = js2SDL_GPUTexture(js,s);
    JS_FreeValue(js,s);
  }
  SDL_BindGPUComputeStorageTextures(pass,js2number(js,argv[1]),b,n);
)

JSC_CCALL(compute_storage_buffers,
  SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self);
  JSValue buffers = argv[0];
  int n = JS_ArrayLength(js,buffers);
  SDL_GPUBuffer *b[n];
  for (int i = 0; i < n; i++) {
    JSValue s = JS_GetPropertyUint32(js,buffers,i);
    b[i] = js2SDL_GPUBuffer(js,s);
    JS_FreeValue(js,s);
  }
  SDL_BindGPUComputeStorageTextures(pass,js2number(js,argv[1]),b,n);
)

static const JSCFunctionListEntry js_SDL_GPUComputePass_funcs[] = {
  MIST_FUNC_DEF(compute, dispatch, 3),
  MIST_FUNC_DEF(compute, end, 0),
  MIST_FUNC_DEF(compute, pipeline, 1),
  MIST_FUNC_DEF(compute, samplers, 2),
  MIST_FUNC_DEF(compute, storage_buffers, 2),
  MIST_FUNC_DEF(compute, storage_textures, 2),
};

JSValue make_gpu_buffer(JSContext *js, void *data, size_t size, int type, int elements, int copy, int index)
{
  JSValue tstack[2];
  if (copy) {
    tstack[0] = js_new_blob_stoned_copy(js,data,size);//, make_gpu_buffer, NULL, 1);
  } else {
    tstack[0] = JS_NewArrayBuffer(js,data,size,free_gpu_buffer, NULL, 0);
  }
  tstack[1] = JS_NewObject(js);
  JS_SetPropertyStr(js,tstack[1],"type",number2js(js,type));
  JS_SetPropertyStr(js,tstack[1],"elements",number2js(js,elements));
  JS_SetPropertyStr(js,tstack[1],"buffer",tstack[0]);
  JS_SetPropertyStr(js,tstack[1],"index", number2js(js,index));
  return tstack[1];
}

void *get_gpu_buffer(JSContext *js, JSValue argv, size_t *stride, size_t *size)
{
  if (JS_IsArray(js,argv)) {
    if (stride) *stride = sizeof(float);
    int arrlen = JS_ArrayLength(js,argv);
    float *arr = malloc(sizeof(float)*arrlen);
    for (int i = 0; i < arrlen; i++) {
      JSValue v = JS_GetPropertyUint32(js,argv,i);
      arr[i] = js2number(js,v);
      JS_FreeValue(js,v);
    }
    if (size) *size = sizeof(float)*arrlen;
    return arr;
  }

  JSValue buffer = JS_GetPropertyStr(js,argv,"buffer");
  void *data = js_get_blob_data(js,NULL,buffer);
  JS_FreeValue(js,buffer);

  JSValue typeval = JS_GetPropertyStr(js,argv,"type");
  int type = js2number(js,typeval);
  JS_FreeValue(js,typeval);

  return data;
}

// Conversion functions for SDL GPU types
SDL_GPUGraphicsPipelineTargetInfo js2SDL_GPUGraphicsPipelineTargetInfo(JSContext *js, JSValue v)
{
  SDL_GPUGraphicsPipelineTargetInfo info = {0};
  return info;
}

SDL_GPUSampleCount js2SDL_GPUSampleCount(JSContext *js, JSValue v)
{
  int n = js2number(js,v);
  switch(n) {
    case 1: return SDL_GPU_SAMPLECOUNT_1;
    case 2: return SDL_GPU_SAMPLECOUNT_2;
    case 4: return SDL_GPU_SAMPLECOUNT_4;
    case 8: return SDL_GPU_SAMPLECOUNT_8;
  }
  return -1;
}

// Enum conversion macro and definitions
#define JS2ENUM(NAME, RETS, VALS)                            \
int js2##NAME(JSContext *js, JSValue v) {                    \
  if (JS_IsNull(v)) return 0;                           \
  const char *str = JS_ToCString(js, v);                     \
  int *rets = (RETS);                                        \
  const char **vals = (VALS);                                \
  /* Compute how many entries are in the arrays */           \
  int n = (int)(sizeof((RETS)) / sizeof((RETS)[0]));         \
  for(int i = 0; i < n; i++)                                 \
    if(!strcmp(vals[i], str)) {                              \
      JS_FreeCString(js, str);                               \
      return rets[i];                                        \
    }                                                        \
  JS_FreeCString(js, str);                                   \
  return 0;                                                 \
}

// SDL GPU Swapchain Composition
static int rets_SDL_GPUSwapchainComposition[] = {
  SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
  SDL_GPU_SWAPCHAINCOMPOSITION_SDR_LINEAR,
  SDL_GPU_SWAPCHAINCOMPOSITION_HDR_EXTENDED_LINEAR,
//  SDL_GPU_SWAPCHAINCOMPOSITION_HDR10_ST2084
};

static const char *vals_SDL_GPUSwapchainComposition[] = {
  "sdr",
  "linear",
  "hdr",
//  "hdr10"
};

JS2ENUM(SDL_GPUSwapchainComposition, rets_SDL_GPUSwapchainComposition, vals_SDL_GPUSwapchainComposition)

// SDL GPU Blend Factor
static int rets_SDL_GPUBlendFactor[] = {
  SDL_GPU_BLENDFACTOR_INVALID,
  SDL_GPU_BLENDFACTOR_ZERO,
  SDL_GPU_BLENDFACTOR_ONE,
  SDL_GPU_BLENDFACTOR_SRC_COLOR,
  SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_COLOR,
  SDL_GPU_BLENDFACTOR_DST_COLOR,
  SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_COLOR,
  SDL_GPU_BLENDFACTOR_SRC_ALPHA,
  SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
  SDL_GPU_BLENDFACTOR_DST_ALPHA,
  SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_ALPHA,
  SDL_GPU_BLENDFACTOR_CONSTANT_COLOR,
  SDL_GPU_BLENDFACTOR_ONE_MINUS_CONSTANT_COLOR,
  SDL_GPU_BLENDFACTOR_SRC_ALPHA_SATURATE
};

static const char *vals_SDL_GPUBlendFactor[] = {
  "invalid",
  "zero",
  "one",
  "src_color",
  "one_minus_src_color",
  "dst_color",
  "one_minus_dst_color",
  "src_alpha",
  "one_minus_src_alpha",
  "dst_alpha",
  "one_minus_dst_alpha",
  "constant_color",
  "one_minus_constant_color",
  "src_alpha_saturate"
};

JS2ENUM(SDL_GPUBlendFactor, rets_SDL_GPUBlendFactor, vals_SDL_GPUBlendFactor)

// GPU FUNCTIONS

JSC_CCALL(SDL_Window_make_gpu,
  if (global_gpu)
    return JS_ThrowReferenceError(js, "A gpu has already been created somewhere.");
  const char *name = JS_ToCString(js,argv[1]);
  SDL_PropertiesID props = SDL_CreateProperties();
  SDL_SetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING, name);
  JS_FreeCString(js,name);
  SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, JS_ToBool(js,argv[0]));
  SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, 1);
  SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN, 1);
  SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN, 1);
  SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN, 1);
  SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN, 1);

  SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN, 1);

  SDL_GPUDevice *gpu = SDL_CreateGPUDeviceWithProperties(props);
  SDL_DestroyProperties(props);

  if (!gpu) return JS_ThrowReferenceError(js, "Could not create GPU device! %s", SDL_GetError());

  global_gpu = gpu;
  return SDL_GPUDevice2js(js,gpu);
)

JSC_CCALL(gpu_claim_window,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
  SDL_Window *w = js2SDL_Window(js,argv[0]);
  if (!SDL_ClaimWindowForGPUDevice(gpu, w))
    return JS_ThrowReferenceError(js, "%s", SDL_GetError());
)

JSC_CCALL(gpu_set_swapchain,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
  SDL_Window *w = js2SDL_Window(js,argv[0]);
  SDL_GPUSwapchainComposition comp = js2SDL_GPUSwapchainComposition(js,argv[1]);
  if (!SDL_SetGPUSwapchainParameters(gpu, w, comp, SDL_GPU_PRESENTMODE_VSYNC))
    return JS_ThrowReferenceError(js, "%s", SDL_GetError());
)

JSC_CCALL(gpu_driver,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
  return JS_NewString(js, SDL_GetGPUDeviceDriver(gpu));
)

JSC_CCALL(gpu_shader_format,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
  SDL_GPUShaderFormat format = SDL_GetGPUShaderFormats(gpu);
  switch(format) {
    case SDL_GPU_SHADERFORMAT_SPIRV: return JS_NewString(js, "spirv");
    case SDL_GPU_SHADERFORMAT_DXBC: return JS_NewString(js, "dxbc");
    case SDL_GPU_SHADERFORMAT_DXIL: return JS_NewString(js, "dxil");
    case SDL_GPU_SHADERFORMAT_MSL: return JS_NewString(js, "msl");
    case SDL_GPU_SHADERFORMAT_METALLIB: return JS_NewString(js, "metallib");
  }
  return JS_NewString(js, "unknown");
)

JSC_CCALL(gpu_acquire_cmd_buffer,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
  SDL_GPUCommandBuffer *cmd = SDL_AcquireGPUCommandBuffer(gpu);
  if (!cmd) return JS_ThrowReferenceError(js, "%s", SDL_GetError());
  return SDL_GPUCommandBuffer2js(js,cmd);
)

JSC_CCALL(gpu_wait_for_fences,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
  int fencecount = js2number(js,argv[1]);
  SDL_GPUFence **fences = malloc(sizeof(SDL_GPUFence*)*fencecount);
  for (int i = 0; i < fencecount; i++) {
    JSValue fenceval = JS_GetPropertyUint32(js,argv[0],i);
    fences[i] = js2SDL_GPUFence(js,fenceval);
    JS_FreeValue(js,fenceval);
  }
  int ok = SDL_WaitForGPUFences(gpu,1,fences,fencecount);
  free(fences);
  if (!ok) return JS_ThrowReferenceError(js, "%s", SDL_GetError());
)

JSC_CCALL(gpu_query_fence,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
  SDL_GPUFence *fence = js2SDL_GPUFence(js,argv[0]);
  return JS_NewBool(js, SDL_QueryGPUFence(gpu,fence));
)

// Function array for SDL_GPUDevice
static const JSCFunctionListEntry js_SDL_GPUDevice_funcs[] = {
  MIST_FUNC_DEF(gpu, claim_window, 1),
  MIST_FUNC_DEF(gpu, set_swapchain, 2),
  MIST_FUNC_DEF(gpu, driver, 0),
  MIST_FUNC_DEF(gpu, acquire_cmd_buffer, 0),
  MIST_FUNC_DEF(gpu, wait_for_fences, 2),
  MIST_FUNC_DEF(gpu, query_fence, 1),
  MIST_FUNC_DEF(gpu, shader_format, 0),
};

// Function array for SDL_Window GPU functions
static const JSCFunctionListEntry js_SDL_Window_gpu_funcs[] = {
  MIST_FUNC_DEF(SDL_Window, make_gpu, 2),
};

JSC_SCALL(texture_name,
  SDL_GPUTexture *texture = js2SDL_GPUTexture(js,self);
  SDL_GPUDevice *gpu;
  JS_GETPROP(js,gpu,self,gpu,SDL_GPUDevice)
  SDL_SetGPUTextureName(gpu,texture,str);
)

static const JSCFunctionListEntry js_SDL_GPUTexture_funcs[] = {
  MIST_FUNC_DEF(texture, name, 1),
};

// Function to register module - GPU classes are registered in main FFI loading
JSValue js_sdl_gpu_use(JSContext *js)
{
  // GPU classes are registered via the main FFI loading system
  // This module doesn't export its own functions, it just provides class definitions
  return JS_NULL;
}
