#include "sdl.h" #include "quickjs.h" #include #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; \ int owned; \ } 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 && wrapper->owned) \ 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; \ wrapper->owned = 1; \ JSValue j = JS_NewObjectClass(js, js_SDL_##SDLTYPE##_id); \ JS_SetOpaque(j, wrapper); \ return j; \ } // 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.x, color.y, color.z, color.w}; } 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}; if (!JS_IsObject(v)) return state; 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)); if (!JS_IsObject(v)) return 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 == -1) return JS_EXCEPTION; if (!code_data) return JS_ThrowTypeError(js, "empty code"); 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); if (info.code == -1) return JS_EXCEPTION; if (!info.code) return JS_ThrowTypeError(js, "compute pipeline shader must be a valid blob"); 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); } int JS_GETBOOL(JSContext *js, JSValue obj, const char *prop) { JSValue v = JS_GetPropertyStr(js,obj,prop); int ret = JS_ToBool(js,v); JS_FreeValue(js,v); return ret; } // 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, (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 = (SDL_GPUDepthStencilTargetInfo){0}; 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_IsObject(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) // If texture was null/invalid, treat as no depth attachment. if (!depthtar.texture) has_depth = 0; } 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]); if (data == -1) return JS_EXCEPTION; if (!data) return JS_ThrowTypeError(js, "uniform data must be a valid blob"); 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]); if (data == -1) return JS_EXCEPTION; if (!data) return JS_ThrowTypeError(js, "uniform data must be a valid blob"); 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]); if (data == -1) return JS_EXCEPTION; if (!data) return JS_ThrowTypeError(js, "uniform data must be a valid blob"); 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"); } if (argc > 1 && JS_IsObject(argv[1])) { JSValue passObj = argv[1]; JSValue colorTargetsVal = JS_GetPropertyStr(js, passObj, "color_targets"); if (!JS_IsArray(js, colorTargetsVal)) { JS_FreeValue(js, colorTargetsVal); return JS_ThrowTypeError(js, "swapchain_pass: color_targets must be an array"); } uint32_t colorCount = JS_ArrayLength(js, colorTargetsVal); if (colorCount != 1) { JS_FreeValue(js, colorTargetsVal); return JS_ThrowTypeError(js, "swapchain_pass: only 1 color target is supported"); } JSValue ctargetVal = JS_GetPropertyUint32(js, colorTargetsVal, 0); SDL_GPUColorTargetInfo colorTargetInfo = js2SDL_GPUColorTargetInfo(js, ctargetVal); JS_FreeValue(js, ctargetVal); JS_FreeValue(js, colorTargetsVal); colorTargetInfo.texture = swapchainTexture; SDL_GPUDepthStencilTargetInfo depthtar = (SDL_GPUDepthStencilTargetInfo){0}; int has_depth = 0; JSValue depthval = JS_GetPropertyStr(js, passObj, "depth_stencil"); if (JS_IsObject(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) // If texture was null/invalid, treat as no depth attachment. if (!depthtar.texture) has_depth = 0; } JS_FreeValue(js, depthval); SDL_GPURenderPass* renderPass = SDL_BeginGPURenderPass( cmdbuf, &colorTargetInfo, 1, has_depth ? &depthtar : NULL ); if (!renderPass) { return JS_ThrowReferenceError(js, "Failed to begin render pass: %s", SDL_GetError()); } return SDL_GPURenderPass2js(js, renderPass); } 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_acquire_swapchain_texture, SDL_GPUCommandBuffer *cmdbuf = js2SDL_GPUCommandBuffer(js, self); SDL_Window *window = js2SDL_Window(js, argv[0]); SDL_GPUTexture* swapchainTexture; Uint32 w, h; if (!SDL_WaitAndAcquireGPUSwapchainTexture(cmdbuf, window, &swapchainTexture, &w, &h)) { return JS_ThrowReferenceError(js, "WaitAndAcquireGPUSwapchainTexture failed: %s", SDL_GetError()); } if (!swapchainTexture) return JS_NULL; // Manually construct a non-owning texture wrapper gpu_texture_wrapper *wrapper = malloc(sizeof(gpu_texture_wrapper)); wrapper->js_device = JS_NULL; // We don't hold a reference to the JS device wrapper->device = NULL; // We don't need the device pointer as we don't release wrapper->type = swapchainTexture; wrapper->owned = 0; // CRITICAL: Do not release this texture JSValue jstex = JS_NewObjectClass(js, js_SDL_GPUTexture_id); JS_SetOpaque(jstex, wrapper); JS_SetPropertyStr(js, jstex, "width", number2js(js, w)); JS_SetPropertyStr(js, jstex, "height", number2js(js, h)); JS_SetPropertyStr(js, jstex, "dim", vec22js(js, (vec2){w, h})); return jstex; ) 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, 2), MIST_FUNC_DEF(cmd, acquire_swapchain_texture, 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 == -1) return JS_EXCEPTION; 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 CELL_USE_INIT( 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; )