#include "qjs_sdl_gpu.h"
#include "jsffi.h"
#include "qjs_macros.h"
#include "qjs_sdl_surface.h"
#include "qjs_blob.h"
#include "quickjs.h"

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

// Macro for GPU wrapper classes that need cleanup
#define QJSCLASSGPUWRAPPER(WRAPPERTYPE, SDLTYPE) \
typedef struct { \
  SDL_GPUDevice *device; \
  JSValue js_device; \
  SDL_##SDLTYPE *type; \
} WRAPPERTYPE; \
JSClassID js_SDL_##SDLTYPE##_id; \
static void js_SDL_##SDLTYPE##_finalizer(JSRuntime *rt, JSValue val) { \
  WRAPPERTYPE *wrapper = JS_GetOpaque(val, js_SDL_##SDLTYPE##_id); \
  JS_FreeValueRT(rt, wrapper->js_device); \
  if (wrapper && wrapper->device && wrapper->type) \
    SDL_Release##SDLTYPE(wrapper->device, wrapper->type); \
  free(wrapper); \
} \
static JSClassDef js_SDL_##SDLTYPE##_class = { \
  .class_name = #SDLTYPE, \
  .finalizer = js_SDL_##SDLTYPE##_finalizer, \
}; \
SDL_##SDLTYPE *js2SDL_##SDLTYPE(JSContext *js, JSValue val) { \
  if (JS_GetClassID(val) != js_SDL_##SDLTYPE##_id) return NULL; \
  WRAPPERTYPE *wrapper = JS_GetOpaque(val, js_SDL_##SDLTYPE##_id); \
  return wrapper ? wrapper->type : NULL; \
} \
JSValue SDL_##SDLTYPE##2js(JSContext *js, JSValue device, SDL_##SDLTYPE *member) { \
  WRAPPERTYPE *wrapper = malloc(sizeof(WRAPPERTYPE)); \
  wrapper->js_device = JS_DupValue(js,device); \
  wrapper->device = js2SDL_GPUDevice(js, device); \
  wrapper->type = member; \
  JSValue j = JS_NewObjectClass(js, js_SDL_##SDLTYPE##_id); \
  JS_SetOpaque(j, wrapper); \
  return j; \
}

extern double js2number(JSContext *js, JSValue v);
extern int JS_GETBOOL(JSContext *js, JSValue obj, const char *prop);
extern double js_getnum_str(JSContext *js, JSValue obj, const char *str);

// Simple string conversion helper
const char *js2cstring(JSContext *js, JSValue v)
{
  return JS_ToCString(js, v);
}

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

void SDL_GPUCommandBuffer_free(JSRuntime *rt, void *w)
{
}

void gpu_command_buffer_wrapper_free(JSRuntime *rt, void *w)
{
}

// Moved to jsffi.c - extern declaration

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
QJSCLASS(SDL_GPUDevice,)
QJSCLASSGPUWRAPPER(gpu_buffer_wrapper, GPUBuffer)
QJSCLASSGPUWRAPPER(gpu_compute_pipeline_wrapper, GPUComputePipeline)
QJSCLASSGPUWRAPPER(gpu_graphics_pipeline_wrapper, GPUGraphicsPipeline)
QJSCLASSGPUWRAPPER(gpu_sampler_wrapper, GPUSampler)
QJSCLASSGPUWRAPPER(gpu_shader_wrapper, GPUShader)
QJSCLASSGPUWRAPPER(gpu_texture_wrapper, GPUTexture)
QJSCLASSGPUWRAPPER(gpu_transfer_buffer_wrapper, GPUTransferBuffer)
QJSCLASSGPUWRAPPER(gpu_fence_wrapper, GPUFence)
QJSCLASS(SDL_GPUCommandBuffer,)
QJSCLASS(SDL_GPUComputePass,)
QJSCLASS(SDL_GPUCopyPass,)
QJSCLASS(SDL_GPURenderPass,)

// GPU type conversion functions
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 SDL_GPU_SAMPLECOUNT_1;
}

// X-macro enum definition system
#define ENUM_MAPPING_TABLE(ENUM) \
  static const struct { int value; const char *name; } ENUM##_mapping[]

