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