From fd2e2b3dbc101be3b4566c40b50d17b2c8b69624 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Thu, 11 Dec 2025 00:10:41 -0600 Subject: [PATCH] initial add --- audio.c | 171 +++++++++ cell.toml | 27 ++ gfx.c | 1012 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1210 insertions(+) create mode 100644 audio.c create mode 100644 cell.toml create mode 100644 gfx.c diff --git a/audio.c b/audio.c new file mode 100644 index 0000000..bd08034 --- /dev/null +++ b/audio.c @@ -0,0 +1,171 @@ +#define SOKOL_AUDIO_IMPL +#include "sokol/sokol_audio.h" + +#include "cell.h" + +// Helper to check bool property +static 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; +} + +// Global JS callback for audio streaming +static JSContext *g_audio_js = NULL; +static JSValue g_audio_callback = JS_NULL; + +// Audio stream callback that calls into JS +static void audio_stream_callback(float *buffer, int num_frames, int num_channels) { + if (!g_audio_js || JS_IsNull(g_audio_callback)) { + // Fill with silence + for (int i = 0; i < num_frames * num_channels; i++) { + buffer[i] = 0.0f; + } + return; + } + + // Create a blob from the buffer data + int num_samples = num_frames * num_channels; + size_t buffer_size = num_samples * sizeof(float); + JSValue blob = js_new_blob_stoned_copy(g_audio_js, buffer, buffer_size); + + JSValue args[3] = { + blob, + JS_NewInt32(g_audio_js, num_frames), + JS_NewInt32(g_audio_js, num_channels) + }; + + JSValue result = JS_Call(g_audio_js, g_audio_callback, JS_NULL, 3, args); + + JS_FreeValue(g_audio_js, args[0]); + JS_FreeValue(g_audio_js, args[1]); + JS_FreeValue(g_audio_js, args[2]); + JS_FreeValue(g_audio_js, result); +} + +// ============================================================================ +// SETUP / SHUTDOWN +// ============================================================================ + +// audio.setup(desc) - Initialize sokol_audio +// desc: { sample_rate, num_channels, buffer_frames, packet_frames, num_packets, stream_cb } +JSC_CCALL(audio_setup, + saudio_desc desc = {0}; + + if (argc > 0 && JS_IsObject(argv[0])) { + JSValue cfg = argv[0]; + + JS_GETPROP(js, desc.sample_rate, cfg, sample_rate, number); + JS_GETPROP(js, desc.num_channels, cfg, num_channels, number); + JS_GETPROP(js, desc.buffer_frames, cfg, buffer_frames, number); + JS_GETPROP(js, desc.packet_frames, cfg, packet_frames, number); + JS_GETPROP(js, desc.num_packets, cfg, num_packets, number); + + // Check for stream callback + JSValue cb_val = JS_GetPropertyStr(js, cfg, "stream_cb"); + if (JS_IsFunction(js, cb_val)) { + // Store the callback globally + if (!JS_IsNull(g_audio_callback)) { + JS_FreeValue(g_audio_js, g_audio_callback); + } + g_audio_js = js; + g_audio_callback = JS_DupValue(js, cb_val); + desc.stream_cb = audio_stream_callback; + } + JS_FreeValue(js, cb_val); + } + + saudio_setup(&desc); +) + +// audio.shutdown() - Shutdown sokol_audio +JSC_CCALL(audio_shutdown, + saudio_shutdown(); + + // Clean up callback + if (!JS_IsNull(g_audio_callback)) { + JS_FreeValue(g_audio_js, g_audio_callback); + g_audio_callback = JS_NULL; + g_audio_js = NULL; + } +) + +// audio.is_valid() - Check if audio is initialized +JSC_CCALL(audio_is_valid, + return JS_NewBool(js, saudio_isvalid()); +) + +// ============================================================================ +// QUERY +// ============================================================================ + +// audio.sample_rate() - Get actual sample rate +JSC_CCALL(audio_sample_rate, + return JS_NewInt32(js, saudio_sample_rate()); +) + +// audio.buffer_frames() - Get actual buffer size in frames +JSC_CCALL(audio_buffer_frames, + return JS_NewInt32(js, saudio_buffer_frames()); +) + +// audio.channels() - Get actual number of channels +JSC_CCALL(audio_channels, + return JS_NewInt32(js, saudio_channels()); +) + +// audio.suspended() - Check if audio is suspended (WebAudio only) +JSC_CCALL(audio_suspended, + return JS_NewBool(js, saudio_suspended()); +) + +// ============================================================================ +// PUSH MODEL +// ============================================================================ + +// audio.expect() - Get number of frames that can be pushed +JSC_CCALL(audio_expect, + return JS_NewInt32(js, saudio_expect()); +) + +// audio.push(samples) - Push audio samples (Float32Array) +// Returns number of frames actually pushed +JSC_CCALL(audio_push, + size_t data_size; + void *data_ptr = js_get_blob_data(js, &data_size, argv[0]); + + if (data_ptr && data_ptr != (void*)-1) { + int num_channels = saudio_channels(); + int num_frames = data_size / (sizeof(float) * num_channels); + int pushed = saudio_push((const float*)data_ptr, num_frames); + + return JS_NewInt32(js, pushed); + } + + return JS_NewInt32(js, 0); +) + +// ============================================================================ +// MODULE INIT +// ============================================================================ + +static const JSCFunctionListEntry js_audio_funcs[] = { + JS_CFUNC_DEF("setup", 1, js_audio_setup), + JS_CFUNC_DEF("shutdown", 0, js_audio_shutdown), + JS_CFUNC_DEF("is_valid", 0, js_audio_is_valid), + + JS_CFUNC_DEF("sample_rate", 0, js_audio_sample_rate), + JS_CFUNC_DEF("buffer_frames", 0, js_audio_buffer_frames), + JS_CFUNC_DEF("channels", 0, js_audio_channels), + JS_CFUNC_DEF("suspended", 0, js_audio_suspended), + + JS_CFUNC_DEF("expect", 0, js_audio_expect), + JS_CFUNC_DEF("push", 1, js_audio_push), +}; + +CELL_USE_INIT( + JSValue audio = JS_NewObject(js); + JS_SetPropertyFunctionList(js, audio, js_audio_funcs, sizeof(js_audio_funcs)/sizeof(js_audio_funcs[0])); + return audio; +) \ No newline at end of file diff --git a/cell.toml b/cell.toml new file mode 100644 index 0000000..360324a --- /dev/null +++ b/cell.toml @@ -0,0 +1,27 @@ +[compilation.macos_arm64] +CFLAGS = "-x objective-c -DSOKOL_METAL" +LDFLAGS = "-framework CoreAudio -framework Metal" + +[compilation.ios] +CFLAGS = "-x objective-c -DSOKOL_METAL" +LDFLAGS = "-framework CoreAudio -framework AVFoundation -framework Metal" + +[compilation.windows] +CFLAGS = "-mwin32 -DSOKOL_D3D11" +LDFLAGS = "-lole32 -lwinmm -ld3d11" + +[compilation.linux] +CFLAGS = "-DSOKOL_VULKAN" +LDFLAGS = "-lasound -lGL" + +[compilation.freebsd] +CFLAGS = "-DSOKOL_VULKAN" +LDFLAGS = "-lasound -lGL" + +[compilation.android] +CFLAGS = "-DSOKOL_GLES3" +LDFLAGS = "-laaudio -lGLESv3 -llog -landroid" + +[compilation.emscripten] +CFLAGS = "-DSOKOL_WGPU +LDFLAGS = "-sUSE_WEBGL2=1" diff --git a/gfx.c b/gfx.c new file mode 100644 index 0000000..decb72e --- /dev/null +++ b/gfx.c @@ -0,0 +1,1012 @@ +#define SOKOL_GFX_IMPL +#include "sokol/sokol_gfx.h" +#include "cell.h" + +// Helper macros for enum mapping (same pattern as sdl/gpu.c) +#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; \ +} + +// Backend enum +ENUM_MAPPING_TABLE(sg_backend) = { + {SG_BACKEND_GLCORE, "glcore"}, + {SG_BACKEND_GLES3, "gles3"}, + {SG_BACKEND_D3D11, "d3d11"}, + {SG_BACKEND_METAL_IOS, "metal_ios"}, + {SG_BACKEND_METAL_MACOS, "metal_macos"}, + {SG_BACKEND_METAL_SIMULATOR, "metal_simulator"}, + {SG_BACKEND_WGPU, "wgpu"}, + {SG_BACKEND_VULKAN, "vulkan"}, + {SG_BACKEND_DUMMY, "dummy"}, +}; +JS2ENUM(sg_backend) + +// Pixel format enum +ENUM_MAPPING_TABLE(sg_pixel_format) = { + {SG_PIXELFORMAT_NONE, "none"}, + {SG_PIXELFORMAT_R8, "r8"}, + {SG_PIXELFORMAT_R8SN, "r8sn"}, + {SG_PIXELFORMAT_R8UI, "r8ui"}, + {SG_PIXELFORMAT_R8SI, "r8si"}, + {SG_PIXELFORMAT_R16, "r16"}, + {SG_PIXELFORMAT_R16SN, "r16sn"}, + {SG_PIXELFORMAT_R16UI, "r16ui"}, + {SG_PIXELFORMAT_R16SI, "r16si"}, + {SG_PIXELFORMAT_R16F, "r16f"}, + {SG_PIXELFORMAT_RG8, "rg8"}, + {SG_PIXELFORMAT_RG8SN, "rg8sn"}, + {SG_PIXELFORMAT_RG8UI, "rg8ui"}, + {SG_PIXELFORMAT_RG8SI, "rg8si"}, + {SG_PIXELFORMAT_R32UI, "r32ui"}, + {SG_PIXELFORMAT_R32SI, "r32si"}, + {SG_PIXELFORMAT_R32F, "r32f"}, + {SG_PIXELFORMAT_RG16, "rg16"}, + {SG_PIXELFORMAT_RG16SN, "rg16sn"}, + {SG_PIXELFORMAT_RG16UI, "rg16ui"}, + {SG_PIXELFORMAT_RG16SI, "rg16si"}, + {SG_PIXELFORMAT_RG16F, "rg16f"}, + {SG_PIXELFORMAT_RGBA8, "rgba8"}, + {SG_PIXELFORMAT_SRGB8A8, "srgba8"}, + {SG_PIXELFORMAT_RGBA8SN, "rgba8sn"}, + {SG_PIXELFORMAT_RGBA8UI, "rgba8ui"}, + {SG_PIXELFORMAT_RGBA8SI, "rgba8si"}, + {SG_PIXELFORMAT_BGRA8, "bgra8"}, + {SG_PIXELFORMAT_RGB10A2, "rgb10a2"}, + {SG_PIXELFORMAT_RG11B10F, "rg11b10f"}, + {SG_PIXELFORMAT_RG32UI, "rg32ui"}, + {SG_PIXELFORMAT_RG32SI, "rg32si"}, + {SG_PIXELFORMAT_RG32F, "rg32f"}, + {SG_PIXELFORMAT_RGBA16, "rgba16"}, + {SG_PIXELFORMAT_RGBA16SN, "rgba16sn"}, + {SG_PIXELFORMAT_RGBA16UI, "rgba16ui"}, + {SG_PIXELFORMAT_RGBA16SI, "rgba16si"}, + {SG_PIXELFORMAT_RGBA16F, "rgba16f"}, + {SG_PIXELFORMAT_RGBA32UI, "rgba32ui"}, + {SG_PIXELFORMAT_RGBA32SI, "rgba32si"}, + {SG_PIXELFORMAT_RGBA32F, "rgba32f"}, + {SG_PIXELFORMAT_DEPTH, "depth"}, + {SG_PIXELFORMAT_DEPTH_STENCIL, "depth_stencil"}, +}; +JS2ENUM(sg_pixel_format) + +// Primitive type +ENUM_MAPPING_TABLE(sg_primitive_type) = { + {SG_PRIMITIVETYPE_POINTS, "points"}, + {SG_PRIMITIVETYPE_LINES, "lines"}, + {SG_PRIMITIVETYPE_LINE_STRIP, "line_strip"}, + {SG_PRIMITIVETYPE_TRIANGLES, "triangles"}, + {SG_PRIMITIVETYPE_TRIANGLE_STRIP, "triangle_strip"}, +}; +JS2ENUM(sg_primitive_type) + +// Index type +ENUM_MAPPING_TABLE(sg_index_type) = { + {SG_INDEXTYPE_NONE, "none"}, + {SG_INDEXTYPE_UINT16, "uint16"}, + {SG_INDEXTYPE_UINT32, "uint32"}, +}; +JS2ENUM(sg_index_type) + +// Image type +ENUM_MAPPING_TABLE(sg_image_type) = { + {SG_IMAGETYPE_2D, "2d"}, + {SG_IMAGETYPE_CUBE, "cube"}, + {SG_IMAGETYPE_3D, "3d"}, + {SG_IMAGETYPE_ARRAY, "array"}, +}; +JS2ENUM(sg_image_type) + +// Filter +ENUM_MAPPING_TABLE(sg_filter) = { + {SG_FILTER_NEAREST, "nearest"}, + {SG_FILTER_LINEAR, "linear"}, +}; +JS2ENUM(sg_filter) + +// Wrap mode +ENUM_MAPPING_TABLE(sg_wrap) = { + {SG_WRAP_REPEAT, "repeat"}, + {SG_WRAP_CLAMP_TO_EDGE, "clamp"}, + {SG_WRAP_CLAMP_TO_BORDER, "border"}, + {SG_WRAP_MIRRORED_REPEAT, "mirror"}, +}; +JS2ENUM(sg_wrap) + +// Compare function +ENUM_MAPPING_TABLE(sg_compare_func) = { + {SG_COMPAREFUNC_NEVER, "never"}, + {SG_COMPAREFUNC_LESS, "less"}, + {SG_COMPAREFUNC_EQUAL, "equal"}, + {SG_COMPAREFUNC_LESS_EQUAL, "less_equal"}, + {SG_COMPAREFUNC_GREATER, "greater"}, + {SG_COMPAREFUNC_NOT_EQUAL, "not_equal"}, + {SG_COMPAREFUNC_GREATER_EQUAL, "greater_equal"}, + {SG_COMPAREFUNC_ALWAYS, "always"}, +}; +JS2ENUM(sg_compare_func) + +// Stencil op +ENUM_MAPPING_TABLE(sg_stencil_op) = { + {SG_STENCILOP_KEEP, "keep"}, + {SG_STENCILOP_ZERO, "zero"}, + {SG_STENCILOP_REPLACE, "replace"}, + {SG_STENCILOP_INCR_CLAMP, "incr_clamp"}, + {SG_STENCILOP_DECR_CLAMP, "decr_clamp"}, + {SG_STENCILOP_INVERT, "invert"}, + {SG_STENCILOP_INCR_WRAP, "incr_wrap"}, + {SG_STENCILOP_DECR_WRAP, "decr_wrap"}, +}; +JS2ENUM(sg_stencil_op) + +// Blend factor +ENUM_MAPPING_TABLE(sg_blend_factor) = { + {SG_BLENDFACTOR_ZERO, "zero"}, + {SG_BLENDFACTOR_ONE, "one"}, + {SG_BLENDFACTOR_SRC_COLOR, "src_color"}, + {SG_BLENDFACTOR_ONE_MINUS_SRC_COLOR, "one_minus_src_color"}, + {SG_BLENDFACTOR_SRC_ALPHA, "src_alpha"}, + {SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, "one_minus_src_alpha"}, + {SG_BLENDFACTOR_DST_COLOR, "dst_color"}, + {SG_BLENDFACTOR_ONE_MINUS_DST_COLOR, "one_minus_dst_color"}, + {SG_BLENDFACTOR_DST_ALPHA, "dst_alpha"}, + {SG_BLENDFACTOR_ONE_MINUS_DST_ALPHA, "one_minus_dst_alpha"}, + {SG_BLENDFACTOR_SRC_ALPHA_SATURATED, "src_alpha_saturated"}, + {SG_BLENDFACTOR_BLEND_COLOR, "blend_color"}, + {SG_BLENDFACTOR_ONE_MINUS_BLEND_COLOR, "one_minus_blend_color"}, +}; +JS2ENUM(sg_blend_factor) + +// Blend op +ENUM_MAPPING_TABLE(sg_blend_op) = { + {SG_BLENDOP_ADD, "add"}, + {SG_BLENDOP_SUBTRACT, "subtract"}, + {SG_BLENDOP_REVERSE_SUBTRACT, "reverse_subtract"}, + {SG_BLENDOP_MIN, "min"}, + {SG_BLENDOP_MAX, "max"}, +}; +JS2ENUM(sg_blend_op) + +// Cull mode +ENUM_MAPPING_TABLE(sg_cull_mode) = { + {SG_CULLMODE_NONE, "none"}, + {SG_CULLMODE_FRONT, "front"}, + {SG_CULLMODE_BACK, "back"}, +}; +JS2ENUM(sg_cull_mode) + +// Face winding +ENUM_MAPPING_TABLE(sg_face_winding) = { + {SG_FACEWINDING_CCW, "ccw"}, + {SG_FACEWINDING_CW, "cw"}, +}; +JS2ENUM(sg_face_winding) + +// Vertex format +ENUM_MAPPING_TABLE(sg_vertex_format) = { + {SG_VERTEXFORMAT_FLOAT, "float"}, + {SG_VERTEXFORMAT_FLOAT2, "float2"}, + {SG_VERTEXFORMAT_FLOAT3, "float3"}, + {SG_VERTEXFORMAT_FLOAT4, "float4"}, + {SG_VERTEXFORMAT_INT, "int"}, + {SG_VERTEXFORMAT_INT2, "int2"}, + {SG_VERTEXFORMAT_INT3, "int3"}, + {SG_VERTEXFORMAT_INT4, "int4"}, + {SG_VERTEXFORMAT_UINT, "uint"}, + {SG_VERTEXFORMAT_UINT2, "uint2"}, + {SG_VERTEXFORMAT_UINT3, "uint3"}, + {SG_VERTEXFORMAT_UINT4, "uint4"}, + {SG_VERTEXFORMAT_BYTE4, "byte4"}, + {SG_VERTEXFORMAT_BYTE4N, "byte4n"}, + {SG_VERTEXFORMAT_UBYTE4, "ubyte4"}, + {SG_VERTEXFORMAT_UBYTE4N, "ubyte4n"}, + {SG_VERTEXFORMAT_SHORT2, "short2"}, + {SG_VERTEXFORMAT_SHORT2N, "short2n"}, + {SG_VERTEXFORMAT_USHORT2, "ushort2"}, + {SG_VERTEXFORMAT_USHORT2N, "ushort2n"}, + {SG_VERTEXFORMAT_SHORT4, "short4"}, + {SG_VERTEXFORMAT_SHORT4N, "short4n"}, + {SG_VERTEXFORMAT_USHORT4, "ushort4"}, + {SG_VERTEXFORMAT_USHORT4N, "ushort4n"}, + {SG_VERTEXFORMAT_HALF2, "half2"}, + {SG_VERTEXFORMAT_HALF4, "half4"}, +}; +JS2ENUM(sg_vertex_format) + +// Load action +ENUM_MAPPING_TABLE(sg_load_action) = { + {SG_LOADACTION_CLEAR, "clear"}, + {SG_LOADACTION_LOAD, "load"}, + {SG_LOADACTION_DONTCARE, "dontcare"}, +}; +JS2ENUM(sg_load_action) + +// Store action +ENUM_MAPPING_TABLE(sg_store_action) = { + {SG_STOREACTION_STORE, "store"}, + {SG_STOREACTION_DONTCARE, "dontcare"}, +}; +JS2ENUM(sg_store_action) + +// Uniform type +ENUM_MAPPING_TABLE(sg_uniform_type) = { + {SG_UNIFORMTYPE_FLOAT, "float"}, + {SG_UNIFORMTYPE_FLOAT2, "float2"}, + {SG_UNIFORMTYPE_FLOAT3, "float3"}, + {SG_UNIFORMTYPE_FLOAT4, "float4"}, + {SG_UNIFORMTYPE_INT, "int"}, + {SG_UNIFORMTYPE_INT2, "int2"}, + {SG_UNIFORMTYPE_INT3, "int3"}, + {SG_UNIFORMTYPE_INT4, "int4"}, + {SG_UNIFORMTYPE_MAT4, "mat4"}, +}; +JS2ENUM(sg_uniform_type) + +// Helper to check bool property +static 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; +} + +// Color conversion +static sg_color js2sg_color(JSContext *js, JSValue v) { + sg_color c = {0}; + if (JS_IsArray(js, v)) { + JSValue r = JS_GetPropertyUint32(js, v, 0); + JSValue g = JS_GetPropertyUint32(js, v, 1); + JSValue b = JS_GetPropertyUint32(js, v, 2); + JSValue a = JS_GetPropertyUint32(js, v, 3); + c.r = js2number(js, r); + c.g = js2number(js, g); + c.b = js2number(js, b); + c.a = JS_IsNull(a) ? 1.0f : js2number(js, a); + JS_FreeValue(js, r); + JS_FreeValue(js, g); + JS_FreeValue(js, b); + JS_FreeValue(js, a); + } else if (JS_IsObject(v)) { + JS_GETPROP(js, c.r, v, r, number); + JS_GETPROP(js, c.g, v, g, number); + JS_GETPROP(js, c.b, v, b, number); + c.a = 1.0f; + JS_GETPROP(js, c.a, v, a, number); + } + return c; +} + +// ============================================================================ +// SETUP / SHUTDOWN +// ============================================================================ + +// gfx.setup(desc) - Initialize sokol_gfx +JSC_CCALL(gfx_setup, + sg_desc desc = {0}; + if (argc > 0 && JS_IsObject(argv[0])) { + JSValue cfg = argv[0]; + JSValue env = JS_GetPropertyStr(js, cfg, "environment"); + if (!JS_IsNull(env)) { + // Environment settings would be set here + // For now, use defaults from sokol_glue + } + JS_FreeValue(js, env); + } + sg_setup(&desc); +) + +// gfx.shutdown() - Shutdown sokol_gfx +JSC_CCALL(gfx_shutdown, + sg_shutdown(); +) + +// gfx.is_valid() - Check if sokol_gfx is initialized +JSC_CCALL(gfx_is_valid, + return JS_NewBool(js, sg_isvalid()); +) + +// gfx.backend() - Get current backend as string +JSC_CCALL(gfx_backend, + return sg_backend2js(js, sg_query_backend()); +) + +// gfx.commit() - Commit the current frame +JSC_CCALL(gfx_commit, + sg_commit(); +) + +// gfx.reset_state_cache() - Reset internal state cache +JSC_CCALL(gfx_reset_state_cache, + sg_reset_state_cache(); +) + +// ============================================================================ +// BUFFER +// ============================================================================ + +// gfx.make_buffer(desc) - Create a buffer +// desc: { size, data, vertex, index, storage, immutable, dynamic, stream, label } +JSC_CCALL(gfx_make_buffer, + sg_buffer_desc desc = {0}; + JSValue cfg = argv[0]; + + JS_GETPROP(js, desc.size, cfg, size, number); + + // Usage flags + desc.usage.vertex_buffer = JS_GETBOOL(js, cfg, "vertex"); + desc.usage.index_buffer = JS_GETBOOL(js, cfg, "index"); + desc.usage.storage_buffer = JS_GETBOOL(js, cfg, "storage"); + desc.usage.immutable = !JS_GETBOOL(js, cfg, "dynamic") && !JS_GETBOOL(js, cfg, "stream"); + desc.usage.dynamic_update = JS_GETBOOL(js, cfg, "dynamic"); + desc.usage.stream_update = JS_GETBOOL(js, cfg, "stream"); + + // Default to vertex buffer if nothing specified + if (!desc.usage.vertex_buffer && !desc.usage.index_buffer && !desc.usage.storage_buffer) + desc.usage.vertex_buffer = true; + + // Initial data + JSValue data_val = JS_GetPropertyStr(js, cfg, "data"); + if (!JS_IsNull(data_val)) { + size_t data_size; + void *data_ptr = js_get_blob_data(js, &data_size, data_val); + if (data_ptr && data_ptr != (void*)-1) { + desc.data.ptr = data_ptr; + desc.data.size = data_size; + if (desc.size == 0) desc.size = data_size; + } + } + JS_FreeValue(js, data_val); + + // Label + JSValue label_val = JS_GetPropertyStr(js, cfg, "label"); + if (JS_IsString(label_val)) { + desc.label = JS_ToCString(js, label_val); + } + JS_FreeValue(js, label_val); + + sg_buffer buf = sg_make_buffer(&desc); + + if (desc.label) JS_FreeCString(js, desc.label); + + return JS_NewInt32(js, buf.id); +) + +// gfx.destroy_buffer(buf_id) +JSC_CCALL(gfx_destroy_buffer, + sg_buffer buf = { .id = js2number(js, argv[0]) }; + sg_destroy_buffer(buf); +) + +// gfx.update_buffer(buf_id, data) +JSC_CCALL(gfx_update_buffer, + sg_buffer buf = { .id = js2number(js, argv[0]) }; + size_t data_size; + void *data_ptr = js_get_blob_data(js, &data_size, argv[1]); + if (data_ptr && data_ptr != (void*)-1) { + sg_update_buffer(buf, &(sg_range){ .ptr = data_ptr, .size = data_size }); + } +) + +// gfx.append_buffer(buf_id, data) -> offset +JSC_CCALL(gfx_append_buffer, + sg_buffer buf = { .id = js2number(js, argv[0]) }; + size_t data_size; + void *data_ptr = js_get_blob_data(js, &data_size, argv[1]); + if (data_ptr && data_ptr != (void*)-1) { + int offset = sg_append_buffer(buf, &(sg_range){ .ptr = data_ptr, .size = data_size }); + return JS_NewInt32(js, offset); + } + return JS_NewInt32(js, -1); +) + +// ============================================================================ +// IMAGE +// ============================================================================ + +// gfx.make_image(desc) - Create an image +// desc: { width, height, type, format, mip_levels, samples, storage, color_target, depth_target, ... } +JSC_CCALL(gfx_make_image, + sg_image_desc desc = {0}; + JSValue cfg = argv[0]; + + JS_GETPROP(js, desc.width, cfg, width, number); + JS_GETPROP(js, desc.height, cfg, height, number); + JS_GETPROP(js, desc.num_slices, cfg, slices, number); + JS_GETPROP(js, desc.num_mipmaps, cfg, mip_levels, number); + JS_GETPROP(js, desc.type, cfg, type, sg_image_type); + JS_GETPROP(js, desc.pixel_format, cfg, format, sg_pixel_format); + JS_GETPROP(js, desc.sample_count, cfg, samples, number); + + // Usage flags + desc.usage.storage_image = JS_GETBOOL(js, cfg, "storage"); + desc.usage.color_attachment = JS_GETBOOL(js, cfg, "color_target"); + desc.usage.resolve_attachment = JS_GETBOOL(js, cfg, "resolve_target"); + desc.usage.depth_stencil_attachment = JS_GETBOOL(js, cfg, "depth_target"); + desc.usage.immutable = !JS_GETBOOL(js, cfg, "dynamic") && !JS_GETBOOL(js, cfg, "stream"); + desc.usage.dynamic_update = JS_GETBOOL(js, cfg, "dynamic"); + desc.usage.stream_update = JS_GETBOOL(js, cfg, "stream"); + + // Initial data for mip 0, face 0 + JSValue data_val = JS_GetPropertyStr(js, cfg, "data"); + if (!JS_IsNull(data_val)) { + size_t data_size; + void *data_ptr = js_get_blob_data(js, &data_size, data_val); + if (data_ptr && data_ptr != (void*)-1) { + desc.data.mip_levels[0].ptr = data_ptr; + desc.data.mip_levels[0].size = data_size; + } + } + JS_FreeValue(js, data_val); + + sg_image img = sg_make_image(&desc); + return JS_NewInt32(js, img.id); +) + +// gfx.destroy_image(img_id) +JSC_CCALL(gfx_destroy_image, + sg_image img = { .id = js2number(js, argv[0]) }; + sg_destroy_image(img); +) + +// gfx.update_image(img_id, data) +JSC_CCALL(gfx_update_image, + sg_image img = { .id = js2number(js, argv[0]) }; + sg_image_data data = {0}; + + size_t data_size; + void *data_ptr = js_get_blob_data(js, &data_size, argv[1]); + if (data_ptr && data_ptr != (void*)-1) { + data.mip_levels[0].ptr = data_ptr; + data.mip_levels[0].size = data_size; + sg_update_image(img, &data); + } +) + +// ============================================================================ +// SAMPLER +// ============================================================================ + +// gfx.make_sampler(desc) - Create a sampler +// desc: { min_filter, mag_filter, mipmap_filter, wrap_u, wrap_v, wrap_w, ... } +JSC_CCALL(gfx_make_sampler, + sg_sampler_desc desc = {0}; + JSValue cfg = argv[0]; + + JS_GETPROP(js, desc.min_filter, cfg, min_filter, sg_filter); + JS_GETPROP(js, desc.mag_filter, cfg, mag_filter, sg_filter); + JS_GETPROP(js, desc.mipmap_filter, cfg, mipmap_filter, sg_filter); + JS_GETPROP(js, desc.wrap_u, cfg, wrap_u, sg_wrap); + JS_GETPROP(js, desc.wrap_v, cfg, wrap_v, sg_wrap); + JS_GETPROP(js, desc.wrap_w, cfg, wrap_w, sg_wrap); + JS_GETPROP(js, desc.min_lod, cfg, min_lod, number); + JS_GETPROP(js, desc.max_lod, cfg, max_lod, number); + JS_GETPROP(js, desc.max_anisotropy, cfg, max_anisotropy, number); + JS_GETPROP(js, desc.compare, cfg, compare, sg_compare_func); + + sg_sampler smp = sg_make_sampler(&desc); + return JS_NewInt32(js, smp.id); +) + +// gfx.destroy_sampler(smp_id) +JSC_CCALL(gfx_destroy_sampler, + sg_sampler smp = { .id = js2number(js, argv[0]) }; + sg_destroy_sampler(smp); +) + +// ============================================================================ +// SHADER +// ============================================================================ + +// gfx.make_shader(desc) - Create a shader +// This is complex; for now support basic vertex/fragment with uniform blocks +JSC_CCALL(gfx_make_shader, + sg_shader_desc desc = {0}; + JSValue cfg = argv[0]; + + // Vertex shader source + JSValue vs_val = JS_GetPropertyStr(js, cfg, "vs"); + if (JS_IsObject(vs_val)) { + JSValue src = JS_GetPropertyStr(js, vs_val, "source"); + if (JS_IsString(src)) { + desc.vertex_func.source = JS_ToCString(js, src); + } + JS_FreeValue(js, src); + + JSValue entry = JS_GetPropertyStr(js, vs_val, "entry"); + if (JS_IsString(entry)) { + desc.vertex_func.entry = JS_ToCString(js, entry); + } + JS_FreeValue(js, entry); + } + JS_FreeValue(js, vs_val); + + // Fragment shader source + JSValue fs_val = JS_GetPropertyStr(js, cfg, "fs"); + if (JS_IsObject(fs_val)) { + JSValue src = JS_GetPropertyStr(js, fs_val, "source"); + if (JS_IsString(src)) { + desc.fragment_func.source = JS_ToCString(js, src); + } + JS_FreeValue(js, src); + + JSValue entry = JS_GetPropertyStr(js, fs_val, "entry"); + if (JS_IsString(entry)) { + desc.fragment_func.entry = JS_ToCString(js, entry); + } + JS_FreeValue(js, entry); + } + JS_FreeValue(js, fs_val); + + sg_shader shd = sg_make_shader(&desc); + + // Free strings + if (desc.vertex_func.source) JS_FreeCString(js, desc.vertex_func.source); + if (desc.vertex_func.entry) JS_FreeCString(js, desc.vertex_func.entry); + if (desc.fragment_func.source) JS_FreeCString(js, desc.fragment_func.source); + if (desc.fragment_func.entry) JS_FreeCString(js, desc.fragment_func.entry); + + return JS_NewInt32(js, shd.id); +) + +// gfx.destroy_shader(shd_id) +JSC_CCALL(gfx_destroy_shader, + sg_shader shd = { .id = js2number(js, argv[0]) }; + sg_destroy_shader(shd); +) + +// ============================================================================ +// PIPELINE +// ============================================================================ + +// gfx.make_pipeline(desc) - Create a pipeline +JSC_CCALL(gfx_make_pipeline, + sg_pipeline_desc desc = {0}; + JSValue cfg = argv[0]; + + // Shader + JSValue shd_val = JS_GetPropertyStr(js, cfg, "shader"); + desc.shader.id = js2number(js, shd_val); + JS_FreeValue(js, shd_val); + + // Vertex layout + JSValue layout_val = JS_GetPropertyStr(js, cfg, "layout"); + if (JS_IsObject(layout_val)) { + // Buffers + JSValue buffers_val = JS_GetPropertyStr(js, layout_val, "buffers"); + if (JS_IsArray(js, buffers_val)) { + int len = 0; + JSValue len_val = JS_GetPropertyStr(js, buffers_val, "length"); + JS_ToInt32(js, &len, len_val); + JS_FreeValue(js, len_val); + + for (int i = 0; i < len && i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + JSValue buf = JS_GetPropertyUint32(js, buffers_val, i); + if (JS_IsObject(buf)) { + JS_GETPROP(js, desc.layout.buffers[i].stride, buf, stride, number); + JSValue step_val = JS_GetPropertyStr(js, buf, "step"); + if (!JS_IsNull(step_val)) { + const char *step_str = JS_ToCString(js, step_val); + if (step_str) { + if (!strcmp(step_str, "instance")) + desc.layout.buffers[i].step_func = SG_VERTEXSTEP_PER_INSTANCE; + JS_FreeCString(js, step_str); + } + } + JS_FreeValue(js, step_val); + JS_GETPROP(js, desc.layout.buffers[i].step_rate, buf, step_rate, number); + } + JS_FreeValue(js, buf); + } + } + JS_FreeValue(js, buffers_val); + + // Attributes + JSValue attrs_val = JS_GetPropertyStr(js, layout_val, "attrs"); + if (JS_IsArray(js, attrs_val)) { + int len = 0; + JSValue len_val = JS_GetPropertyStr(js, attrs_val, "length"); + JS_ToInt32(js, &len, len_val); + JS_FreeValue(js, len_val); + + for (int i = 0; i < len && i < SG_MAX_VERTEX_ATTRIBUTES; i++) { + JSValue attr = JS_GetPropertyUint32(js, attrs_val, i); + if (JS_IsObject(attr)) { + JS_GETPROP(js, desc.layout.attrs[i].format, attr, format, sg_vertex_format); + JS_GETPROP(js, desc.layout.attrs[i].offset, attr, offset, number); + JS_GETPROP(js, desc.layout.attrs[i].buffer_index, attr, buffer, number); + } + JS_FreeValue(js, attr); + } + } + JS_FreeValue(js, attrs_val); + } + JS_FreeValue(js, layout_val); + + // Primitive type + JS_GETPROP(js, desc.primitive_type, cfg, primitive, sg_primitive_type); + + // Index type + JS_GETPROP(js, desc.index_type, cfg, index_type, sg_index_type); + + // Cull mode + JS_GETPROP(js, desc.cull_mode, cfg, cull, sg_cull_mode); + + // Face winding + JS_GETPROP(js, desc.face_winding, cfg, winding, sg_face_winding); + + // Depth state + JSValue depth_val = JS_GetPropertyStr(js, cfg, "depth"); + if (JS_IsObject(depth_val)) { + JS_GETPROP(js, desc.depth.pixel_format, depth_val, format, sg_pixel_format); + JS_GETPROP(js, desc.depth.compare, depth_val, compare, sg_compare_func); + desc.depth.write_enabled = JS_GETBOOL(js, depth_val, "write"); + JS_GETPROP(js, desc.depth.bias, depth_val, bias, number); + JS_GETPROP(js, desc.depth.bias_slope_scale, depth_val, bias_slope, number); + JS_GETPROP(js, desc.depth.bias_clamp, depth_val, bias_clamp, number); + } + JS_FreeValue(js, depth_val); + + // Stencil state + JSValue stencil_val = JS_GetPropertyStr(js, cfg, "stencil"); + if (JS_IsObject(stencil_val)) { + desc.stencil.enabled = JS_GETBOOL(js, stencil_val, "enabled"); + JS_GETPROP(js, desc.stencil.read_mask, stencil_val, read_mask, number); + JS_GETPROP(js, desc.stencil.write_mask, stencil_val, write_mask, number); + JS_GETPROP(js, desc.stencil.ref, stencil_val, ref, number); + + JSValue front_val = JS_GetPropertyStr(js, stencil_val, "front"); + if (JS_IsObject(front_val)) { + JS_GETPROP(js, desc.stencil.front.compare, front_val, compare, sg_compare_func); + JS_GETPROP(js, desc.stencil.front.fail_op, front_val, fail, sg_stencil_op); + JS_GETPROP(js, desc.stencil.front.depth_fail_op, front_val, depth_fail, sg_stencil_op); + JS_GETPROP(js, desc.stencil.front.pass_op, front_val, pass, sg_stencil_op); + } + JS_FreeValue(js, front_val); + + JSValue back_val = JS_GetPropertyStr(js, stencil_val, "back"); + if (JS_IsObject(back_val)) { + JS_GETPROP(js, desc.stencil.back.compare, back_val, compare, sg_compare_func); + JS_GETPROP(js, desc.stencil.back.fail_op, back_val, fail, sg_stencil_op); + JS_GETPROP(js, desc.stencil.back.depth_fail_op, back_val, depth_fail, sg_stencil_op); + JS_GETPROP(js, desc.stencil.back.pass_op, back_val, pass, sg_stencil_op); + } + JS_FreeValue(js, back_val); + } + JS_FreeValue(js, stencil_val); + + // Color targets + JSValue colors_val = JS_GetPropertyStr(js, cfg, "colors"); + if (JS_IsArray(js, colors_val)) { + int len = 0; + JSValue len_val = JS_GetPropertyStr(js, colors_val, "length"); + JS_ToInt32(js, &len, len_val); + JS_FreeValue(js, len_val); + + for (int i = 0; i < len && i < SG_MAX_COLOR_ATTACHMENTS; i++) { + JSValue col = JS_GetPropertyUint32(js, colors_val, i); + if (JS_IsObject(col)) { + JS_GETPROP(js, desc.colors[i].pixel_format, col, format, sg_pixel_format); + + JSValue blend_val = JS_GetPropertyStr(js, col, "blend"); + if (JS_IsObject(blend_val)) { + desc.colors[i].blend.enabled = JS_GETBOOL(js, blend_val, "enabled"); + JS_GETPROP(js, desc.colors[i].blend.src_factor_rgb, blend_val, src_rgb, sg_blend_factor); + JS_GETPROP(js, desc.colors[i].blend.dst_factor_rgb, blend_val, dst_rgb, sg_blend_factor); + JS_GETPROP(js, desc.colors[i].blend.op_rgb, blend_val, op_rgb, sg_blend_op); + JS_GETPROP(js, desc.colors[i].blend.src_factor_alpha, blend_val, src_alpha, sg_blend_factor); + JS_GETPROP(js, desc.colors[i].blend.dst_factor_alpha, blend_val, dst_alpha, sg_blend_factor); + JS_GETPROP(js, desc.colors[i].blend.op_alpha, blend_val, op_alpha, sg_blend_op); + } + JS_FreeValue(js, blend_val); + } + JS_FreeValue(js, col); + } + } + JS_FreeValue(js, colors_val); + + // Sample count + JS_GETPROP(js, desc.sample_count, cfg, samples, number); + + sg_pipeline pip = sg_make_pipeline(&desc); + return JS_NewInt32(js, pip.id); +) + +// gfx.destroy_pipeline(pip_id) +JSC_CCALL(gfx_destroy_pipeline, + sg_pipeline pip = { .id = js2number(js, argv[0]) }; + sg_destroy_pipeline(pip); +) + +// ============================================================================ +// PASS / RENDERING +// ============================================================================ + +// gfx.begin_pass(desc) - Begin a render pass +// desc: { action: { colors: [{load, store, clear}], depth: {...}, stencil: {...} }, swapchain: {...} } +JSC_CCALL(gfx_begin_pass, + sg_pass pass = {0}; + + if (argc > 0 && JS_IsObject(argv[0])) { + JSValue cfg = argv[0]; + + // Pass action + JSValue action_val = JS_GetPropertyStr(js, cfg, "action"); + if (JS_IsObject(action_val)) { + // Color attachments + JSValue colors_val = JS_GetPropertyStr(js, action_val, "colors"); + if (JS_IsArray(js, colors_val)) { + int len = 0; + JSValue len_val = JS_GetPropertyStr(js, colors_val, "length"); + JS_ToInt32(js, &len, len_val); + JS_FreeValue(js, len_val); + + for (int i = 0; i < len && i < SG_MAX_COLOR_ATTACHMENTS; i++) { + JSValue col = JS_GetPropertyUint32(js, colors_val, i); + if (JS_IsObject(col)) { + JS_GETPROP(js, pass.action.colors[i].load_action, col, load, sg_load_action); + JS_GETPROP(js, pass.action.colors[i].store_action, col, store, sg_store_action); + + JSValue clear_val = JS_GetPropertyStr(js, col, "clear"); + if (!JS_IsNull(clear_val)) { + pass.action.colors[i].clear_value = js2sg_color(js, clear_val); + } + JS_FreeValue(js, clear_val); + } + JS_FreeValue(js, col); + } + } + JS_FreeValue(js, colors_val); + + // Depth + JSValue depth_val = JS_GetPropertyStr(js, action_val, "depth"); + if (JS_IsObject(depth_val)) { + JS_GETPROP(js, pass.action.depth.load_action, depth_val, load, sg_load_action); + JS_GETPROP(js, pass.action.depth.store_action, depth_val, store, sg_store_action); + JS_GETPROP(js, pass.action.depth.clear_value, depth_val, clear, number); + } + JS_FreeValue(js, depth_val); + + // Stencil + JSValue stencil_val = JS_GetPropertyStr(js, action_val, "stencil"); + if (JS_IsObject(stencil_val)) { + JS_GETPROP(js, pass.action.stencil.load_action, stencil_val, load, sg_load_action); + JS_GETPROP(js, pass.action.stencil.store_action, stencil_val, store, sg_store_action); + JS_GETPROP(js, pass.action.stencil.clear_value, stencil_val, clear, number); + } + JS_FreeValue(js, stencil_val); + } + JS_FreeValue(js, action_val); + + // Swapchain + JSValue swapchain_val = JS_GetPropertyStr(js, cfg, "swapchain"); + if (JS_IsObject(swapchain_val)) { + JS_GETPROP(js, pass.swapchain.width, swapchain_val, width, number); + JS_GETPROP(js, pass.swapchain.height, swapchain_val, height, number); + JS_GETPROP(js, pass.swapchain.sample_count, swapchain_val, samples, number); + JS_GETPROP(js, pass.swapchain.color_format, swapchain_val, color_format, sg_pixel_format); + JS_GETPROP(js, pass.swapchain.depth_format, swapchain_val, depth_format, sg_pixel_format); + } + JS_FreeValue(js, swapchain_val); + + // Compute pass + pass.compute = JS_GETBOOL(js, cfg, "compute"); + } + + sg_begin_pass(&pass); +) + +// gfx.end_pass() +JSC_CCALL(gfx_end_pass, + sg_end_pass(); +) + +// gfx.apply_pipeline(pip_id) +JSC_CCALL(gfx_apply_pipeline, + sg_pipeline pip = { .id = js2number(js, argv[0]) }; + sg_apply_pipeline(pip); +) + +// gfx.apply_bindings(bindings) +// bindings: { vertex_buffers: [{buffer, offset}], index_buffer: {buffer, offset}, samplers: [...] } +JSC_CCALL(gfx_apply_bindings, + sg_bindings bind = {0}; + JSValue cfg = argv[0]; + + // Vertex buffers + JSValue vb_val = JS_GetPropertyStr(js, cfg, "vertex_buffers"); + if (JS_IsArray(js, vb_val)) { + int len = 0; + JSValue len_val = JS_GetPropertyStr(js, vb_val, "length"); + JS_ToInt32(js, &len, len_val); + JS_FreeValue(js, len_val); + + for (int i = 0; i < len && i < SG_MAX_VERTEXBUFFER_BINDSLOTS; i++) { + JSValue vb = JS_GetPropertyUint32(js, vb_val, i); + if (JS_IsNumber(vb)) { + bind.vertex_buffers[i].id = js2number(js, vb); + } else if (JS_IsObject(vb)) { + JSValue buf_val = JS_GetPropertyStr(js, vb, "buffer"); + bind.vertex_buffers[i].id = js2number(js, buf_val); + JS_FreeValue(js, buf_val); + JS_GETPROP(js, bind.vertex_buffer_offsets[i], vb, offset, number); + } + JS_FreeValue(js, vb); + } + } + JS_FreeValue(js, vb_val); + + // Index buffer + JSValue ib_val = JS_GetPropertyStr(js, cfg, "index_buffer"); + if (JS_IsNumber(ib_val)) { + bind.index_buffer.id = js2number(js, ib_val); + } else if (JS_IsObject(ib_val)) { + JSValue buf_val = JS_GetPropertyStr(js, ib_val, "buffer"); + bind.index_buffer.id = js2number(js, buf_val); + JS_FreeValue(js, buf_val); + JS_GETPROP(js, bind.index_buffer_offset, ib_val, offset, number); + } + JS_FreeValue(js, ib_val); + + // Samplers + JSValue smp_val = JS_GetPropertyStr(js, cfg, "samplers"); + if (JS_IsArray(js, smp_val)) { + int len = 0; + JSValue len_val = JS_GetPropertyStr(js, smp_val, "length"); + JS_ToInt32(js, &len, len_val); + JS_FreeValue(js, len_val); + + for (int i = 0; i < len && i < SG_MAX_SAMPLER_BINDSLOTS; i++) { + JSValue s = JS_GetPropertyUint32(js, smp_val, i); + bind.samplers[i].id = js2number(js, s); + JS_FreeValue(js, s); + } + } + JS_FreeValue(js, smp_val); + + sg_apply_bindings(&bind); +) + +// gfx.apply_uniforms(stage, slot, data) +// stage: "vertex" or "fragment" +JSC_CCALL(gfx_apply_uniforms, + int slot = js2number(js, argv[0]); + + size_t data_size; + void *data_ptr = js_get_blob_data(js, &data_size, argv[1]); + if (data_ptr && data_ptr != (void*)-1) { + sg_apply_uniforms(slot, &(sg_range){ .ptr = data_ptr, .size = data_size }); + } +) + +// gfx.apply_viewport(x, y, w, h, origin_top_left) +JSC_CCALL(gfx_apply_viewport, + int x = js2number(js, argv[0]); + int y = js2number(js, argv[1]); + int w = js2number(js, argv[2]); + int h = js2number(js, argv[3]); + int origin_top_left = argc > 4 ? js2bool(js, argv[4]) : true; + sg_apply_viewport(x, y, w, h, origin_top_left); +) + +// gfx.apply_scissor_rect(x, y, w, h, origin_top_left) +JSC_CCALL(gfx_apply_scissor_rect, + int x = js2number(js, argv[0]); + int y = js2number(js, argv[1]); + int w = js2number(js, argv[2]); + int h = js2number(js, argv[3]); + int origin_top_left = argc > 4 ? js2bool(js, argv[4]) : true; + sg_apply_scissor_rect(x, y, w, h, origin_top_left); +) + +// gfx.draw(base_element, num_elements, num_instances) +JSC_CCALL(gfx_draw, + int base = js2number(js, argv[0]); + int num = js2number(js, argv[1]); + int instances = argc > 2 ? js2number(js, argv[2]) : 1; + sg_draw(base, num, instances); +) + +// gfx.dispatch(x, y, z) - For compute passes +JSC_CCALL(gfx_dispatch, + int x = js2number(js, argv[0]); + int y = argc > 1 ? js2number(js, argv[1]) : 1; + int z = argc > 2 ? js2number(js, argv[2]) : 1; + sg_dispatch(x, y, z); +) + +// ============================================================================ +// QUERY +// ============================================================================ + +// gfx.query_features() -> object +JSC_CCALL(gfx_query_features, + sg_features f = sg_query_features(); + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "origin_top_left", JS_NewBool(js, f.origin_top_left)); + JS_SetPropertyStr(js, obj, "image_clamp_to_border", JS_NewBool(js, f.image_clamp_to_border)); + JS_SetPropertyStr(js, obj, "mrt_independent_blend_state", JS_NewBool(js, f.mrt_independent_blend_state)); + JS_SetPropertyStr(js, obj, "mrt_independent_write_mask", JS_NewBool(js, f.mrt_independent_write_mask)); + JS_SetPropertyStr(js, obj, "compute", JS_NewBool(js, f.compute)); + return obj; +) + +// gfx.query_limits() -> object +JSC_CCALL(gfx_query_limits, + sg_limits l = sg_query_limits(); + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "max_image_size_2d", JS_NewInt32(js, l.max_image_size_2d)); + JS_SetPropertyStr(js, obj, "max_image_size_cube", JS_NewInt32(js, l.max_image_size_cube)); + JS_SetPropertyStr(js, obj, "max_image_size_3d", JS_NewInt32(js, l.max_image_size_3d)); + JS_SetPropertyStr(js, obj, "max_image_size_array", JS_NewInt32(js, l.max_image_size_array)); + JS_SetPropertyStr(js, obj, "max_image_array_layers", JS_NewInt32(js, l.max_image_array_layers)); + JS_SetPropertyStr(js, obj, "max_vertex_attrs", JS_NewInt32(js, l.max_vertex_attrs)); + return obj; +) + +// ============================================================================ +// MODULE INIT +// ============================================================================ + +static const JSCFunctionListEntry js_gfx_funcs[] = { + JS_CFUNC_DEF("setup", 1, js_gfx_setup), + JS_CFUNC_DEF("shutdown", 0, js_gfx_shutdown), + JS_CFUNC_DEF("is_valid", 0, js_gfx_is_valid), + JS_CFUNC_DEF("backend", 0, js_gfx_backend), + JS_CFUNC_DEF("commit", 0, js_gfx_commit), + JS_CFUNC_DEF("reset_state_cache", 0, js_gfx_reset_state_cache), + + JS_CFUNC_DEF("make_buffer", 1, js_gfx_make_buffer), + JS_CFUNC_DEF("destroy_buffer", 1, js_gfx_destroy_buffer), + JS_CFUNC_DEF("update_buffer", 2, js_gfx_update_buffer), + JS_CFUNC_DEF("append_buffer", 2, js_gfx_append_buffer), + + JS_CFUNC_DEF("make_image", 1, js_gfx_make_image), + JS_CFUNC_DEF("destroy_image", 1, js_gfx_destroy_image), + JS_CFUNC_DEF("update_image", 2, js_gfx_update_image), + + JS_CFUNC_DEF("make_sampler", 1, js_gfx_make_sampler), + JS_CFUNC_DEF("destroy_sampler", 1, js_gfx_destroy_sampler), + + JS_CFUNC_DEF("make_shader", 1, js_gfx_make_shader), + JS_CFUNC_DEF("destroy_shader", 1, js_gfx_destroy_shader), + + JS_CFUNC_DEF("make_pipeline", 1, js_gfx_make_pipeline), + JS_CFUNC_DEF("destroy_pipeline", 1, js_gfx_destroy_pipeline), + + JS_CFUNC_DEF("begin_pass", 1, js_gfx_begin_pass), + JS_CFUNC_DEF("end_pass", 0, js_gfx_end_pass), + JS_CFUNC_DEF("apply_pipeline", 1, js_gfx_apply_pipeline), + JS_CFUNC_DEF("apply_bindings", 1, js_gfx_apply_bindings), + JS_CFUNC_DEF("apply_uniforms", 2, js_gfx_apply_uniforms), + JS_CFUNC_DEF("apply_viewport", 5, js_gfx_apply_viewport), + JS_CFUNC_DEF("apply_scissor_rect", 5, js_gfx_apply_scissor_rect), + JS_CFUNC_DEF("draw", 3, js_gfx_draw), + JS_CFUNC_DEF("dispatch", 3, js_gfx_dispatch), + + JS_CFUNC_DEF("query_features", 0, js_gfx_query_features), + JS_CFUNC_DEF("query_limits", 0, js_gfx_query_limits), +}; + +CELL_USE_INIT( + JSValue gfx = JS_NewObject(js); + JS_SetPropertyFunctionList(js, gfx, js_gfx_funcs, sizeof(js_gfx_funcs)/sizeof(js_gfx_funcs[0])); + return gfx; +) \ No newline at end of file