#define JS2ENUM(NAME)                                         \
int js2##NAME(JSContext *js, JSValue v) {                    \
  if (JS_IsNull(v)) return 0;                                \
  const char *str = JS_ToCString(js, v);                     \
  if (!str) return 0;                                        \
  for(int i = 0; i < sizeof(NAME##_mapping)/sizeof(NAME##_mapping[0]); i++) \
    if(!strcmp(NAME##_mapping[i].name, str)) {               \
      JS_FreeCString(js, str);                               \
      return NAME##_mapping[i].value;                        \
    }                                                        \
  JS_FreeCString(js, str);                                   \
  return 0;                                                  \
}                                                            \
JSValue NAME##2js(JSContext *js, int enumval) {              \
  for(int i = 0; i < sizeof(NAME##_mapping)/sizeof(NAME##_mapping[0]); i++) \
    if(NAME##_mapping[i].value == enumval)                   \
      return JS_NewString(js, NAME##_mapping[i].name);       \
  return JS_NULL;                                            \
}

// Enum conversion tables using x-macro technique
ENUM_MAPPING_TABLE(SDL_GPUSwapchainComposition) = {
  {SDL_GPU_SWAPCHAINCOMPOSITION_SDR, "sdr"},
  {SDL_GPU_SWAPCHAINCOMPOSITION_SDR_LINEAR, "linear"},
  {SDL_GPU_SWAPCHAINCOMPOSITION_HDR_EXTENDED_LINEAR, "hdr"},
  {SDL_GPU_SWAPCHAINCOMPOSITION_HDR10_ST2084, "hdr10"}
};

JS2ENUM(SDL_GPUSwapchainComposition)

ENUM_MAPPING_TABLE(SDL_FlipMode) = {
  {SDL_FLIP_NONE, "none"},
  {SDL_FLIP_HORIZONTAL, "horizontal"},
  {SDL_FLIP_VERTICAL, "vertical"}
};

JS2ENUM(SDL_FlipMode)

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

ENUM_MAPPING_TABLE(SDL_GPUBlendFactor) = {
  {SDL_GPU_BLENDFACTOR_INVALID, "invalid"},
  {SDL_GPU_BLENDFACTOR_ZERO, "zero"},
  {SDL_GPU_BLENDFACTOR_ONE, "one"},
  {SDL_GPU_BLENDFACTOR_SRC_COLOR, "src_color"},
  {SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_COLOR, "one_minus_src_color"},
  {SDL_GPU_BLENDFACTOR_DST_COLOR, "dst_color"},
  {SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_COLOR, "one_minus_dst_color"},
  {SDL_GPU_BLENDFACTOR_SRC_ALPHA, "src_alpha"},
  {SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, "one_minus_src_alpha"},
  {SDL_GPU_BLENDFACTOR_DST_ALPHA, "dst_alpha"},
  {SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_ALPHA, "one_minus_dst_alpha"},
  {SDL_GPU_BLENDFACTOR_CONSTANT_COLOR, "constant_color"},
  {SDL_GPU_BLENDFACTOR_ONE_MINUS_CONSTANT_COLOR, "one_minus_constant_color"},
  {SDL_GPU_BLENDFACTOR_SRC_ALPHA_SATURATE, "src_alpha_saturate"}
};

JS2ENUM(SDL_GPUBlendFactor)

ENUM_MAPPING_TABLE(SDL_GPUBlendOp) = {
  {SDL_GPU_BLENDOP_INVALID, "invalid"},
  {SDL_GPU_BLENDOP_ADD, "add"},
  {SDL_GPU_BLENDOP_SUBTRACT, "subtract"},
  {SDL_GPU_BLENDOP_REVERSE_SUBTRACT, "reverse_subtract"},
  {SDL_GPU_BLENDOP_MIN, "min"},
  {SDL_GPU_BLENDOP_MAX, "max"}
};

JS2ENUM(SDL_GPUBlendOp)


ENUM_MAPPING_TABLE(SDL_GPUCompareOp) = {
  {SDL_GPU_COMPAREOP_INVALID, "invalid"},
  {SDL_GPU_COMPAREOP_NEVER, "never"},
  {SDL_GPU_COMPAREOP_LESS, "less"},
  {SDL_GPU_COMPAREOP_EQUAL, "equal"},
  {SDL_GPU_COMPAREOP_LESS_OR_EQUAL, "less_or_equal"},
  {SDL_GPU_COMPAREOP_GREATER, "greater"},
  {SDL_GPU_COMPAREOP_NOT_EQUAL, "not_equal"},
  {SDL_GPU_COMPAREOP_GREATER_OR_EQUAL, "greater_or_equal"},
  {SDL_GPU_COMPAREOP_ALWAYS, "always"}
};

JS2ENUM(SDL_GPUCompareOp)

ENUM_MAPPING_TABLE(SDL_GPUCullMode) = {
  {SDL_GPU_CULLMODE_NONE, "none"},
  {SDL_GPU_CULLMODE_FRONT, "front"},
  {SDL_GPU_CULLMODE_BACK, "back"}
};

JS2ENUM(SDL_GPUCullMode)

ENUM_MAPPING_TABLE(SDL_GPUFillMode) = {
  {SDL_GPU_FILLMODE_FILL, "fill"},
  {SDL_GPU_FILLMODE_LINE, "line"}
};

JS2ENUM(SDL_GPUFillMode)

ENUM_MAPPING_TABLE(SDL_GPUFilter) = {
  {SDL_GPU_FILTER_NEAREST, "nearest"},
  {SDL_GPU_FILTER_LINEAR, "linear"}
};

JS2ENUM(SDL_GPUFilter)

ENUM_MAPPING_TABLE(SDL_GPUFrontFace) = {
  {SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE, "counter_clockwise"},
  {SDL_GPU_FRONTFACE_CLOCKWISE, "clockwise"}
};

JS2ENUM(SDL_GPUFrontFace)

ENUM_MAPPING_TABLE(SDL_GPULoadOp) = {
  {SDL_GPU_LOADOP_LOAD, "load"},
  {SDL_GPU_LOADOP_CLEAR, "clear"},
  {SDL_GPU_LOADOP_DONT_CARE, "dont_care"}
};

JS2ENUM(SDL_GPULoadOp)

ENUM_MAPPING_TABLE(SDL_GPUPresentMode) = {
  {SDL_GPU_PRESENTMODE_VSYNC, "vsync"},
  {SDL_GPU_PRESENTMODE_IMMEDIATE, "immediate"},
  {SDL_GPU_PRESENTMODE_MAILBOX, "mailbox"}
};

JS2ENUM(SDL_GPUPresentMode)

ENUM_MAPPING_TABLE(SDL_GPUPrimitiveType) = {
  {SDL_GPU_PRIMITIVETYPE_TRIANGLELIST, "triangle"},
  {SDL_GPU_PRIMITIVETYPE_TRIANGLESTRIP, "trianglestrip"},
  {SDL_GPU_PRIMITIVETYPE_LINELIST, "line"},
  {SDL_GPU_PRIMITIVETYPE_LINESTRIP, "linestrip"},
  {SDL_GPU_PRIMITIVETYPE_POINTLIST, "point"}
};

JS2ENUM(SDL_GPUPrimitiveType)

ENUM_MAPPING_TABLE(SDL_GPUSamplerAddressMode) = {
  {SDL_GPU_SAMPLERADDRESSMODE_REPEAT, "repeat"},
  {SDL_GPU_SAMPLERADDRESSMODE_MIRRORED_REPEAT, "mirrored_repeat"},
  {SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, "clamp"}
};

JS2ENUM(SDL_GPUSamplerAddressMode)

ENUM_MAPPING_TABLE(SDL_GPUSamplerMipmapMode) = {
  {SDL_GPU_SAMPLERMIPMAPMODE_NEAREST, "nearest"},
  {SDL_GPU_SAMPLERMIPMAPMODE_LINEAR, "linear"}
};

JS2ENUM(SDL_GPUSamplerMipmapMode)

ENUM_MAPPING_TABLE(SDL_GPUStencilOp) = {
  {SDL_GPU_STENCILOP_INVALID, "invalid"},
  {SDL_GPU_STENCILOP_KEEP, "keep"},
  {SDL_GPU_STENCILOP_ZERO, "zero"},
  {SDL_GPU_STENCILOP_REPLACE, "replace"},
  {SDL_GPU_STENCILOP_INCREMENT_AND_CLAMP, "increment_and_clamp"},
  {SDL_GPU_STENCILOP_DECREMENT_AND_CLAMP, "decrement_and_clamp"},
  {SDL_GPU_STENCILOP_INVERT, "invert"},
  {SDL_GPU_STENCILOP_INCREMENT_AND_WRAP, "increment_and_wrap"},
  {SDL_GPU_STENCILOP_DECREMENT_AND_WRAP, "decrement_and_wrap"}
};

JS2ENUM(SDL_GPUStencilOp)

ENUM_MAPPING_TABLE(SDL_GPUTextureType) = {
  {SDL_GPU_TEXTURETYPE_2D, "2d"},
  {SDL_GPU_TEXTURETYPE_2D_ARRAY, "2d array"},
  {SDL_GPU_TEXTURETYPE_3D, "3d"},
  {SDL_GPU_TEXTURETYPE_CUBE, "cube"},
  {SDL_GPU_TEXTURETYPE_CUBE_ARRAY, "cube array"}
};

JS2ENUM(SDL_GPUTextureType)

ENUM_MAPPING_TABLE(SDL_GPUStoreOp) = {
  {SDL_GPU_STOREOP_STORE, "store"},
  {SDL_GPU_STOREOP_DONT_CARE, "dont_care"},
  {SDL_GPU_STOREOP_RESOLVE, "resolve"},
  {SDL_GPU_STOREOP_RESOLVE_AND_STORE, "resolve_and_store"}
};

JS2ENUM(SDL_GPUStoreOp)

ENUM_MAPPING_TABLE(SDL_GPUTextureFormat) = {
  {SDL_GPU_TEXTUREFORMAT_INVALID, "invalid"},
  {SDL_GPU_TEXTUREFORMAT_A8_UNORM, "a8"},
  {SDL_GPU_TEXTUREFORMAT_R8_UNORM, "r8"},
  {SDL_GPU_TEXTUREFORMAT_R8G8_UNORM, "rg8"},
  {SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, "rgba8"},
  {SDL_GPU_TEXTUREFORMAT_R16_UNORM, "r16"},
  {SDL_GPU_TEXTUREFORMAT_R16G16_UNORM, "rg16"},
  {SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM, "rgba16"},
  {SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM, "r10g10b10a2"},
  {SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM, "b5g6r5"},
  {SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM, "b5g5r5a1"},
  {SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM, "b4g4r4a4"},
  {SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM, "bgra8"},
  {SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM, "bc1"},
  {SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM, "bc2"},
  {SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM, "bc3"},
  {SDL_GPU_TEXTUREFORMAT_BC4_R_UNORM, "bc4"},
  {SDL_GPU_TEXTUREFORMAT_BC5_RG_UNORM, "bc5"},
  {SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM, "bc7"},
  {SDL_GPU_TEXTUREFORMAT_BC6H_RGB_FLOAT, "bc6h float"},
  {SDL_GPU_TEXTUREFORMAT_BC6H_RGB_UFLOAT, "bc6h ufloat"},
  {SDL_GPU_TEXTUREFORMAT_R8_SNORM, "r8 snorm"},
  {SDL_GPU_TEXTUREFORMAT_R8G8_SNORM, "rg8 snorm"},
  {SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM, "rgba8 snorm"},
  {SDL_GPU_TEXTUREFORMAT_R16_SNORM, "r16 snorm"},
  {SDL_GPU_TEXTUREFORMAT_R16G16_SNORM, "rg16 snorm"},
  {SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM, "rgba16 snorm"},
  {SDL_GPU_TEXTUREFORMAT_R16_FLOAT, "r16 float"},
  {SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT, "rg16 float"},
  {SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT, "rgba16 float"},
  {SDL_GPU_TEXTUREFORMAT_R32_FLOAT, "r32 float"},
  {SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT, "rg32 float"},
  {SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, "rgba32 float"},
  {SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT, "r11g11b10"},
  {SDL_GPU_TEXTUREFORMAT_R8_UINT, "r8 uint"},
  {SDL_GPU_TEXTUREFORMAT_R8G8_UINT, "rg8 uint"},
  {SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT, "rgba8 uint"},
  {SDL_GPU_TEXTUREFORMAT_R16_UINT, "r16 uint"},
  {SDL_GPU_TEXTUREFORMAT_R16G16_UINT, "rg16 uint"},
  {SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT, "rgba16 uint"},
  {SDL_GPU_TEXTUREFORMAT_R32_UINT, "r32 uint"},
  {SDL_GPU_TEXTUREFORMAT_R32G32_UINT, "rg32 uint"},
  {SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT, "rgba32 uint"},
  {SDL_GPU_TEXTUREFORMAT_R8_INT, "r8 int"},
  {SDL_GPU_TEXTUREFORMAT_R8G8_INT, "rg8 int"},
  {SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT, "rgba8 int"},
  {SDL_GPU_TEXTUREFORMAT_R16_INT, "r16 int"},
  {SDL_GPU_TEXTUREFORMAT_R16G16_INT, "rg16 int"},
  {SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT, "rgba16 int"},
  {SDL_GPU_TEXTUREFORMAT_R32_INT, "r32 int"},
  {SDL_GPU_TEXTUREFORMAT_R32G32_INT, "rg32 int"},
  {SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT, "rgba32 int"},
  {SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB, "rgba8 srgb"},
  {SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB, "b8g8r8a8 srgb"},
  {SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB, "bc1 srgb"},
  {SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB, "bc2 srgb"},
  {SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB, "bc3 srgb"},
  {SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB, "bc7 srgb"},
  {SDL_GPU_TEXTUREFORMAT_D16_UNORM, "d16"},
  {SDL_GPU_TEXTUREFORMAT_D24_UNORM, "d24"},
  {SDL_GPU_TEXTUREFORMAT_D32_FLOAT, "d32 float"},
  {SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT, "d24 s8"},
  {SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT, "d32 float s8"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM, "astc 4x4"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM, "astc 5x4"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM, "astc 5x5"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM, "astc 6x5"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM, "astc 6x6"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM, "astc 8x5"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM, "astc 8x6"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM, "astc 8x8"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM, "astc 10x5"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM, "astc 10x6"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM, "astc 10x8"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM, "astc 10x10"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM, "astc 12x10"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM, "astc 12x12"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM_SRGB, "astc 4x4 srgb"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM_SRGB, "astc 5x4 srgb"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM_SRGB, "astc 5x5 srgb"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM_SRGB, "astc 6x5 srgb"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM_SRGB, "astc 6x6 srgb"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM_SRGB, "astc 8x5 srgb"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM_SRGB, "astc 8x6 srgb"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM_SRGB, "astc 8x8 srgb"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM_SRGB, "astc 10x5 srgb"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM_SRGB, "astc 10x6 srgb"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM_SRGB, "astc 10x8 srgb"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM_SRGB, "astc 10x10 srgb"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM_SRGB, "astc 12x10 srgb"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM_SRGB, "astc 12x12 srgb"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_4x4_FLOAT, "astc 4x4 float"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_5x4_FLOAT, "astc 5x4 float"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_5x5_FLOAT, "astc 5x5 float"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_6x5_FLOAT, "astc 6x5 float"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_6x6_FLOAT, "astc 6x6 float"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_8x5_FLOAT, "astc 8x5 float"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_8x6_FLOAT, "astc 8x6 float"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_8x8_FLOAT, "astc 8x8 float"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_10x5_FLOAT, "astc 10x5 float"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_10x6_FLOAT, "astc 10x6 float"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_10x8_FLOAT, "astc 10x8 float"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_10x10_FLOAT, "astc 10x10 float"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_12x10_FLOAT, "astc 12x10 float"},
  {SDL_GPU_TEXTUREFORMAT_ASTC_12x12_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_color,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;
}

SDL_GPUBufferBinding js2SDL_GPUBufferBinding(JSContext *js, JSValue v)
{
  SDL_GPUBufferBinding binding = {0};
  JS_GETPROP(js, binding.buffer, v, buffer, SDL_GPUBuffer)
  JS_GETPROP(js, binding.offset, v, offset, number)
  return binding;
}

JS2ENUM(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,dst_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;
}

ENUM_MAPPING_TABLE(SDL_GPUShaderFormat) = {
  {SDL_GPU_SHADERFORMAT_PRIVATE, "private"},
  {SDL_GPU_SHADERFORMAT_SPIRV, "spv"},
  {SDL_GPU_SHADERFORMAT_DXBC, "dxbc"},
  {SDL_GPU_SHADERFORMAT_DXIL, "dxil"},
  {SDL_GPU_SHADERFORMAT_MSL, "msl"},
  {SDL_GPU_SHADERFORMAT_METALLIB, "metallib"}
};

JS2ENUM(SDL_GPUShaderFormat)

ENUM_MAPPING_TABLE(SDL_GPUVertexInputRate) = {
  {SDL_GPU_VERTEXINPUTRATE_VERTEX, "vertex"},
  {SDL_GPU_VERTEXINPUTRATE_INSTANCE, "instance"}
};

JS2ENUM(SDL_GPUVertexInputRate)

ENUM_MAPPING_TABLE(SDL_GPUTransferBufferUsage) = {
  {SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, "upload"},
  {SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD, "download"}
};

JS2ENUM(SDL_GPUTransferBufferUsage)

ENUM_MAPPING_TABLE(SDL_GPUVertexElementFormat) = {
  {SDL_GPU_VERTEXELEMENTFORMAT_INVALID, "invalid"},
  {SDL_GPU_VERTEXELEMENTFORMAT_INT, "int"},
  {SDL_GPU_VERTEXELEMENTFORMAT_INT2, "int2"},
  {SDL_GPU_VERTEXELEMENTFORMAT_INT3, "int3"},
  {SDL_GPU_VERTEXELEMENTFORMAT_INT4, "int4"},
  {SDL_GPU_VERTEXELEMENTFORMAT_UINT, "uint"},
  {SDL_GPU_VERTEXELEMENTFORMAT_UINT2, "uint2"},
  {SDL_GPU_VERTEXELEMENTFORMAT_UINT3, "uint3"},
  {SDL_GPU_VERTEXELEMENTFORMAT_UINT4, "uint4"},
  {SDL_GPU_VERTEXELEMENTFORMAT_FLOAT, "float"},
  {SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, "float2"},
  {SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, "float3"},
  {SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4, "float4"},
  {SDL_GPU_VERTEXELEMENTFORMAT_BYTE2, "byte2"},
  {SDL_GPU_VERTEXELEMENTFORMAT_BYTE4, "byte4"},
  {SDL_GPU_VERTEXELEMENTFORMAT_UBYTE2, "ubyte2"},
  {SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4, "ubyte4"},
  {SDL_GPU_VERTEXELEMENTFORMAT_BYTE2_NORM, "byte2_norm"},
  {SDL_GPU_VERTEXELEMENTFORMAT_BYTE4_NORM, "byte4_norm"},
  {SDL_GPU_VERTEXELEMENTFORMAT_UBYTE2_NORM, "ubyte2_norm"},
  {SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4_NORM, "ubyte4_norm"},
  {SDL_GPU_VERTEXELEMENTFORMAT_SHORT2, "short2"},
  {SDL_GPU_VERTEXELEMENTFORMAT_SHORT4, "short4"},
  {SDL_GPU_VERTEXELEMENTFORMAT_USHORT2, "ushort2"},
  {SDL_GPU_VERTEXELEMENTFORMAT_USHORT4, "ushort4"},
  {SDL_GPU_VERTEXELEMENTFORMAT_SHORT2_NORM, "short2_norm"},
  {SDL_GPU_VERTEXELEMENTFORMAT_SHORT4_NORM, "short4_norm"},
  {SDL_GPU_VERTEXELEMENTFORMAT_USHORT2_NORM, "ushort2_norm"},
  {SDL_GPU_VERTEXELEMENTFORMAT_USHORT4_NORM, "ushort4_norm"},
  {SDL_GPU_VERTEXELEMENTFORMAT_HALF2, "half2"},
  {SDL_GPU_VERTEXELEMENTFORMAT_HALF4, "half4"}
};

JS2ENUM(SDL_GPUVertexElementFormat)

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;
}

JSC_CCALL(gpu_claim_window,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
  SDL_Window *win = js2SDL_Window(js, argv[0]);
  if (!SDL_ClaimWindowForGPUDevice(gpu,win))
    return JS_ThrowInternalError(js, "couldn't claim window for GPU device: %s", SDL_GetError());
)

JSC_CCALL(gpu_set_swapchain,
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, self);
  SDL_Window *window = js2SDL_Window(js, argv[0]);
  if (!SDL_SetGPUSwapchainParameters(gpu, window, js2SDL_GPUSwapchainComposition(js,argv[1]), js2SDL_GPUPresentMode(js,argv[2])))
    return JS_ThrowReferenceError(js, "Could not set: %s\n", SDL_GetError());  
)

static JSValue js_gpu_graphics_pipeline_constructor(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  if (argc < 2)
    return JS_ThrowTypeError(js, "graphics pipeline constructor requires device and config parameters");

  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, argv[0]);
  if (!gpu) return JS_ThrowTypeError(js, "Invalid GPU device");

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

  SDL_GPUGraphicsPipelineCreateInfo info = {0};

  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");

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

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

          SDL_GPUVertexInputRate input_rate = js2SDL_GPUVertexInputRate(js, rate_val);
          JS_FreeValue(js, rate_val);

          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);

  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");

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

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

          SDL_GPUVertexElementFormat format = js2SDL_GPUVertexElementFormat(js, fmt_val);
          JS_FreeValue(js, fmt_val);

          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
  };

  JS_GETPROP(js,info.vertex_shader, pipe, vertex,SDL_GPUShader)
  JS_GETPROP(js, info.fragment_shader, pipe, fragment, SDL_GPUShader)
  
  JS_GETPROP(js, info.primitive_type, pipe, primitive, SDL_GPUPrimitiveType)
  
  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)
  
  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);

  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: %s", SDL_GetError());

    return SDL_GPUGraphicsPipeline2js(js, argv[0], pipeline);
}

