Files
cell-sokol/gfx.c
2025-12-11 00:10:41 -06:00

1012 lines
35 KiB
C

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