// Standalone sampler constructor: new sdl_gpu.sampler(device, config)
static JSValue js_gpu_sampler_constructor(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  if (argc < 2) return JS_ThrowTypeError(js, "sampler constructor requires device and config parameters");
  
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, argv[0]);
  if (!gpu) return JS_ThrowTypeError(js, "Invalid GPU device");
  
  SDL_GPUSamplerCreateInfo info = {0};
  JSValue sampler = argv[1];
  
  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: %s", SDL_GetError());

  return SDL_GPUSampler2js(js, argv[0], sdl_sampler);
}

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

// Standalone shader constructor: new sdl_gpu.shader(device, config)
static JSValue js_gpu_shader_constructor(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  if (argc < 2 || !JS_IsObject(argv[1]))
    return JS_ThrowTypeError(js, "shader constructor requires device and config parameters");

  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, argv[0]);
  if (!gpu) return JS_ThrowTypeError(js, "Invalid GPU device");

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

  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 a blob");

  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
  
  SDL_GPUShader *shader = SDL_CreateGPUShader(gpu, &info);
  
  JS_FreeCString(js,info.entrypoint);
  if (!shader)
    return JS_ThrowReferenceError(js, "Unable to create shader: %s", SDL_GetError());

  return SDL_GPUShader2js(js, argv[0], 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());
  return SDL_GPUCommandBuffer2js(js, cb);
)

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;
)

// Standalone compute pipeline constructor: new sdl_gpu.compute_pipeline(device, config)
static JSValue js_gpu_compute_pipeline_constructor(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  if (argc < 2) return JS_ThrowTypeError(js, "compute pipeline constructor requires device and config parameters");
  
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, argv[0]);
  if (!gpu) return JS_ThrowTypeError(js, "Invalid GPU device");
  
  SDL_GPUComputePipelineCreateInfo info = {0};
  JSValue pipe = argv[1];
  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, argv[0], pipeline);
}

// Standalone buffer constructor: new sdl_gpu.buffer(device, config)
static JSValue js_gpu_buffer_constructor(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  if (argc < 2) return JS_ThrowTypeError(js, "buffer constructor requires device and config parameters");
  
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, argv[0]);
  if (!gpu) return JS_ThrowTypeError(js, "Invalid GPU device");
  
  SDL_GPUBufferCreateInfo info = {0};
  JSValue config = argv[1];
  
  // Get size - this is required
  JS_GETPROP(js, info.size, config, size, number)
  if (info.size == 0) return JS_ThrowTypeError(js, "Buffer size must be greater than 0");
  
  // Parse usage flags
  if (JS_GETBOOL(js, config, "vertex"))
    info.usage |= SDL_GPU_BUFFERUSAGE_VERTEX;
  if (JS_GETBOOL(js, config, "index"))
    info.usage |= SDL_GPU_BUFFERUSAGE_INDEX;
  if (JS_GETBOOL(js, config, "indirect"))
    info.usage |= SDL_GPU_BUFFERUSAGE_INDIRECT;
  if (JS_GETBOOL(js, config, "graphics_storage_read"))
    info.usage |= SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ;
  if (JS_GETBOOL(js, config, "compute_storage_read"))
    info.usage |= SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_READ;
  if (JS_GETBOOL(js, config, "compute_storage_write"))
    info.usage |= SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_WRITE;
  
  if (info.usage == 0) return JS_ThrowTypeError(js, "Buffer must have at least one usage flag");
  
  SDL_GPUBuffer *buffer = SDL_CreateGPUBuffer(gpu, &info);
  if (!buffer) return JS_ThrowReferenceError(js, "Unable to create buffer: %s", SDL_GetError());
  
  return SDL_GPUBuffer2js(js, argv[0], buffer);
}

static JSValue js_gpu_transfer_buffer_constructor(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  if (argc < 2) return JS_ThrowTypeError(js, "transfer_buffer constructor requires device and config parameters");
  
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, argv[0]);
  if (!gpu) return JS_ThrowTypeError(js, "Invalid GPU device");
  
  SDL_GPUTransferBufferCreateInfo info = {0};
  JSValue config = argv[1];
  
  // Get size - this is required
  JS_GETPROP(js, info.size, config, size, number)
  if (info.size == 0) return JS_ThrowTypeError(js, "Transfer buffer size must be greater than 0");
  
  JS_GETPROP(js, info.usage, config, usage, SDL_GPUTransferBufferUsage)
  
  SDL_GPUTransferBuffer *buffer = SDL_CreateGPUTransferBuffer(gpu, &info);
  if (!buffer) return JS_ThrowReferenceError(js, "Unable to create transfer buffer: %s", SDL_GetError());
  
  return SDL_GPUTransferBuffer2js(js, argv[0], buffer);
}

// Standalone texture constructor: new sdl_gpu.texture(device, config)
static JSValue js_gpu_texture_constructor(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  if (argc < 2) return JS_ThrowTypeError(js, "texture constructor requires device and config parameters");
  
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, argv[0]);
  if (!gpu) return JS_ThrowTypeError(js, "Invalid GPU device");
  
  SDL_GPUTextureCreateInfo info = {0};
  JSValue fmt = argv[1];
  
  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)
  
  // Parse usage flags
  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, argv[0], 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;
}

JSC_CCALL(gpu_swapchain_format,
  SDL_GPUDevice *dev = js2SDL_GPUDevice(js, self);
  SDL_Window *win = js2SDL_Window(js, argv[0]);
  return SDL_GPUTextureFormat2js(js, SDL_GetGPUSwapchainTextureFormat(dev, win));
)

static const JSCFunctionListEntry js_SDL_GPUDevice_funcs[] = {
  MIST_FUNC_DEF(gpu, claim_window, 1),
  MIST_FUNC_DEF(gpu, set_swapchain, 3),
  MIST_FUNC_DEF(gpu, swapchain_format, 1),
  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),
};

JSC_CCALL(renderpass_bind_pipeline,
  SDL_GPURenderPass *r = js2SDL_GPURenderPass(js,self);
  if (!r) return JS_ThrowInternalError(js, "Invalid render pass");
  SDL_GPUGraphicsPipeline *pipe = js2SDL_GPUGraphicsPipeline(js,argv[0]);
  if (!pipe) return JS_ThrowInternalError(js, "Invalid graphics pipeline");
  SDL_BindGPUGraphicsPipeline(r,pipe);
)

JSC_CCALL(renderpass_draw_indexed,
  SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
  if (!pass) return JS_ThrowInternalError(js, "Invalid render pass");
  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);
  if (!pass) return JS_ThrowInternalError(js, "Invalid render pass");
  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] = js2SDL_GPUBufferBinding(js, 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 = js2SDL_GPUBufferBinding(js, argv[0]);
  int elen = js2number(js,argv[1]);
  SDL_BindGPUIndexBuffer(pass,&bind,elen == 16 ? SDL_GPU_INDEXELEMENTSIZE_16BIT : SDL_GPU_INDEXELEMENTSIZE_32BIT);
)

JSC_CCALL(renderpass_end,
  SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
  if (!pass) return JS_ThrowInternalError(js, "Invalid render pass");
  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);
    binds[i] = js2SDL_GPUTextureSamplerBinding(js, val);
    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);
)

JSC_CCALL(renderpass_bind_vertex_samplers,
  SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
  int first_slot = js2number(js,argv[0]);
  JSValue arr = argv[1];
  int num = JS_ArrayLength(js,arr);
  SDL_GPUTextureSamplerBinding binds[num];
  for (int i = 0; i < num; i++) {
    JSValue val = JS_GetPropertyUint32(js,arr,i);
    binds[i] = js2SDL_GPUTextureSamplerBinding(js,val);
    JS_FreeValue(js,val);
  }
  SDL_BindGPUVertexSamplers(pass, first_slot, binds, num);
)

JSC_CCALL(renderpass_bind_fragment_samplers,
  SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
  int first_slot = js2number(js,argv[0]);
  JSValue arr = argv[1];
  int num = JS_ArrayLength(js,arr);
  SDL_GPUTextureSamplerBinding binds[num];
  for (int i = 0; i < num; i++) {
    JSValue val = JS_GetPropertyUint32(js,arr,i);
    binds[i] = js2SDL_GPUTextureSamplerBinding(js,val);
    JS_FreeValue(js,val);
  }
  SDL_BindGPUFragmentSamplers(pass, first_slot, binds, num);
)

JSC_CCALL(renderpass_bind_vertex_buffers,
  SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
  int first_slot = js2number(js,argv[0]);
  JSValue arr = argv[1];
  int num = JS_ArrayLength(js,arr);
  SDL_GPUBufferBinding binds[num];
  for (int i = 0; i < num; i++) {
    JSValue val = JS_GetPropertyUint32(js,arr,i);
    JS_GETPROP(js, binds[i].buffer, val, buffer, SDL_GPUBuffer)
    JS_GETPROP(js, binds[i].offset, val, offset, number)
    JS_FreeValue(js,val);
  }
  SDL_BindGPUVertexBuffers(pass, first_slot, binds, num);
)

JSC_CCALL(renderpass_set_stencil_reference,
  SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js, self);
  Uint8 ref = js2number(js,argv[0]);
  SDL_SetGPUStencilReference(pass, ref);
)

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, set_stencil_reference, 1),
  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_vertex_samplers, 2),
  MIST_FUNC_DEF(renderpass, bind_fragment_samplers, 2),
  MIST_FUNC_DEF(renderpass, bind_vertex_buffers, 2),
  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: %s", SDL_GetError());

  return SDL_GPURenderPass2js(js, pass);
)

JSC_CCALL(cmd_copy_pass,
  SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
  SDL_GPUCopyPass *pass = SDL_BeginGPUCopyPass(cmds);
  if (!pass) return JS_ThrowInternalError(js, "copy_pass: Failed to begin copy pass: %s", SDL_GetError());
  return SDL_GPUCopyPass2js(js, pass);
)

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_SubmitGPUCommandBuffer(cmds);
)

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_swapchain_pass,
  SDL_GPUCommandBuffer *cmdbuf = js2SDL_GPUCommandBuffer(js, self);
  SDL_Window *window = js2SDL_Window(js, argv[0]);
  
  SDL_GPUTexture* swapchainTexture;
  if (!SDL_WaitAndAcquireGPUSwapchainTexture(cmdbuf, window, &swapchainTexture, NULL, NULL)) {
    return JS_ThrowReferenceError(js, "WaitAndAcquireGPUSwapchainTexture failed: %s", SDL_GetError());
  }
  
  if (swapchainTexture == NULL) {
    return JS_ThrowReferenceError(js, "Failed to acquire swapchain texture");
  }
  
  SDL_GPUColorTargetInfo colorTargetInfo = { 0 };
  colorTargetInfo.texture = swapchainTexture;
  colorTargetInfo.clear_color = (SDL_FColor){ 0.0f, 0.0f, 0.0f, 1.0f };
  colorTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR;
  colorTargetInfo.store_op = SDL_GPU_STOREOP_STORE;
  
  SDL_GPURenderPass* renderPass = SDL_BeginGPURenderPass(cmdbuf, &colorTargetInfo, 1, NULL);
  if (!renderPass) {
    return JS_ThrowReferenceError(js, "Failed to begin render pass: %s", SDL_GetError());
  }
  
  return SDL_GPURenderPass2js(js, renderPass);
)

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);
)

JSC_CCALL(cmd_generate_mipmaps,
  SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self);
  SDL_GPUTexture *tex = js2SDL_GPUTexture(js,argv[0]);
  SDL_GenerateMipmapsForGPUTexture(cmd,tex);
)

static const JSCFunctionListEntry js_SDL_GPUCommandBuffer_funcs[] = {
  MIST_FUNC_DEF(cmd, render_pass, 1),
  MIST_FUNC_DEF(cmd, copy_pass, 0),
  MIST_FUNC_DEF(cmd, compute_pass, 2),
  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, generate_mipmaps, 1),
  MIST_FUNC_DEF(cmd, submit, 0),
  MIST_FUNC_DEF(cmd, cancel, 0),
  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),
  MIST_FUNC_DEF(cmd, swapchain_pass, 1),
};

JSC_CCALL(copypass_end,
  SDL_GPUCopyPass *pass = js2SDL_GPUCopyPass(js,self);
  SDL_EndGPUCopyPass(pass);
)

JSC_CCALL(copypass_upload_to_buffer,
  SDL_GPUCopyPass *pass = js2SDL_GPUCopyPass(js,self);
  JSValue transfer_loc = argv[0];
  JSValue buffer_region = argv[1];
  
  SDL_GPUTransferBufferLocation transfer_info = {0};
  JS_GETPROP(js, transfer_info.transfer_buffer, transfer_loc, transfer_buffer, SDL_GPUTransferBuffer)
  JS_GETPROP(js, transfer_info.offset, transfer_loc, offset, number)
  
  SDL_GPUBufferRegion buffer_info = {0};
  JS_GETPROP(js, buffer_info.buffer, buffer_region, buffer, SDL_GPUBuffer)
  JS_GETPROP(js, buffer_info.offset, buffer_region, offset, number)
  JS_GETPROP(js, buffer_info.size, buffer_region, size, number)
  
  bool cycle = argc > 2 ? JS_ToBool(js, argv[2]) : false;
  
  SDL_UploadToGPUBuffer(pass, &transfer_info, &buffer_info, cycle);
)

JSC_CCALL(copypass_upload_to_texture,
  SDL_GPUCopyPass *pass = js2SDL_GPUCopyPass(js,self);
  JSValue transfer_info_val = argv[0];
  JSValue texture_region_val = argv[1];
  
  SDL_GPUTextureTransferInfo transfer_info = {0};
  JS_GETPROP(js, transfer_info.transfer_buffer, transfer_info_val, transfer_buffer, SDL_GPUTransferBuffer)
  JS_GETPROP(js, transfer_info.offset, transfer_info_val, offset, number)
  JS_GETPROP(js, transfer_info.pixels_per_row, transfer_info_val, pixels_per_row, number)
  JS_GETPROP(js, transfer_info.rows_per_layer, transfer_info_val, rows_per_layer, number)
  
  SDL_GPUTextureRegion texture_region = {0};
  JS_GETPROP(js, texture_region.texture, texture_region_val, texture, SDL_GPUTexture)
  JS_GETPROP(js, texture_region.mip_level, texture_region_val, mip_level, number)
  JS_GETPROP(js, texture_region.layer, texture_region_val, layer, number)
  JS_GETPROP(js, texture_region.x, texture_region_val, x, number)
  JS_GETPROP(js, texture_region.y, texture_region_val, y, number)
  JS_GETPROP(js, texture_region.z, texture_region_val, z, number)
  JS_GETPROP(js, texture_region.w, texture_region_val, w, number)
  JS_GETPROP(js, texture_region.h, texture_region_val, h, number)
  JS_GETPROP(js, texture_region.d, texture_region_val, d, number)
  
  bool cycle = JS_ToBool(js, argv[2]);
  
  SDL_UploadToGPUTexture(pass, &transfer_info, &texture_region, cycle);
)

JSC_CCALL(copypass_buffer_to_buffer,
  SDL_GPUCopyPass *pass = js2SDL_GPUCopyPass(js,self);
  SDL_GPUBuffer *b1 = js2SDL_GPUBuffer(js,argv[0]);
  SDL_GPUBuffer *b2 = js2SDL_GPUBuffer(js,argv[1]);
  size_t size = js2number(js,argv[2]);
  bool cycle = js2bool(js,argv[3]);
  SDL_CopyGPUBufferToBuffer(pass,b1,b2,size,cycle);
)

static const JSCFunctionListEntry js_SDL_GPUCopyPass_funcs[] = {
  MIST_FUNC_DEF(copypass, end, 0),
  MIST_FUNC_DEF(copypass, upload_to_buffer, 3),
  MIST_FUNC_DEF(copypass, upload_to_texture, 3),
  MIST_FUNC_DEF(copypass, buffer_to_buffer, 4),
};

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_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),
};

JSC_CCALL(transferbuffer_copy_blob,
  SDL_GPUTransferBuffer *buffer = js2SDL_GPUTransferBuffer(js, self);
  SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, argv[0]);
  JSValue blob = argv[1];
  
  size_t blob_size;
  void *blob_data = js_get_blob_data(js, &blob_size, blob);
  if (!blob_data) return JS_ThrowTypeError(js, "copy_blob: Expected a blob");
  
  void *mapped_data = SDL_MapGPUTransferBuffer(gpu, buffer, false);
  if (!mapped_data) return JS_ThrowReferenceError(js, "copy_blob: Failed to map transfer buffer: %s", SDL_GetError());
  
  SDL_memcpy(mapped_data, blob_data, blob_size);
  
  // Unmap the transfer buffer
  SDL_UnmapGPUTransferBuffer(gpu, buffer);
  
  return JS_UNINITIALIZED;
)

static const JSCFunctionListEntry js_SDL_GPUTransferBuffer_funcs[] = {
  MIST_FUNC_DEF(transferbuffer, copy_blob, 2),
};

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_BindGPUComputeStorageBuffers(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),
};

// Empty function arrays for classes with no methods yet

// GPU device constructor function
static JSValue js_gpu_constructor(JSContext *js, JSValueConst new_target, int argc, JSValueConst *argv)
{
  SDL_PropertiesID props = SDL_CreateProperties();
  
  // Handle properties object if provided
  if (argc > 0 && JS_IsObject(argv[0])) {
    JSValue opts = argv[0];
    
    // Helper function to check and set boolean properties
    #define SET_BOOL_PROP(js_name, sdl_prop) do { \
      JSValue val = JS_GetPropertyStr(js, opts, js_name); \
      if (!JS_IsNull(val)) { \
        SDL_SetBooleanProperty(props, sdl_prop, JS_ToBool(js, val)); \
      } \
      JS_FreeValue(js, val); \
    } while(0)
    
    SET_BOOL_PROP("debug", SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN);
    SET_BOOL_PROP("lowpower", SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN);
    
    // Shader format properties
    SET_BOOL_PROP("shaders_private", SDL_PROP_GPU_DEVICE_CREATE_SHADERS_PRIVATE_BOOLEAN);
    SET_BOOL_PROP("shaders_spirv", SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN);
    SET_BOOL_PROP("shaders_dxbc", SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN);
    SET_BOOL_PROP("shaders_dxil", SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN);
    SET_BOOL_PROP("shaders_msl", SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN);
    SET_BOOL_PROP("shaders_metallib", SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN);
    
    #undef SET_BOOL_PROP
    
    // Handle string properties
    JSValue name_val = JS_GetPropertyStr(js, opts, "name");
    if (!JS_IsNull(name_val)) {
      const char *name = JS_ToCString(js, name_val);
      if (name) {
        SDL_SetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING, name);
        JS_FreeCString(js, name);
      }
    }
    JS_FreeValue(js, name_val);
    
    // D3D12 semantic name
    JSValue semantic_val = JS_GetPropertyStr(js, opts, "d3d12_semantic_name");
    if (!JS_IsNull(semantic_val)) {
      const char *semantic = JS_ToCString(js, semantic_val);
      if (semantic) {
        SDL_SetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING, semantic);
        JS_FreeCString(js, semantic);
      }
    }
    JS_FreeValue(js, semantic_val);
  }
  
  // Create GPU device with properties
  SDL_GPUDevice *device = SDL_CreateGPUDeviceWithProperties(props);
  SDL_DestroyProperties(props);
  
  if (!device) {
    return JS_ThrowReferenceError(js, "Failed to create GPU device: %s", SDL_GetError());
  }
  
  // Global device storage removed - device now passed as parameter
  
  // Create the GPU device JS object
  JSValue device_obj = SDL_GPUDevice2js(js, device);
  
  return device_obj;
}

// Function to register module
JSValue js_sdl_gpu_use(JSContext *js)
{
  JSValue ret = JS_NewObject(js);
  
  // Initialize classes
  QJSCLASSPREP_FUNCS(SDL_GPUDevice)
  QJSCLASSPREP_FUNCS(SDL_GPUBuffer)
  QJSCLASSPREP_FUNCS(SDL_GPUTexture)
  QJSCLASSPREP_FUNCS(SDL_GPUTransferBuffer)
  QJSCLASSPREP_FUNCS(SDL_GPUCommandBuffer)
  QJSCLASSPREP_FUNCS(SDL_GPUComputePass)
  QJSCLASSPREP_FUNCS(SDL_GPUCopyPass)
  QJSCLASSPREP_FUNCS(SDL_GPURenderPass)
  
  QJSCLASSPREP_NO_FUNCS(SDL_GPUFence)  
  QJSCLASSPREP_NO_FUNCS(SDL_GPUComputePipeline)
  QJSCLASSPREP_NO_FUNCS(SDL_GPUGraphicsPipeline)
  QJSCLASSPREP_NO_FUNCS(SDL_GPUSampler)
  QJSCLASSPREP_NO_FUNCS(SDL_GPUShader)
  
  // Create GPU constructor
  JSValue gpu_ctor = JS_NewCFunction2(js, js_gpu_constructor, "gpu", 1, JS_CFUNC_constructor, 0);
  
  // Set prototype on constructor
  JS_SetConstructor(js, gpu_ctor, SDL_GPUDevice_proto);
  
  // Set constructor in exports
  JS_SetPropertyStr(js, ret, "gpu", gpu_ctor);
  
  // Add GPU object constructors
  JSValue sampler_ctor = JS_NewCFunction2(js, js_gpu_sampler_constructor, "sampler", 2, JS_CFUNC_constructor, 0);
  JS_SetPropertyStr(js, ret, "sampler", sampler_ctor);
  
  JSValue shader_ctor = JS_NewCFunction2(js, js_gpu_shader_constructor, "shader", 2, JS_CFUNC_constructor, 0);
  JS_SetPropertyStr(js, ret, "shader", shader_ctor);
  
  JSValue graphics_pipeline_ctor = JS_NewCFunction2(js, js_gpu_graphics_pipeline_constructor, "graphics_pipeline", 2, JS_CFUNC_constructor, 0);
  JS_SetPropertyStr(js, ret, "graphics_pipeline", graphics_pipeline_ctor);
  
  JSValue compute_pipeline_ctor = JS_NewCFunction2(js, js_gpu_compute_pipeline_constructor, "compute_pipeline", 2, JS_CFUNC_constructor, 0);
  JS_SetPropertyStr(js, ret, "compute_pipeline", compute_pipeline_ctor);
  
  JSValue buffer_ctor = JS_NewCFunction2(js, js_gpu_buffer_constructor, "buffer", 2, JS_CFUNC_constructor, 0);
  JS_SetPropertyStr(js, ret, "buffer", buffer_ctor);
  
  JSValue transfer_buffer_ctor = JS_NewCFunction2(js, js_gpu_transfer_buffer_constructor, "transfer_buffer", 2, JS_CFUNC_constructor, 0);
  JS_SetPropertyStr(js, ret, "transfer_buffer", transfer_buffer_ctor);
  
  JSValue texture_ctor = JS_NewCFunction2(js, js_gpu_texture_constructor, "texture", 2, JS_CFUNC_constructor, 0);
  JS_SetPropertyStr(js, ret, "texture", texture_ctor);
  
  return ret;
}
