Files
cell/source/jsffi.c

7789 lines
238 KiB
C

#include "jsffi.h"
#include "script.h"
#include "font.h"
#include "datastream.h"
#include "stb_ds.h"
#include "stb_image.h"
#include "stb_rect_pack.h"
#define STB_DXT_IMPLEMENTATION
#include "stb_dxt.h"
#include "string.h"
#include "spline.h"
#include <assert.h>
#include <time.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <limits.h>
#include "render.h"
#include "model.h"
#include "HandmadeMath.h"
#include "par/par_streamlines.h"
#include "par/par_shapes.h"
#include <stdint.h>
#include "timer.h"
#include <signal.h>
#include "cute_aseprite.h"
#include "cgltf.h"
#include "physfs.h"
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#include <sys/utsname.h>
#ifdef __linux__
#include <sys/sysinfo.h>
#endif
#endif
#include "wildmatch.h"
#include "freelist.h"
#include "sprite.h"
#include "rtree.h"
typedef struct rtree rtree;
#include <SDL3/SDL.h>
#include <SDL3/SDL_gpu.h>
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_properties.h>
#include <SDL3/SDL_loadso.h>
#include <SDL3/SDL_cpuinfo.h>
#ifdef __APPLE__
#include <Accelerate/Accelerate.h>
//#else
//#include <cblas.h>
#endif
#define STATE_VECTOR_LENGTH 624
#define STATE_VECTOR_M 397 /* changes to STATE_VECTOR_LENGTH also require changes to this */
typedef struct tagMTRand {
uint32_t mt[STATE_VECTOR_LENGTH];
int32_t index;
} MTRand;
static MTRand mrand;
#define UPPER_MASK 0x80000000
#define LOWER_MASK 0x7fffffff
#define TEMPERING_MASK_B 0x9d2c5680
#define TEMPERING_MASK_C 0xefc60000
inline static void m_seedRand(MTRand* rand, uint32_t seed) {
/* set initial seeds to mt[STATE_VECTOR_LENGTH] using the generator
* from Line 25 of Table 1 in: Donald Knuth, "The Art of Computer
* Programming," Vol. 2 (2nd Ed.) pp.102.
*/
rand->mt[0] = seed & 0xffffffff;
for(rand->index=1; rand->index<STATE_VECTOR_LENGTH; rand->index++) {
rand->mt[rand->index] = (6069 * rand->mt[rand->index-1]) & 0xffffffff;
}
}
uint32_t genRandLong(MTRand* rand) {
uint32_t y;
static uint32_t mag[2] = {0x0, 0x9908b0df}; /* mag[x] = x * 0x9908b0df for x = 0,1 */
if(rand->index >= STATE_VECTOR_LENGTH || rand->index < 0) {
/* generate STATE_VECTOR_LENGTH words at a time */
int32_t kk;
if(rand->index >= STATE_VECTOR_LENGTH+1 || rand->index < 0) {
m_seedRand(rand, 4357);
}
for(kk=0; kk<STATE_VECTOR_LENGTH-STATE_VECTOR_M; kk++) {
y = (rand->mt[kk] & UPPER_MASK) | (rand->mt[kk+1] & LOWER_MASK);
rand->mt[kk] = rand->mt[kk+STATE_VECTOR_M] ^ (y >> 1) ^ mag[y & 0x1];
}
for(; kk<STATE_VECTOR_LENGTH-1; kk++) {
y = (rand->mt[kk] & UPPER_MASK) | (rand->mt[kk+1] & LOWER_MASK);
rand->mt[kk] = rand->mt[kk+(STATE_VECTOR_M-STATE_VECTOR_LENGTH)] ^ (y >> 1) ^ mag[y & 0x1];
}
y = (rand->mt[STATE_VECTOR_LENGTH-1] & UPPER_MASK) | (rand->mt[0] & LOWER_MASK);
rand->mt[STATE_VECTOR_LENGTH-1] = rand->mt[STATE_VECTOR_M-1] ^ (y >> 1) ^ mag[y & 0x1];
rand->index = 0;
}
y = rand->mt[rand->index++];
y ^= (y >> 11);
y ^= (y << 7) & TEMPERING_MASK_B;
y ^= (y << 15) & TEMPERING_MASK_C;
y ^= (y >> 18);
return y;
}
double genRand(MTRand* rand) {
return((double)genRandLong(rand) / (uint32_t)0xffffffff);
}
static JSAtom width_atom;
static JSAtom height_atom;
static JSAtom l_atom;
static JSAtom r_atom;
static JSAtom t_atom;
static JSAtom b_atom;
static JSAtom x_atom;
static JSAtom y_atom;
static JSAtom anchor_x_atom;
static JSAtom anchor_y_atom;
static JSAtom pos_atom;
static JSAtom uv_atom;
static JSAtom color_atom;
static JSAtom indices_atom;
static JSAtom vertices_atom;
static JSAtom dst_atom;
static JSAtom src_atom;
static JSAtom count_atom;
static JSAtom num_indices_atom;
static JSAtom transform_atom;
static JSAtom image_atom;
static JSAtom layer_atom;
static JSAtom texture_atom;
static JSAtom parent_atom;
// GPU ATOMS
static JSAtom cw_atom;
static JSAtom ccw_atom;
static JSAtom zero_atom;
static JSAtom one_atom;
static JSAtom add_atom;
static JSAtom sub_atom;
static JSAtom rev_sub_atom;
static JSAtom min_atom;
static JSAtom max_atom;
static JSAtom none_atom;
static JSAtom norm_atom;
static JSAtom front_atom;
static JSAtom back_atom;
static JSAtom never_atom;
static JSAtom less_atom;
static JSAtom equal_atom;
static JSAtom less_equal_atom;
static JSAtom greater_atom;
static JSAtom not_equal_atom;
static JSAtom greater_equal_atom;
static JSAtom always_atom;
static JSAtom keep_atom;
static JSAtom zero_stencil_atom;
static JSAtom replace_atom;
static JSAtom incr_clamp_atom;
static JSAtom decr_clamp_atom;
static JSAtom invert_atom;
static JSAtom incr_wrap_atom;
static JSAtom decr_wrap_atom;
static JSAtom point_atom;
static JSAtom line_atom;
static JSAtom linestrip_atom;
static JSAtom triangle_atom;
static JSAtom trianglestrip_atom;
static JSAtom src_color_atom;
static JSAtom one_minus_src_color_atom;
static JSAtom dst_color_atom;
static JSAtom one_minus_dst_color_atom;
static JSAtom src_alpha_atom;
static JSAtom one_minus_src_alpha_atom;
static JSAtom dst_alpha_atom;
static JSAtom one_minus_dst_alpha_atom;
static JSAtom constant_color_atom;
static JSAtom one_minus_constant_color_atom;
static JSAtom src_alpha_saturate_atom;
static JSAtom none_cull_atom;
static JSAtom front_cull_atom;
static JSAtom back_cull_atom;
// For sampler filtering:
static JSAtom nearest_atom;
static JSAtom linear_atom;
// For sampler mipmap modes:
static JSAtom mipmap_nearest_atom;
static JSAtom mipmap_linear_atom;
// For sampler address modes:
static JSAtom repeat_atom;
static JSAtom mirror_atom;
static JSAtom clamp_edge_atom;
static JSAtom clamp_border_atom;
static JSAtom vertex_atom;
static JSAtom index_atom;
static JSAtom indirect_atom;
static JSAtom rect_atom;
typedef struct texture_vertex {
float x, y, z;
float u, v;
uint8_t r, g, b,a;
} texture_vertex;
#pragma pack(push, 1)
typedef struct shader_globals {
HMM_Mat4 world_to_projection;
HMM_Mat4 projection_to_world;
HMM_Mat4 world_to_view;
HMM_Mat4 view_to_projection;
HMM_Vec3 camera_pos_world;
HMM_Vec3 camera_dir_world;
float viewport_min_z;
float viewport_max_z;
HMM_Vec2 viewport_size;
HMM_Vec2 viewport_offset;
HMM_Vec2 render_size;
float time;
} shader_globals;
#pragma pack(pop)
static inline size_t typed_array_bytes(JSTypedArrayEnum type) {
switch(type) {
case JS_TYPED_ARRAY_UINT8C:
case JS_TYPED_ARRAY_INT8:
case JS_TYPED_ARRAY_UINT8:
return 1;
case JS_TYPED_ARRAY_INT16:
case JS_TYPED_ARRAY_UINT16:
return 2;
case JS_TYPED_ARRAY_INT32:
case JS_TYPED_ARRAY_UINT32:
case JS_TYPED_ARRAY_FLOAT32:
return 4;
case JS_TYPED_ARRAY_BIG_INT64:
case JS_TYPED_ARRAY_BIG_UINT64:
case JS_TYPED_ARRAY_FLOAT64:
return 8;
default:
return 0; // Return 0 for unknown types
}
}
#define JS_GETNUM(JS,VAL,I,TO,TYPE) { \
JSValue val = JS_GetPropertyUint32(JS,VAL,I); \
TO = js2##TYPE(JS, val); \
JS_FreeValue(JS, val); } \
static inline int js2bool(JSContext *js, JSValue v) { return JS_ToBool(js,v);}
static inline const char *js2cstring(JSContext *js, JSValue v) { return JS_ToCString(js,v); }
JSValue number2js(JSContext *js, double g) { return JS_NewFloat64(js,g); }
double js2number(JSContext *js, JSValue v) {
double g;
JS_ToFloat64(js, &g, v);
if (isnan(g)) g = 0;
return g;
}
JSValue js_getpropertyuint32(JSContext *js, JSValue v, unsigned int i)
{
JSValue ret = JS_GetPropertyUint32(js,v,i);
JS_FreeValue(js,ret);
return ret;
}
double js_getnum_uint32(JSContext *js, JSValue v, unsigned int i)
{
JSValue val = JS_GetPropertyUint32(js,v,i);
double ret = js2number(js, val);
JS_FreeValue(js,val);
return ret;
}
static HMM_Mat3 cam_mat;
double js_getnum_str(JSContext *js, JSValue v, const char *str)
{
JSValue val = JS_GetPropertyStr(js,v,str);
double ret = js2number(js,val);
JS_FreeValue(js,val);
return ret;
}
double js_getnum(JSContext *js, JSValue v, JSAtom prop)
{
JSValue val = JS_GetProperty(js,v,prop);
double ret = js2number(js,val);
JS_FreeValue(js,val);
return ret;
}
#define JS_GETPROP(JS, TARGET, VALUE, PROP, TYPE) {\
JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#PROP); \
TARGET = js2##TYPE(JS, __##PROP##__v); \
JS_FreeValue(JS,__##PROP##__v); }\
#define JS_GETATOM(JS, TARGET, VALUE, ATOM, TYPE) {\
JSValue __##PROP##__v = JS_GetProperty(JS,VALUE,ATOM); \
TARGET = js2##TYPE(JS, __##PROP##__v); \
JS_FreeValue(JS,__##PROP##__v); }\
int JS_GETBOOL(JSContext *js, JSValue v, const char *prop)
{
JSValue __v = JS_GetPropertyStr(js,v,prop);
int r = JS_ToBool(js,__v);
JS_FreeValue(js,__v);
return r;
}
JSValue js_getproperty(JSContext *js, JSValue v, JSAtom atom)
{
JSValue ret = JS_GetProperty(js, v, atom);
JS_FreeValue(js,ret);
return ret;
}
SDL_Window *global_window;
void free_gpu_buffer(JSRuntime *rt, void *opaque, void *ptr)
{
free(ptr);
}
JSValue make_gpu_buffer(JSContext *js, void *data, size_t size, int type, int elements, int copy, int index)
{
JSValue tstack[3];
tstack[1] = JS_UNDEFINED;
tstack[2] = JS_UNDEFINED;
if (copy)
tstack[0] = JS_NewArrayBufferCopy(js,data,size);//, make_gpu_buffer, NULL, 1);
else
tstack[0] = JS_NewArrayBuffer(js,data,size,free_gpu_buffer, NULL, 0);
JSValue ret = JS_NewTypedArray(js, 3, tstack, type);
JS_SetPropertyStr(js,ret,"stride", number2js(js,typed_array_bytes(type)*elements));
JS_SetPropertyStr(js,ret,"elen", number2js(js,typed_array_bytes(type)));
JS_SetPropertyStr(js,ret,"index", JS_NewBool(js,index));
JS_FreeValue(js,tstack[0]);
return ret;
}
void *get_gpu_buffer(JSContext *js, JSValue argv, size_t *stride, size_t *size)
{
size_t o, len, bytes, msize;
JSValue buf = JS_GetTypedArrayBuffer(js, argv, &o, &len, &bytes);
void *data = JS_GetArrayBuffer(js, &msize, buf);
JS_FreeValue(js,buf);
if (stride) *stride = js_getnum_str(js, argv, "stride");
if (size) *size = msize;
return data;
}
typedef struct {
JSValue val;
void *ptr;
size_t size;
int need_new;
} BufferCheckResult;
static BufferCheckResult get_or_extend_buffer(
JSContext *js,
JSValue old_mesh,
JSAtom prop,
size_t needed_size,
int type,
int elements_per_item,
int copy,
int index
) {
BufferCheckResult res = { JS_UNDEFINED, NULL, 0, 0 };
if (!JS_IsUndefined(old_mesh)) {
JSValue old_buf = JS_GetProperty(js, old_mesh, prop);
if (!JS_IsUndefined(old_buf)) {
size_t old_size;
void *data = get_gpu_buffer(js, old_buf, NULL, &old_size);
if (data && old_size >= needed_size) {
// Old buffer is large enough
res.val = old_buf; // keep it
res.ptr = data;
res.size = old_size;
return res;
}
JS_FreeValue(js, old_buf);
}
}
// If we reach here, we need a new buffer
res.need_new = 1;
return res;
}
#ifndef _WIN32
#include <sys/resource.h>
#endif
#if (defined(_WIN32) || defined(__WIN32__))
#include <direct.h>
#define mkdir(x,y) _mkdir(x)
#endif
struct lrtb {
float l;
float r;
float t;
float b;
};
static SDL_GPUDevice *global_gpu;
SDL_GPUGraphicsPipelineTargetInfo js2SDL_GPUGraphicsPipelineTargetInfo(JSContext *js, JSValue v)
{
SDL_GPUGraphicsPipelineTargetInfo info = {0};
return info;
}
SDL_GPUSampleCount js2SDL_GPUSampleCount(JSContext *js, JSValue v)
{
int n = js2number(js,v);
switch(n) {
case 1: return SDL_GPU_SAMPLECOUNT_1;
case 2: return SDL_GPU_SAMPLECOUNT_2;
case 4: return SDL_GPU_SAMPLECOUNT_4;
case 8: return SDL_GPU_SAMPLECOUNT_8;
}
return -1;
}
#define JS2ENUM(NAME, RETS, VALS) \
int js2##NAME(JSContext *js, JSValue v) { \
if (JS_IsUndefined(v)) return 0; \
const char *str = JS_ToCString(js, v); \
int *rets = (RETS); \
const char **vals = (VALS); \
/* Compute how many entries are in the arrays */ \
int n = (int)(sizeof((RETS)) / sizeof((RETS)[0])); \
for(int i = 0; i < n; i++) \
if(!strcmp(vals[i], str)) { \
JS_FreeCString(js, str); \
return rets[i]; \
} \
JS_FreeCString(js, str); \
return 0; \
}
static int rets_SDL_GPUSwapchainComposition[] = {
SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
SDL_GPU_SWAPCHAINCOMPOSITION_SDR_LINEAR,
SDL_GPU_SWAPCHAINCOMPOSITION_HDR_EXTENDED_LINEAR,
// SDL_GPU_SWAPCHAINCOMPOSITION_HDR10_ST2084
};
static const char *vals_SDL_GPUSwapchainComposition[] = {
"sdr",
"linear",
"hdr",
// "hdr10"
};
JS2ENUM(SDL_GPUSwapchainComposition, rets_SDL_GPUSwapchainComposition, vals_SDL_GPUSwapchainComposition)
static int rets_SDL_FlipMode[] = {
SDL_FLIP_NONE,
SDL_FLIP_HORIZONTAL,
SDL_FLIP_VERTICAL
};
static const char *vals_SDL_FlipMode[] = {
"none",
"horizontal",
"vertical"
};
JS2ENUM(SDL_FlipMode, rets_SDL_FlipMode, vals_SDL_FlipMode)
/* -------------------------------------------------------
1) SDL_GPUBlendFactor
------------------------------------------------------- */
static int rets_SDL_GPUBlendFactor[] = {
SDL_GPU_BLENDFACTOR_INVALID,
SDL_GPU_BLENDFACTOR_ZERO,
SDL_GPU_BLENDFACTOR_ONE,
SDL_GPU_BLENDFACTOR_SRC_COLOR,
SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_COLOR,
SDL_GPU_BLENDFACTOR_DST_COLOR,
SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_COLOR,
SDL_GPU_BLENDFACTOR_SRC_ALPHA,
SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
SDL_GPU_BLENDFACTOR_DST_ALPHA,
SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_ALPHA,
SDL_GPU_BLENDFACTOR_CONSTANT_COLOR,
SDL_GPU_BLENDFACTOR_ONE_MINUS_CONSTANT_COLOR,
SDL_GPU_BLENDFACTOR_SRC_ALPHA_SATURATE
};
static const char *vals_SDL_GPUBlendFactor[] = {
"invalid",
"zero",
"one",
"src_color",
"one_minus_src_color",
"dst_color",
"one_minus_dst_color",
"src_alpha",
"one_minus_src_alpha",
"dst_alpha",
"one_minus_dst_alpha",
"constant_color",
"one_minus_constant_color",
"src_alpha_saturate"
};
JS2ENUM(SDL_GPUBlendFactor, rets_SDL_GPUBlendFactor, vals_SDL_GPUBlendFactor)
/* -------------------------------------------------------
2) SDL_GPUBlendOp
------------------------------------------------------- */
static int rets_SDL_GPUBlendOp[] = {
SDL_GPU_BLENDOP_INVALID,
SDL_GPU_BLENDOP_ADD,
SDL_GPU_BLENDOP_SUBTRACT,
SDL_GPU_BLENDOP_REVERSE_SUBTRACT,
SDL_GPU_BLENDOP_MIN,
SDL_GPU_BLENDOP_MAX
};
static const char *vals_SDL_GPUBlendOp[] = {
"invalid",
"add",
"subtract",
"reverse_subtract",
"min",
"max"
};
JS2ENUM(SDL_GPUBlendOp, rets_SDL_GPUBlendOp, vals_SDL_GPUBlendOp)
/* -------------------------------------------------------
3) SDL_GPUCompareOp
------------------------------------------------------- */
static int rets_SDL_GPUCompareOp[] = {
SDL_GPU_COMPAREOP_INVALID,
SDL_GPU_COMPAREOP_NEVER,
SDL_GPU_COMPAREOP_LESS,
SDL_GPU_COMPAREOP_EQUAL,
SDL_GPU_COMPAREOP_LESS_OR_EQUAL,
SDL_GPU_COMPAREOP_GREATER,
SDL_GPU_COMPAREOP_NOT_EQUAL,
SDL_GPU_COMPAREOP_GREATER_OR_EQUAL,
SDL_GPU_COMPAREOP_ALWAYS
};
static const char *vals_SDL_GPUCompareOp[] = {
"invalid",
"never",
"less",
"equal",
"less_or_equal",
"greater",
"not_equal",
"greater_or_equal",
"always"
};
JS2ENUM(SDL_GPUCompareOp, rets_SDL_GPUCompareOp, vals_SDL_GPUCompareOp)
/* -------------------------------------------------------
4) SDL_GPUCullMode
------------------------------------------------------- */
static int rets_SDL_GPUCullMode[] = {
SDL_GPU_CULLMODE_NONE,
SDL_GPU_CULLMODE_FRONT,
SDL_GPU_CULLMODE_BACK
};
static const char *vals_SDL_GPUCullMode[] = {
"none",
"front",
"back"
};
JS2ENUM(SDL_GPUCullMode, rets_SDL_GPUCullMode, vals_SDL_GPUCullMode)
/* -------------------------------------------------------
5) SDL_GPUFillMode
------------------------------------------------------- */
static int rets_SDL_GPUFillMode[] = {
SDL_GPU_FILLMODE_FILL,
SDL_GPU_FILLMODE_LINE
};
static const char *vals_SDL_GPUFillMode[] = {
"fill",
"line"
};
JS2ENUM(SDL_GPUFillMode, rets_SDL_GPUFillMode, vals_SDL_GPUFillMode)
/* -------------------------------------------------------
6) SDL_GPUFilter
------------------------------------------------------- */
static int rets_SDL_GPUFilter[] = {
SDL_GPU_FILTER_NEAREST,
SDL_GPU_FILTER_LINEAR
};
static const char *vals_SDL_GPUFilter[] = {
"nearest",
"linear"
};
JS2ENUM(SDL_GPUFilter, rets_SDL_GPUFilter, vals_SDL_GPUFilter)
/* -------------------------------------------------------
7) SDL_GPUFrontFace
------------------------------------------------------- */
static int rets_SDL_GPUFrontFace[] = {
SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE,
SDL_GPU_FRONTFACE_CLOCKWISE
};
static const char *vals_SDL_GPUFrontFace[] = {
"counter_clockwise",
"clockwise"
};
JS2ENUM(SDL_GPUFrontFace, rets_SDL_GPUFrontFace, vals_SDL_GPUFrontFace)
/* -------------------------------------------------------
8) SDL_GPULoadOp
------------------------------------------------------- */
static int rets_SDL_GPULoadOp[] = {
SDL_GPU_LOADOP_LOAD,
SDL_GPU_LOADOP_CLEAR,
SDL_GPU_LOADOP_DONT_CARE
};
static const char *vals_SDL_GPULoadOp[] = {
"load",
"clear",
"dont_care"
};
JS2ENUM(SDL_GPULoadOp, rets_SDL_GPULoadOp, vals_SDL_GPULoadOp)
/* -------------------------------------------------------
9) SDL_GPUPresentMode
------------------------------------------------------- */
static int rets_SDL_GPUPresentMode[] = {
SDL_GPU_PRESENTMODE_VSYNC,
SDL_GPU_PRESENTMODE_IMMEDIATE,
SDL_GPU_PRESENTMODE_MAILBOX
};
static const char *vals_SDL_GPUPresentMode[] = {
"vsync",
"immediate",
"mailbox"
};
JS2ENUM(SDL_GPUPresentMode, rets_SDL_GPUPresentMode, vals_SDL_GPUPresentMode)
/* -------------------------------------------------------
10) SDL_GPUPrimitiveType
------------------------------------------------------- */
static int rets_SDL_GPUPrimitiveType[] = {
SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
SDL_GPU_PRIMITIVETYPE_TRIANGLESTRIP,
SDL_GPU_PRIMITIVETYPE_LINELIST,
SDL_GPU_PRIMITIVETYPE_LINESTRIP,
SDL_GPU_PRIMITIVETYPE_POINTLIST
};
static const char *vals_SDL_GPUPrimitiveType[] = {
"triangle",
"trianglestrip",
"line",
"linestrip",
"point"
};
JS2ENUM(SDL_GPUPrimitiveType, rets_SDL_GPUPrimitiveType, vals_SDL_GPUPrimitiveType)
/* -------------------------------------------------------
11) SDL_GPUSamplerAddressMode
------------------------------------------------------- */
static int rets_SDL_GPUSamplerAddressMode[] = {
SDL_GPU_SAMPLERADDRESSMODE_REPEAT,
SDL_GPU_SAMPLERADDRESSMODE_MIRRORED_REPEAT,
SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE
};
static const char *vals_SDL_GPUSamplerAddressMode[] = {
"repeat",
"mirrored_repeat",
"clamp"
};
JS2ENUM(SDL_GPUSamplerAddressMode, rets_SDL_GPUSamplerAddressMode, vals_SDL_GPUSamplerAddressMode)
/* -------------------------------------------------------
12) SDL_GPUSamplerMipmapMode
------------------------------------------------------- */
static int rets_SDL_GPUSamplerMipmapMode[] = {
SDL_GPU_SAMPLERMIPMAPMODE_NEAREST,
SDL_GPU_SAMPLERMIPMAPMODE_LINEAR
};
static const char *vals_SDL_GPUSamplerMipmapMode[] = {
"nearest",
"linear"
};
JS2ENUM(SDL_GPUSamplerMipmapMode, rets_SDL_GPUSamplerMipmapMode, vals_SDL_GPUSamplerMipmapMode)
/* -------------------------------------------------------
13) SDL_GPUStencilOp
------------------------------------------------------- */
static int rets_SDL_GPUStencilOp[] = {
SDL_GPU_STENCILOP_INVALID,
SDL_GPU_STENCILOP_KEEP,
SDL_GPU_STENCILOP_ZERO,
SDL_GPU_STENCILOP_REPLACE,
SDL_GPU_STENCILOP_INCREMENT_AND_CLAMP,
SDL_GPU_STENCILOP_DECREMENT_AND_CLAMP,
SDL_GPU_STENCILOP_INVERT,
SDL_GPU_STENCILOP_INCREMENT_AND_WRAP,
SDL_GPU_STENCILOP_DECREMENT_AND_WRAP
};
static const char *vals_SDL_GPUStencilOp[] = {
"invalid",
"keep",
"zero",
"replace",
"increment_and_clamp",
"decrement_and_clamp",
"invert",
"increment_and_wrap",
"decrement_and_wrap"
};
JS2ENUM(SDL_GPUStencilOp, rets_SDL_GPUStencilOp, vals_SDL_GPUStencilOp)
static int rets_SDL_GPUTextureType[] = {
SDL_GPU_TEXTURETYPE_2D, /**< The texture is a 2-dimensional image. */
SDL_GPU_TEXTURETYPE_2D_ARRAY, /**< The texture is a 2-dimensional array image. */
SDL_GPU_TEXTURETYPE_3D, /**< The texture is a 3-dimensional image. */
SDL_GPU_TEXTURETYPE_CUBE, /**< The texture is a cube image. */
SDL_GPU_TEXTURETYPE_CUBE_ARRAY /**< The texture is a cube array image. */
};
static const char *vals_SDL_GPUTextureType[] = {
"2d",
"2d array",
"3d",
"cube",
"cube array"
};
JS2ENUM(SDL_GPUTextureType, rets_SDL_GPUTextureType, vals_SDL_GPUTextureType)
/* -------------------------------------------------------
14) SDL_GPUStoreOp
------------------------------------------------------- */
static int rets_SDL_GPUStoreOp[] = {
SDL_GPU_STOREOP_STORE,
SDL_GPU_STOREOP_DONT_CARE,
SDL_GPU_STOREOP_RESOLVE,
SDL_GPU_STOREOP_RESOLVE_AND_STORE
};
static const char *vals_SDL_GPUStoreOp[] = {
"store",
"dont_care",
"resolve",
"resolve_and_store"
};
JS2ENUM(SDL_GPUStoreOp, rets_SDL_GPUStoreOp, vals_SDL_GPUStoreOp)
/* -------------------------------------------------------
X) SDL_GPUTextureType
------------------------------------------------------- */
static int rets_SDL_GPUTextureFormat[] = {
SDL_GPU_TEXTUREFORMAT_INVALID,
/* Unsigned Normalized Float Color Formats */
SDL_GPU_TEXTUREFORMAT_A8_UNORM,
SDL_GPU_TEXTUREFORMAT_R8_UNORM,
SDL_GPU_TEXTUREFORMAT_R8G8_UNORM,
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
SDL_GPU_TEXTUREFORMAT_R16_UNORM,
SDL_GPU_TEXTUREFORMAT_R16G16_UNORM,
SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM,
SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM,
SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM,
SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM,
SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM,
SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM,
/* Compressed Unsigned Normalized Float Color Formats */
SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM,
SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM,
SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM,
SDL_GPU_TEXTUREFORMAT_BC4_R_UNORM,
SDL_GPU_TEXTUREFORMAT_BC5_RG_UNORM,
SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM,
/* Compressed Signed Float Color Formats */
SDL_GPU_TEXTUREFORMAT_BC6H_RGB_FLOAT,
/* Compressed Unsigned Float Color Formats */
SDL_GPU_TEXTUREFORMAT_BC6H_RGB_UFLOAT,
/* Signed Normalized Float Color Formats */
SDL_GPU_TEXTUREFORMAT_R8_SNORM,
SDL_GPU_TEXTUREFORMAT_R8G8_SNORM,
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM,
SDL_GPU_TEXTUREFORMAT_R16_SNORM,
SDL_GPU_TEXTUREFORMAT_R16G16_SNORM,
SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM,
/* Signed Float Color Formats */
SDL_GPU_TEXTUREFORMAT_R16_FLOAT,
SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT,
SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT,
SDL_GPU_TEXTUREFORMAT_R32_FLOAT,
SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT,
SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT,
/* Unsigned Float Color Formats */
SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT,
/* Unsigned Integer Color Formats */
SDL_GPU_TEXTUREFORMAT_R8_UINT,
SDL_GPU_TEXTUREFORMAT_R8G8_UINT,
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT,
SDL_GPU_TEXTUREFORMAT_R16_UINT,
SDL_GPU_TEXTUREFORMAT_R16G16_UINT,
SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT,
SDL_GPU_TEXTUREFORMAT_R32_UINT,
SDL_GPU_TEXTUREFORMAT_R32G32_UINT,
SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT,
/* Signed Integer Color Formats */
SDL_GPU_TEXTUREFORMAT_R8_INT,
SDL_GPU_TEXTUREFORMAT_R8G8_INT,
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT,
SDL_GPU_TEXTUREFORMAT_R16_INT,
SDL_GPU_TEXTUREFORMAT_R16G16_INT,
SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT,
SDL_GPU_TEXTUREFORMAT_R32_INT,
SDL_GPU_TEXTUREFORMAT_R32G32_INT,
SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT,
/* SRGB Unsigned Normalized Color Formats */
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB,
SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB,
/* Compressed SRGB Unsigned Normalized Color Formats */
SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB,
SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB,
SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB,
SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB,
/* Depth Formats */
SDL_GPU_TEXTUREFORMAT_D16_UNORM,
SDL_GPU_TEXTUREFORMAT_D24_UNORM,
SDL_GPU_TEXTUREFORMAT_D32_FLOAT,
SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT,
SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT,
/* Compressed ASTC Normalized Float Color Formats*/
SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM,
SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM,
SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM,
SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM,
SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM,
SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM,
SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM,
SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM,
SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM,
SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM,
SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM,
SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM,
SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM,
SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM,
/* Compressed SRGB ASTC Normalized Float Color Formats*/
SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM_SRGB,
SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM_SRGB,
SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM_SRGB,
SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM_SRGB,
SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM_SRGB,
SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM_SRGB,
SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM_SRGB,
SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM_SRGB,
SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM_SRGB,
SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM_SRGB,
SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM_SRGB,
SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM_SRGB,
SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM_SRGB,
SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM_SRGB,
/* Compressed ASTC Signed Float Color Formats*/
SDL_GPU_TEXTUREFORMAT_ASTC_4x4_FLOAT,
SDL_GPU_TEXTUREFORMAT_ASTC_5x4_FLOAT,
SDL_GPU_TEXTUREFORMAT_ASTC_5x5_FLOAT,
SDL_GPU_TEXTUREFORMAT_ASTC_6x5_FLOAT,
SDL_GPU_TEXTUREFORMAT_ASTC_6x6_FLOAT,
SDL_GPU_TEXTUREFORMAT_ASTC_8x5_FLOAT,
SDL_GPU_TEXTUREFORMAT_ASTC_8x6_FLOAT,
SDL_GPU_TEXTUREFORMAT_ASTC_8x8_FLOAT,
SDL_GPU_TEXTUREFORMAT_ASTC_10x5_FLOAT,
SDL_GPU_TEXTUREFORMAT_ASTC_10x6_FLOAT,
SDL_GPU_TEXTUREFORMAT_ASTC_10x8_FLOAT,
SDL_GPU_TEXTUREFORMAT_ASTC_10x10_FLOAT,
SDL_GPU_TEXTUREFORMAT_ASTC_12x10_FLOAT,
SDL_GPU_TEXTUREFORMAT_ASTC_12x12_FLOAT
};
static const char *vals_SDL_GPUTextureFormat[] = {
"invalid",
"a8",
"r8",
"rg8",
"rgba8",
"r16",
"rg16",
"rgba16",
"r10g10b10a2",
"b5g6r5",
"b5g5r5a1",
"b4g4r4a4",
"b8g8r8a8",
"bc1",
"bc2",
"bc3",
"bc4",
"bc5",
"bc7",
"bc6h float",
"bc6h ufloat",
"r8 snorm",
"rg8 snorm",
"rgba8 snorm",
"r16 snorm",
"rg16 snorm",
"rgba16 snorm",
"r16 float",
"rg16 float",
"rgba16 float",
"r32 float",
"rg32 float",
"rgba32 float",
"r11g11b10",
"r8 uint",
"rg8 uint",
"rgba8 uint",
"r16 uint",
"rg16 uint",
"rgba16 uint",
"r32 uint",
"rg32 uint",
"rgba32 uint",
"r8 int",
"rg8 int",
"rgba8 int",
"r16 int",
"rg16 int",
"rgba16 int",
"r32 int",
"rg32 int",
"rgba32 int",
"rgba8 srgb",
"b8g8r8a8 srgb",
"bc1 srgb",
"bc2 srgb",
"bc3 srgb",
"bc7 srgb",
"d16",
"d24",
"d32 float",
"d24 s8",
"d32 float s8",
"astc 4x4",
"astc 5x4",
"astc 5x5",
"astc 6x5",
"astc 6x6",
"astc 8x5",
"astc 8x6",
"astc 8x8",
"astc 10x5",
"astc 10x6",
"astc 10x8",
"astc 10x10",
"astc 12x10",
"astc 12x12",
"astc 4x4 srgb",
"astc 5x4 srgb",
"astc 5x5 srgb",
"astc 6x5 srgb",
"astc 6x6 srgb",
"astc 8x5 srgb",
"astc 8x6 srgb",
"astc 8x8 srgb",
"astc 10x5 srgb",
"astc 10x6 srgb",
"astc 10x8 srgb",
"astc 10x10 srgb",
"astc 12x10 srgb",
"astc 12x12 srgb",
"astc 4x4 float",
"astc 5x4 float",
"astc 5x5 float",
"astc 6x5 float",
"astc 6x6 float",
"astc 8x5 float",
"astc 8x6 float",
"astc 8x8 float",
"astc 10x5 float",
"astc 10x6 float",
"astc 10x8 float",
"astc 10x10 float",
"astc 12x10 float",
"astc 12x12 float"
};
JS2ENUM(SDL_GPUTextureFormat, rets_SDL_GPUTextureFormat, vals_SDL_GPUTextureFormat);
SDL_GPUColorTargetBlendState js2SDL_GPUColorTargetBlendState(JSContext *js, JSValue v)
{
SDL_GPUColorTargetBlendState state = {0};
JS_GETPROP(js,state.src_color_blendfactor,v,src_rgb,SDL_GPUBlendFactor);
JS_GETPROP(js,state.dst_color_blendfactor,v,dst_rgb,SDL_GPUBlendFactor);
JS_GETPROP(js,state.src_alpha_blendfactor,v,src_alpha,SDL_GPUBlendFactor);
JS_GETPROP(js,state.dst_alpha_blendfactor,v,src_alpha,SDL_GPUBlendFactor);
JS_GETPROP(js,state.color_blend_op,v,op_rgb,SDL_GPUBlendOp)
JS_GETPROP(js,state.alpha_blend_op,v,op_alpha,SDL_GPUBlendOp)
JS_GETPROP(js,state.enable_blend,v,enabled,bool)
return state;
}
SDL_GPUColorTargetDescription js2SDL_GPUColorTargetDescription(JSContext *js, JSValue v)
{
SDL_GPUColorTargetDescription dsc = {0};
JS_GETPROP(js,dsc.format,v,format,SDL_GPUTextureFormat)
JS_GETPROP(js,dsc.blend_state,v,blend,SDL_GPUColorTargetBlendState)
return dsc;
}
SDL_GPUStencilOpState js2SDL_GPUStencilOpState(JSContext *js, JSValue v)
{
SDL_GPUStencilOpState state;
memset(&state, 0, sizeof(state));
JSValue compare_val = JS_GetPropertyStr(js, v, "compare");
if(!JS_IsUndefined(compare_val)) state.compare_op = js2SDL_GPUCompareOp(js, compare_val);
JS_FreeValue(js, compare_val);
JSValue fail_val = JS_GetPropertyStr(js, v, "fail");
if(!JS_IsUndefined(fail_val)) state.fail_op = js2SDL_GPUStencilOp(js, fail_val);
JS_FreeValue(js, fail_val);
JSValue depth_fail_val = JS_GetPropertyStr(js, v, "depth_fail");
if(!JS_IsUndefined(depth_fail_val)) state.depth_fail_op = js2SDL_GPUStencilOp(js, depth_fail_val);
JS_FreeValue(js, depth_fail_val);
JSValue pass_val = JS_GetPropertyStr(js, v, "pass");
if(!JS_IsUndefined(pass_val)) state.pass_op = js2SDL_GPUStencilOp(js, pass_val);
JS_FreeValue(js, pass_val);
return state;
}
typedef struct lrtb lrtb;
lrtb js2lrtb(JSContext *js, JSValue v)
{
lrtb ret = {0};
JS_GETATOM(js,ret.l,v,l_atom,number)
JS_GETATOM(js,ret.r,v,r_atom,number)
JS_GETATOM(js,ret.b,v,b_atom,number)
JS_GETATOM(js,ret.t,v,t_atom,number)
return ret;
}
JSValue vec22js(JSContext *js,HMM_Vec2 v)
{
JSValue array = JS_NewArray(js);
JS_SetPropertyUint32(js, array,0,number2js(js,v.x));
JS_SetPropertyUint32(js, array,1,number2js(js,v.y));
return array;
}
char *js2strdup(JSContext *js, JSValue v) {
const char *str = JS_ToCString(js, v);
char *ret = strdup(str);
JS_FreeCString(js, str);
return ret;
}
#include "qjs_macros.h"
void SDL_Window_free(JSRuntime *rt, SDL_Window *w)
{
SDL_DestroyWindow(w);
}
void SDL_Renderer_free(JSRuntime *rt, SDL_Renderer *r)
{
SDL_DestroyRenderer(r);
}
void SDL_Surface_free(JSRuntime *rt, SDL_Surface *s) {
if (s->flags & SDL_SURFACE_PREALLOCATED)
free(s->pixels);
TracyCFreeN(s,"texture memory");
SDL_DestroySurface(s);
}
void SDL_Camera_free(JSRuntime *rt, SDL_Camera *cam)
{
SDL_CloseCamera(cam);
}
void SDL_Cursor_free(JSRuntime *rt, SDL_Cursor *c)
{
SDL_DestroyCursor(c);
}
void SDL_GPUDevice_free(JSRuntime *rt, SDL_GPUDevice *d)
{
SDL_DestroyGPUDevice(d);
}
void SDL_GPUCommandBuffer_free(JSRuntime *rt, SDL_GPUCommandBuffer *c)
{
}
void SDL_Thread_free(JSRuntime *rt, SDL_Thread *t)
{
}
void SDL_GPUComputePass_free(JSRuntime *rt, SDL_GPUComputePass *c) { }
void SDL_GPUCopyPass_free(JSRuntime *rt, SDL_GPUCopyPass *c) { }
void SDL_GPURenderPass_free(JSRuntime *rt, SDL_GPURenderPass *c) { }
#define GPURELEASECLASS(NAME) \
void SDL_GPU##NAME##_free(JSRuntime *rt, SDL_GPU##NAME *c) { \
SDL_ReleaseGPU##NAME(global_gpu, c); } \
QJSCLASS(SDL_GPU##NAME) \
static JSClassID js_sprite_id;
static void js_sprite_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) {
sprite *sp = JS_GetOpaque(val, js_sprite_id);
if (!sp) return;
JS_MarkValue(rt, sp->image, mark_func);
}
QJSCLASSMARK(sprite)
static JSClassID js_transform_id;
static void js_transform_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) {
transform *t = JS_GetOpaque(val, js_transform_id);
if (!t) return;
// Mark the JSValue references stored in your struct
// JS_MarkValue(rt, t->self, mark_func);
JS_MarkValue(rt, t->change_hook, mark_func);
JS_MarkValue(rt, t->jsparent, mark_func);
// Mark the array elements
for (int i = 0; i < arrlen(t->jschildren); i++)
JS_MarkValue(rt, t->jschildren[i], mark_func);
}
QJSCLASSMARK(transform)
QJSCLASS(font)
QJSCLASS(datastream)
static JSClassID js_timer_id;
static void js_timer_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) {
timer *t = JS_GetOpaque(val,js_timer_id);
if (!t) return;
JS_MarkValue(rt, t->fn, mark_func);
}
QJSCLASSMARK(timer)
QJSCLASS(SDL_Window)
QJSCLASS(SDL_Renderer)
QJSCLASS(SDL_Camera)
void SDL_Texture_free(JSRuntime *rt, SDL_Texture *t){
TracyCFreeN(t, "vram");
SDL_DestroyTexture(t);
}
QJSCLASS(SDL_Texture,
TracyCAllocN(n, n->w*n->h*4, "vram");
JS_SetProperty(js, j, width_atom, number2js(js,n->w));
JS_SetProperty(js,j,height_atom,number2js(js,n->h));
)
QJSCLASS(SDL_Surface,
TracyCAllocN(n, n->pitch*n->h, "texture memory");
JS_SetProperty(js, j, width_atom, number2js(js,n->w));
JS_SetProperty(js,j,height_atom,number2js(js,n->h));
)
QJSCLASS(SDL_GPUDevice)
QJSCLASS(SDL_Thread)
GPURELEASECLASS(Buffer)
GPURELEASECLASS(ComputePipeline)
GPURELEASECLASS(GraphicsPipeline)
GPURELEASECLASS(Sampler)
GPURELEASECLASS(Shader)
GPURELEASECLASS(Texture)
GPURELEASECLASS(TransferBuffer)
GPURELEASECLASS(Fence)
QJSCLASS(SDL_GPUCommandBuffer)
QJSCLASS(SDL_GPUComputePass)
QJSCLASS(SDL_GPUCopyPass)
QJSCLASS(SDL_GPURenderPass)
QJSCLASS(SDL_Cursor)
static void PHYSFS_File_free(JSRuntime *rt, PHYSFS_File *f)
{
PHYSFS_close(f);
}
QJSCLASS(PHYSFS_File)
void rtree_free(JSRuntime *rt, rtree *tree)
{
rtree_destroy(tree);
}
QJSCLASS(rtree)
int js_arrlen(JSContext *js,JSValue v) {
if (JS_IsUndefined(v)) return 0;
int len;
len = js_getnum_str(js,v,"length");
return len;
}
// Unpacks a typed array javascript object. If it has a gpu property, returns it, too. Otherwise, if requested, makes one.
void *gpu_buffer_unpack(JSContext *js, SDL_GPUDevice *device, JSValue buffer, size_t *size, void **send_data, SDL_GPUBuffer **send_gpu)
{
size_t o, len, bytes, msize;
JSValue buf = JS_GetTypedArrayBuffer(js, buffer, &o, &len, &bytes);
void *data = JS_GetArrayBuffer(js, &msize, buf);
JS_FreeValue(js,buf);
if (size) *size = msize;
if (send_gpu) {
JSValue gpu = JS_GetPropertyStr(js,buffer,"gpu");
*send_gpu = js2SDL_GPUBuffer(js,gpu);
if (!*send_gpu) {
JSValue idx = JS_GetPropertyStr(js,buffer, "index");
Uint32 usage = JS_ToBool(js,idx) ? SDL_GPU_BUFFERUSAGE_INDEX : SDL_GPU_BUFFERUSAGE_VERTEX;
JS_FreeValue(js,idx);
*send_gpu = SDL_CreateGPUBuffer(device, &(SDL_GPUBufferCreateInfo) { .usage=usage,.size=msize});
if (!*send_gpu) printf("COULDN'T MAKE GPU BUFFER: %s\n", SDL_GetError());
JS_SetPropertyStr(js, buffer, "gpu", SDL_GPUBuffer2js(js,*send_gpu));
}
JS_FreeValue(js,gpu);
}
if (send_data)
*send_data = data;
return data;
}
void *get_typed_buffer(JSContext *js, JSValue argv, size_t *len)
{
size_t o,bytes,size;
JSValue buf = JS_GetTypedArrayBuffer(js,argv,&o,len,&bytes);
void *data = JS_GetArrayBuffer(js,&size,buf);
JS_FreeValue(js,buf);
return data;
}
float *js2floatarray(JSContext *js, JSValue v) {
JSClassID class = JS_GetClassID(v);
if (class == JS_CLASS_FLOAT32_ARRAY) {
size_t len;
return get_typed_buffer(js,v, &len);
}
// switch(p->class_id){}
int len = js_arrlen(js,v);
float *array = malloc(sizeof(float)*len);
for (int i = 0; i < len; i++)
array[i] = js_getnum_uint32(js,v,i);
return array;
}
JSValue angle2js(JSContext *js,double g) {
return number2js(js,g*HMM_RadToTurn);
}
double js2angle(JSContext *js,JSValue v) {
double n = js2number(js,v);
return n * HMM_TurnToRad;
}
typedef HMM_Vec4 colorf;
colorf js2color(JSContext *js,JSValue v) {
if (JS_IsUndefined(v)) return (colorf){1,1,1,1};
JSValue c[4];
for (int i = 0; i < 4; i++) c[i] = JS_GetPropertyUint32(js,v,i);
float a = JS_IsUndefined(c[3]) ? 1.0 : js2number(js,c[3]);
colorf color = {
.r = js2number(js,c[0]),
.g = js2number(js,c[1]),
.b = js2number(js,c[2]),
.a = a,
};
for (int i = 0; i < 4; i++) JS_FreeValue(js,c[i]);
return color;
}
SDL_FColor js2SDL_FColor(JSContext *js, JSValue v)
{
colorf color = js2color(js,v);
return (SDL_FColor){color.r,color.g,color.b,color.a};
}
SDL_GPUTextureSamplerBinding js2SDL_GPUTextureSamplerBinding(JSContext *js, JSValue v)
{
SDL_GPUTextureSamplerBinding b;
JS_GETPROP(js, b.texture, v, texture, SDL_GPUTexture)
JS_GETPROP(js, b.sampler, v, sampler, SDL_GPUSampler)
return b;
}
JSValue color2js(JSContext *js, colorf color)
{
JSValue arr = JS_NewArray(js);
JS_SetPropertyUint32(js, arr,0,number2js(js,(double)color.r));
JS_SetPropertyUint32(js, arr,1,number2js(js,(double)color.g));
JS_SetPropertyUint32(js, arr,2,number2js(js,(double)color.b));
JS_SetPropertyUint32(js, arr,3,number2js(js,(double)color.a));
return arr;
}
HMM_Vec2 js2vec2(JSContext *js,JSValue v)
{
HMM_Vec2 v2;
v2.X = js_getnum_uint32(js,v,0);
v2.Y = js_getnum_uint32(js,v,1);
return v2;
}
HMM_Vec3 js2vec3(JSContext *js,JSValue v)
{
HMM_Vec3 v3;
v3.x = js_getnum_uint32(js, v,0);
v3.y = js_getnum_uint32(js, v,1);
v3.z = js_getnum_uint32(js, v,2);
return v3;
}
float *js2floats(JSContext *js, JSValue v, size_t *len)
{
*len = js_arrlen(js,v);
float *arr = malloc(sizeof(float)* *len);
for (int i = 0; i < *len; i++)
arr[i] = js_getnum_uint32(js,v,i);
return arr;
}
double *js2doubles(JSContext *js, JSValue v, size_t *len)
{
*len = js_arrlen(js,v);
double *arr = malloc(sizeof(double)* *len);
for (int i = 0; i < *len; i++)
arr[i] = js_getnum_uint32(js,v,i);
return arr;
}
HMM_Vec3 js2vec3f(JSContext *js, JSValue v)
{
HMM_Vec3 vec;
if (JS_IsArray(js, v))
return js2vec3(js,v);
else
vec.x = vec.y = vec.z = js2number(js,v);
return vec;
}
JSValue vec32js(JSContext *js, HMM_Vec3 v)
{
JSValue array = JS_NewArray(js);
JS_SetPropertyUint32(js, array,0,number2js(js,v.x));
JS_SetPropertyUint32(js, array,1,number2js(js,v.y));
JS_SetPropertyUint32(js, array,2,number2js(js,v.z));
return array;
}
JSValue vec3f2js(JSContext *js, HMM_Vec3 v)
{
return vec32js(js,v);
}
JSValue quat2js(JSContext *js, HMM_Quat q)
{
JSValue arr = JS_NewArray(js);
JS_SetPropertyUint32(js, arr, 0, number2js(js,q.x));
JS_SetPropertyUint32(js, arr,1,number2js(js,q.y));
JS_SetPropertyUint32(js, arr,2,number2js(js,q.z));
JS_SetPropertyUint32(js, arr,3,number2js(js,q.w));
return arr;
}
HMM_Vec4 js2vec4(JSContext *js, JSValue v)
{
HMM_Vec4 v4;
for (int i = 0; i < 4; i++)
v4.e[i] = js_getnum_uint32(js, v,i);
return v4;
}
double arr_vec_length(JSContext *js,JSValue v)
{
int len = js_arrlen(js,v);
switch(len) {
case 2: return HMM_LenV2(js2vec2(js,v));
case 3: return HMM_LenV3(js2vec3(js,v));
case 4: return HMM_LenV4(js2vec4(js,v));
}
double sum = 0;
for (int i = 0; i < len; i++)
sum += pow(js_getnum_uint32(js, v, i), 2);
return sqrt(sum);
}
HMM_Quat js2quat(JSContext *js,JSValue v)
{
return js2vec4(js,v).quat;
}
JSValue vec42js(JSContext *js, HMM_Vec4 v)
{
JSValue array = JS_NewArray(js);
for (int i = 0; i < 4; i++)
JS_SetPropertyUint32(js, array,i,number2js(js,v.e[i]));
return array;
}
HMM_Vec2 *js2cpvec2arr(JSContext *js,JSValue v) {
HMM_Vec2 *arr = NULL;
int n = js_arrlen(js,v);
arrsetlen(arr,n);
for (int i = 0; i < n; i++)
arr[i] = js2vec2(js,js_getpropertyuint32(js, v, i));
return arr;
}
JSValue vecarr2js(JSContext *js,HMM_Vec2 *points, int n) {
JSValue array = JS_NewArray(js);
for (int i = 0; i < n; i++)
JS_SetPropertyUint32(js, array,i,vec22js(js,points[i]));
return array;
}
rect js2rect(JSContext *js,JSValue v) {
if (JS_IsUndefined(v)) return (rect){0,0,1,1};
rect rect;
JS_GETATOM(js,rect.x,v,x_atom,number)
JS_GETATOM(js,rect.y,v,y_atom,number)
JS_GETATOM(js,rect.w,v,width_atom,number)
JS_GETATOM(js,rect.h,v,height_atom,number)
float anchor_x = js_getnum(js,v, anchor_x_atom);
float anchor_y = js_getnum(js,v, anchor_y_atom);
rect.y -= anchor_y*rect.h;
rect.x -= anchor_x*rect.w;
return rect;
}
rect transform_rect(SDL_Renderer *ren, rect in, HMM_Mat3 *t)
{
HMM_Vec3 bottom_left = (HMM_Vec3){in.x,in.y,1.0};
HMM_Vec3 transformed_bl = HMM_MulM3V3(*t, bottom_left);
in.x = transformed_bl.x;
in.y = transformed_bl.y;
in.y = in.y - in.h; // should be done for any platform that draws rectangles from top left
return in;
}
HMM_Vec2 transform_point(SDL_Renderer *ren, HMM_Vec2 in, HMM_Mat3 *t)
{
rect logical;
SDL_GetRenderLogicalPresentationRect(ren, &logical);
in.y *= -1;
in.y += logical.h;
in.x -= t->Columns[2].x;
in.y -= t->Columns[2].y;
return in;
}
JSValue rect2js(JSContext *js,rect rect) {
JSValue obj = JS_NewObject(js);
JS_SetProperty(js, obj, x_atom, number2js(js,rect.x));
JS_SetProperty(js, obj, y_atom, number2js(js,rect.y));
JS_SetProperty(js, obj, width_atom, number2js(js,rect.w));
JS_SetProperty(js, obj, height_atom, number2js(js,rect.h));
return obj;
}
JSValue ints2js(JSContext *js,int *ints) {
JSValue arr = JS_NewArray(js);
for (int i = 0; i < arrlen(ints); i++)
JS_SetPropertyUint32(js, arr,i, number2js(js,ints[i]));
return arr;
}
int vec_between(HMM_Vec2 p, HMM_Vec2 a, HMM_Vec2 b) {
HMM_Vec2 n;
n.x = b.x - a.x;
n.y = b.y - a.y;
n = HMM_NormV2(n);
return HMM_DotV2(n, HMM_SubV2(p,a)) > 0 && HMM_DotV2(HMM_MulV2F(n, -1), HMM_SubV2(p,b)) > 0;
}
/* Determines between which two points in 'segs' point 'p' falls.
0 indicates 'p' comes before the first point.
arrlen(segs) indicates it comes after the last point.
*/
int point2segindex(HMM_Vec2 p, HMM_Vec2 *segs, double slop) {
float shortest = slop < 0 ? INFINITY : slop;
int best = -1;
for (int i = 0; i < arrlen(segs) - 1; i++) {
float a = (segs[i + 1].y - segs[i].y) / (segs[i + 1].x - segs[i].x);
float c = segs[i].y - (a * segs[i].x);
float b = -1;
float dist = fabsf(a * p.x + b * p.y + c) / sqrt(pow(a, 2) + 1);
if (dist > shortest) continue;
int between = vec_between(p, segs[i], segs[i + 1]);
if (between) {
shortest = dist;
best = i + 1;
} else {
if (i == 0 && HMM_DistV2(p,segs[0]) < slop) {
shortest = dist;
best = i;
} else if (i == arrlen(segs) - 2 && HMM_DistV2(p,arrlast(segs)) < slop) {
shortest = dist;
best = arrlen(segs);
}
}
}
if (best == 1) {
HMM_Vec2 n;
n.x = segs[1].x - segs[0].x;
n.y = segs[1].y - segs[0].y;
n = HMM_NormV2(n);
if (HMM_DotV2(n, HMM_SubV2(p,segs[0])) < 0 ){
if (HMM_DistV2(p, segs[0]) >= slop)
best = -1;
else
best = 0;
}
}
if (best == arrlen(segs) - 1) {
HMM_Vec2 n;
n.x = segs[best - 1].x - segs[best].x;
n.y = segs[best - 1].y - segs[best - 1].y;
n = HMM_NormV2(n);
if (HMM_DotV2(n, HMM_SubV2(p, segs[best])) < 0) {
if (HMM_DistV2(p, segs[best]) >= slop)
best = -1;
else
best = arrlen(segs);
}
}
return best;
}
static JSValue idx_buffer = JS_UNDEFINED;
static int idx_count = 0;
JSValue make_quad_indices_buffer(JSContext *js, int quads)
{
int count = quads*6;
if (!JS_IsUndefined(idx_buffer) && idx_count >= count)
return JS_DupValue(js,idx_buffer);
int verts = quads*4;
uint16_t *indices = malloc(sizeof(*indices)*count);
for (int i = 0, v = 0; v < verts; i +=6, v += 4) {
indices[i] = v;
indices[i+1] = v+2;
indices[i+2] = v+1;
indices[i+3] = v+2;
indices[i+4] = v+3;
indices[i+5] = v+1;
}
if (!JS_IsUndefined(idx_buffer))
JS_FreeValue(js,idx_buffer);
idx_buffer = make_gpu_buffer(js,indices, sizeof(*indices)*count, JS_TYPED_ARRAY_UINT16, 1,0,1);
idx_count = count;
return JS_DupValue(js,idx_buffer);
}
struct quad_buffers {
HMM_Vec2 *pos;
HMM_Vec2 *uv;
HMM_Vec4 *color;
int verts;
};
struct quad_buffers quad_buffers_new(int verts)
{
struct quad_buffers b;
b.verts = verts;
b.pos = malloc(sizeof(HMM_Vec2)*verts);
b.uv = malloc(sizeof(HMM_Vec2)*verts);
b.color = malloc(sizeof(HMM_Vec4)*verts);
return b;
}
JSValue quadbuffers_to_mesh(JSContext *js, struct quad_buffers buffers)
{
JSValue jspos = make_gpu_buffer(js, buffers.pos, sizeof(HMM_Vec2)*buffers.verts, JS_TYPED_ARRAY_FLOAT32, 2, 0, 0);
JSValue jsuv = make_gpu_buffer(js, buffers.uv, sizeof(HMM_Vec2)*buffers.verts, JS_TYPED_ARRAY_FLOAT32, 2,0,0);
JSValue jscolor = make_gpu_buffer(js, buffers.color, sizeof(HMM_Vec4)*buffers.verts, JS_TYPED_ARRAY_FLOAT32, 4,0,0);
size_t quads = buffers.verts/4;
size_t count = buffers.verts/2*3;
JSValue jsidx = make_quad_indices_buffer(js, quads);
JSValue ret = JS_NewObject(js);
JS_SetProperty(js, ret, pos_atom, jspos);
JS_SetProperty(js, ret, uv_atom, jsuv);
JS_SetProperty(js, ret, color_atom, jscolor);
JS_SetProperty(js, ret, indices_atom, jsidx);
JS_SetProperty(js, ret, vertices_atom, number2js(js, buffers.verts));
JS_SetProperty(js,ret,num_indices_atom, number2js(js,count));
return ret;
}
JSValue quads_to_mesh(JSContext *js, text_vert *buffer)
{
size_t verts = arrlen(buffer);
HMM_Vec2 *pos = malloc(arrlen(buffer)*sizeof(HMM_Vec2));
HMM_Vec2 *uv = malloc(arrlen(buffer)*sizeof(HMM_Vec2));
HMM_Vec4 *color = malloc(arrlen(buffer)*sizeof(HMM_Vec4));
for (int i = 0; i < arrlen(buffer); i++) {
pos[i] = buffer[i].pos;
uv[i] = buffer[i].uv;
color[i] = buffer[i].color;
}
JSValue jspos = make_gpu_buffer(js, pos, sizeof(HMM_Vec2)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 2,0,0);
JSValue jsuv = make_gpu_buffer(js, uv, sizeof(HMM_Vec2)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 2,0,0);
JSValue jscolor = make_gpu_buffer(js, color, sizeof(HMM_Vec4)*arrlen(buffer), JS_TYPED_ARRAY_FLOAT32, 4,0,0);
size_t quads = verts/4;
size_t count = verts/2*3;
JSValue jsidx = make_quad_indices_buffer(js, quads);
JSValue ret = JS_NewObject(js);
JS_SetProperty(js, ret, pos_atom, jspos);
JS_SetProperty(js, ret, uv_atom, jsuv);
JS_SetProperty(js, ret, color_atom, jscolor);
JS_SetProperty(js, ret, indices_atom, jsidx);
JS_SetProperty(js, ret, vertices_atom, number2js(js, verts));
JS_SetProperty(js,ret,num_indices_atom, number2js(js,count));
return ret;
}
JSC_CCALL(os_make_text_buffer,
const char *s = JS_ToCString(js, argv[0]);
rect rectpos = js2rect(js,argv[1]);
float size = js2number(js,argv[2]);
font *f = js2font(js,argv[5]);
if (!size) size = f->height;
colorf c = js2color(js,argv[3]);
int wrap = js2number(js,argv[4]);
HMM_Vec2 startpos = {.x = rectpos.x, .y = rectpos.y };
text_vert *buffer = renderText(s, startpos, f, size, c, wrap);
ret = quads_to_mesh(js,buffer);
JS_FreeCString(js, s);
arrfree(buffer);
)
JSC_CCALL(spline_catmull,
HMM_Vec2 *points = js2cpvec2arr(js,argv[0]);
float param = js2number(js,argv[1]);
HMM_Vec2 *samples = catmull_rom_ma_v2(points,param);
if (!samples)
ret = JS_UNDEFINED;
else
ret = vecarr2js(js,samples, arrlen(samples));
arrfree(points);
arrfree(samples);
)
JSC_CCALL(spline_bezier,
HMM_Vec2 *points = js2cpvec2arr(js,argv[0]);
float param = js2number(js,argv[1]);
HMM_Vec2 *samples = catmull_rom_ma_v2(points,param);
if (!samples)
ret = JS_UNDEFINED;
else
ret = vecarr2js(js,samples, arrlen(samples));
arrfree(samples);
arrfree(points);
)
static const JSCFunctionListEntry js_spline_funcs[] = {
MIST_FUNC_DEF(spline, catmull, 2),
MIST_FUNC_DEF(spline, bezier, 2)
};
shader_globals camera_globals(JSContext *js, JSValue camera)
{
shader_globals data = {0};
HMM_Vec2 size;
transform *transform;
double fov = 0;
double aspect = 0;
int ortho;
double near_z = 0;
double far_z = 0;
JS_GETPROP(js, size, camera, size, vec2)
JS_GETPROP(js, transform, camera, transform, transform)
JS_GETPROP(js, fov, camera, fov, number)
JS_GETPROP(js, aspect, camera, aspect, number)
JS_GETPROP(js, ortho, camera,ortho,bool)
JS_GETPROP(js,near_z,camera,near_z,number)
JS_GETPROP(js,far_z,camera,far_z,number)
HMM_Mat4 proj;
HMM_Mat4 view;
if (ortho) {
proj = HMM_Orthographic_RH_NO(
-size.x*0.5, 0.5*size.x,
-size.y*0.5, 0.5*size.y,
-1.0f, 1.0f
);
view = HMM_Translate((HMM_Vec3){ -transform->pos.x, -transform->pos.y, 0.0f });
}
else {
proj = HMM_Perspective_RH_NO(fov, aspect,near_z,far_z);
HMM_Mat4 camera_transform = HMM_Translate(transform->pos);
camera_transform = HMM_MulM4(camera_transform, HMM_QToM4(transform->rotation));
// camera_transform = HMM_MulM4(camera_transform, HMM_Scale(transform->scale)); // don't bother w/ scale
view = HMM_InvGeneralM4(camera_transform);
}
// Update your shader globals
data.world_to_projection = HMM_MulM4(proj, view);
data.projection_to_world = HMM_InvGeneralM4(data.world_to_projection);
data.camera_pos_world = transform->pos;
data.viewport_min_z = near_z;
data.viewport_max_z = far_z;
data.render_size = size;
data.world_to_view = view;
data.view_to_projection = proj;
data.camera_dir_world = HMM_NormV3(HMM_QVRot((HMM_Vec3){0,0,-1},transform->rotation));
data.viewport_size = (HMM_Vec2){0.5,0.5};
data.viewport_offset = (HMM_Vec2){0,0};
data.time = SDL_GetTicksNS() / 1000000000.0f;
return data;
}
static JSValue floats2array(JSContext *js, float *vals, size_t len) {
JSValue arr = JS_NewArray(js);
for (size_t i = 0; i < len; i++) {
JS_SetPropertyUint32(js, arr, i, number2js(js, vals[i]));
}
return arr;
}
JSValue js_math_dot(JSContext *js, JSValue self, int argc, JSValue *argv) {
size_t alen, blen;
float *a = js2floats(js,argv[0], &alen);
float *b = js2floats(js,argv[1], &blen);
float dot = 0;
size_t len = alen < blen? alen : blen;
for (size_t i = 0; i < len; i++)
dot += a[i] * b[i];
free(a);
free(b);
return number2js(js,dot);
};
JSValue js_math_project(JSContext *js, JSValue self, int argc, JSValue *argv) {
size_t alen, blen;
float *a = js2floats(js, argv[0], &alen);
float *b = js2floats(js, argv[1], &blen);
if (!a || !b) {
free(a);
free(b);
return JS_UNDEFINED;
}
// We'll work up to the smaller length
size_t len = (alen < blen) ? alen : blen;
if (len == 0) {
free(a);
free(b);
return JS_UNDEFINED;
}
// Compute dot products: a·b and b·b
float ab = 0, bb = 0;
for (size_t i = 0; i < len; i++) {
ab += a[i] * b[i];
bb += b[i] * b[i];
}
// Build the result array
float *proj = (float*)malloc(sizeof(float) * len);
if (!proj) {
free(a);
free(b);
return JS_EXCEPTION; // or some error
}
float scale = (bb != 0.0f) ? (ab / bb) : 0.0f;
for (size_t i = 0; i < len; i++)
proj[i] = scale * b[i];
JSValue ret = floats2array(js, proj, len);
free(a);
free(b);
free(proj);
return ret;
}
// ---------------------------
// math_midpoint(a, b)
// midpoint = (a + b) / 2
// returns new vector (array)
// dimension = min(alen, blen)
// ---------------------------
JSValue js_math_midpoint(JSContext *js, JSValue self, int argc, JSValue *argv) {
size_t alen, blen;
float *a = js2floats(js, argv[0], &alen);
float *b = js2floats(js, argv[1], &blen);
if (!a || !b) {
free(a);
free(b);
return JS_UNDEFINED;
}
size_t len = (alen < blen) ? alen : blen;
if (len == 0) {
free(a);
free(b);
return JS_UNDEFINED;
}
float *m = (float*)malloc(sizeof(float) * len);
if (!m) {
free(a);
free(b);
return JS_EXCEPTION;
}
for (size_t i = 0; i < len; i++)
m[i] = (a[i] + b[i]) * 0.5f;
JSValue ret = floats2array(js, m, len);
free(a);
free(b);
free(m);
return ret;
}
// ---------------------------
// math_distance(a, b)
// Euclidean distance = sqrt( Σ (b[i]-a[i])^2 )
// dimension = min(alen, blen)
// returns scalar (number)
// ---------------------------
JSValue js_math_distance(JSContext *js, JSValue self, int argc, JSValue *argv) {
size_t alen, blen;
float *a = js2floats(js, argv[0], &alen);
float *b = js2floats(js, argv[1], &blen);
if (!a || !b) {
free(a);
free(b);
return JS_UNDEFINED;
}
size_t len = (alen < blen) ? alen : blen;
if (len == 0) {
free(a);
free(b);
return JS_UNDEFINED;
}
float distSq = 0.0f;
for (size_t i = 0; i < len; i++) {
float diff = b[i] - a[i];
distSq += diff * diff;
}
float dist = sqrtf(distSq);
free(a);
free(b);
return number2js(js, dist);
}
// ---------------------------
// math_angle(a)
// In 2D: angle from x-axis via atan2(y, x).
// For higher dimensions, there's no single "angle" in the usual sense.
// We'll only return angle if len >= 2, using first two coords.
// Or strictly require len==2, your call.
// ---------------------------
JSValue js_math_angle(JSContext *js, JSValue self, int argc, JSValue *argv) {
size_t alen;
float *a = js2floats(js, argv[0], &alen);
if (!a || alen < 2) {
free(a);
return JS_UNDEFINED;
}
// If you want to be strict, require alen == 2
// if (alen != 2) {
// free(a);
// return JS_UNDEFINED;
// }
float angle = atan2f(a[1], a[0]);
free(a);
return number2js(js, angle);
}
/* Given a series of points p, computes a new series with them expanded on either side by d */
/*
HMM_Vec2 *inflatepoints(HMM_Vec2 *p, float d, int n)
{
if (d == 0) {
HMM_Vec2 *ret = NULL;
arraddn(ret,n);
for (int i = 0; i < n; i++)
ret[i] = p[i];
return ret;
}
parsl_position par_v[n];
uint16_t spine_lens[] = {n};
for (int i = 0; i < n; i++) {
par_v[i].x = p[i].x;
par_v[i].y = p[i].y;
};
parsl_context *par_ctx = parsl_create_context((parsl_config){
.thickness = d,
.flags= PARSL_FLAG_ANNOTATIONS,
.u_mode = PAR_U_MODE_DISTANCE
});
parsl_mesh *mesh = parsl_mesh_from_lines(par_ctx, (parsl_spine_list){
.num_vertices = n,
.num_spines = 1,
.vertices = par_v,
.spine_lengths = spine_lens,
.closed = 0,
});
HMM_Vec2 *ret = NULL;
arraddn(ret,mesh->num_vertices);
for (int i = 0; i < mesh->num_vertices; i++) {
ret[i].x = mesh->positions[i].x;
ret[i].y = mesh->positions[i].y;
};
return ret;
}
JSC_CCALL(math_inflate,
HMM_Vec2 *p = js2cpvec2arr(js,argv[0]);
double d = js2number(js,argv[1]);
HMM_Vec2 *infl = inflatepoints(p,d, js_arrlen(js,argv[0]));
ret = vecarr2js(js,infl, arrlen(infl));
arrfree(infl);
arrfree(p);
)
*/
JSC_CCALL(math_rotate,
HMM_Vec2 vec = js2vec2(js,argv[0]);
double angle = js2angle(js, argv[1]);
HMM_Vec2 pivot = JS_IsUndefined(argv[2]) ? v2zero : js2vec2(js,argv[2]);
// vec = vec - pivot
// cblas_saxpy(2, -1.0f, pivot.e, 1, vec.e, 1);
vec = HMM_SubV2(vec,pivot);
// Length of the vector (r)
// float r = sqrtf(cblas_sdot(2, vec.e, 1, vec.e, 1));
float r = HMM_LenV2(vec);
// Update angle
angle += atan2f(vec.y, vec.x);
// Update vec to rotated position
vec.x = r * cosf(angle);
vec.y = r * sinf(angle);
// vec = vec + pivot
// cblas_saxpy(2, 1.0f, pivot.e, 1, vec.e, 1);
vec = HMM_AddV2(vec,pivot);
// Convert back to JS and return
return vec22js(js, vec);
)
JSC_CCALL(math_norm,
int len = js_arrlen(js,argv[0]);
switch(len) {
case 2: return vec22js(js,HMM_NormV2(js2vec2(js,argv[0])));
case 3: return vec32js(js, HMM_NormV3(js2vec3(js,argv[0])));
case 4: return vec42js(js,HMM_NormV4(js2vec4(js,argv[0])));
}
double length = arr_vec_length(js,argv[0]);
JSValue newarr = JS_NewArray(js);
for (int i = 0; i < len; i++)
JS_SetPropertyUint32(js, newarr, i, number2js(js,js_getnum_uint32(js, argv[0],i)/length));
ret = newarr;
)
JSC_CCALL(math_angle_between,
int len = js_arrlen(js,argv[0]);
switch(len) {
case 2: return angle2js(js,HMM_AngleV2(js2vec2(js,argv[0]), js2vec2(js,argv[1])));
case 3: return angle2js(js,HMM_AngleV3(js2vec3(js,argv[0]), js2vec3(js,argv[1])));
case 4: return angle2js(js,HMM_AngleV4(js2vec4(js,argv[0]), js2vec4(js,argv[1])));
}
return JS_ThrowReferenceError(js, "Input array must have a length between 2 and 4.");
)
JSC_CCALL(math_lerp,
double s = js2number(js,argv[0]);
double f = js2number(js,argv[1]);
double t = js2number(js,argv[2]);
ret = number2js(js,(f-s)*t+s);
)
int gcd(int a, int b) {
if (b == 0)
return a;
return gcd(b, a % b);
}
JSC_CCALL(math_gcd, ret = number2js(js,gcd(js2number(js,argv[0]), js2number(js,argv[1]))); )
JSC_CCALL(math_lcm,
double a = js2number(js,argv[0]);
double b = js2number(js,argv[1]);
ret = number2js(js,(a*b)/gcd(a,b));
)
JSC_CCALL(math_clamp,
double x = js2number(js,argv[0]);
double l = js2number(js,argv[1]);
double h = js2number(js,argv[2]);
return number2js(js,x > h ? h : x < l ? l : x);
)
JSC_CCALL(math_angledist,
double a1 = js2number(js,argv[0]);
double a2 = js2number(js,argv[1]);
a1 = fmod(a1,1);
a2 = fmod(a2,1);
double dist = a2-a1;
if (dist == 0) return number2js(js,dist);
if (dist > 0) {
if (dist > 0.5) return number2js(js,dist-1);
return number2js(js,dist);
}
if (dist < -0.5) return number2js(js,dist+1);
return number2js(js,dist);
)
JSC_CCALL(math_length, return number2js(js,arr_vec_length(js,argv[0])); )
double rand_range(double min, double max)
{
return genRand(&mrand) * (max-min)+min;
}
JSC_CCALL(math_jitter,
double n = js2number(js,argv[0]);
double pct = js2number(js,argv[1]);
return number2js(js,n + (rand_range(-pct,pct)*n));
)
JSC_CCALL(math_mean,
double len = js_arrlen(js,argv[0]);
double sum = 0;
for (int i = 0; i < len; i++)
sum += js_getnum_uint32(js, argv[0], i);
return number2js(js,sum/len);
)
JSC_CCALL(math_sum,
double sum = 0.0;
int len = js_arrlen(js,argv[0]);
for (int i = 0; i < len; i++)
sum += js_getnum_uint32(js, argv[0], i);
return number2js(js,sum);
)
JSC_CCALL(math_sigma,
int len = js_arrlen(js,argv[0]);
double sum = 0;
for (int i = 0; i < len; i++)
sum += js_getnum_uint32(js, argv[0], i);
double mean = sum/(double)len;
double sq_diff = 0;
for (int i = 0; i < len; i++) {
double x = js_getnum_uint32(js, argv[0],i);
sq_diff += pow(x-mean, 2);
}
double variance = sq_diff/((double)len);
return number2js(js,sqrt(variance));
)
JSC_CCALL(math_median,
int len = js_arrlen(js,argv[0]);
double arr[len];
double temp;
for (int i = 0; i < len; i++)
arr[i] = js_getnum_uint32(js, argv[0], i);
for (int i = 0; i < len-1; i++) {
for (int j = i+1; j < len; j++) {
if (arr[i] > arr[j]) {
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
if (len % 2 == 0) return number2js(js,(arr[len/2-1] + arr[len/2])/2.0);
return number2js(js,arr[len/2]);
)
JSC_CCALL(math_from_to,
HMM_Vec2 from = js2vec2(js,argv[0]);
HMM_Vec2 to = js2vec2(js,argv[1]);
float space = js2number(js,argv[2]);
float from_offset = js2number(js,argv[3]);
float to_offset = js2number(js,argv[4]);
HMM_Vec2 dir = HMM_NormV2(HMM_SubV2(to, from));
from = HMM_AddV2(from, HMM_MulV2F(dir, from_offset));
to = HMM_SubV2(to,HMM_MulV2F(dir, to_offset));
float length = HMM_DistV2(from, to);
int steps = floor(length/space);
float stepsize = length/(steps+1.0);
ret = JS_NewArray(js);
JS_SetPropertyUint32(js,ret,0,vec22js(js,from));
for (int i = 1; i < steps+1; i++) {
HMM_Vec2 val = HMM_AddV2(from, HMM_MulV2F(dir, i*stepsize));
JS_SetPropertyUint32(js, ret, i, vec22js(js,val));
}
JS_SetPropertyUint32(js, ret, steps+1, vec22js(js,to));
)
JSC_CCALL(math_rand, return JS_NewFloat64(js, genRand(&mrand)))
JSC_CCALL(math_randi, return JS_NewUint32(js, genRandLong(&mrand)))
JSC_CCALL(math_srand,
if (argc < 1)
m_seedRand(&mrand, time(NULL));
else
m_seedRand(&mrand, js2number(js,argv[0]));
)
JSValue js_math_direction(JSContext *js, JSValue self, int argc, JSValue *argv) {
size_t alen, blen;
float *a = js2floats(js, argv[0], &alen);
float *b = js2floats(js, argv[1], &blen);
if (!a || !b) {
free(a);
free(b);
return JS_UNDEFINED;
}
size_t len = (alen < blen) ? alen : blen;
if (len == 0) {
free(a);
free(b);
return JS_UNDEFINED;
}
float *dir = (float*)malloc(sizeof(float) * len);
if (!dir) {
free(a);
free(b);
return JS_EXCEPTION; // or some error
}
// Compute (b - a)
for (size_t i = 0; i < len; i++)
dir[i] = b[i] - a[i];
// Compute magnitude of dir
float mag = 0.0f;
for (size_t i = 0; i < len; i++)
mag += dir[i] * dir[i];
mag = sqrtf(mag);
// Normalize if possible
if (mag > 0.0f) {
for (size_t i = 0; i < len; i++)
dir[i] /= mag;
} else {
// Optional: if a == b, direction is zero
// you might want to do something else here
// (like return all zeros).
}
JSValue ret = floats2array(js, dir, len);
free(a);
free(b);
free(dir);
return ret;
}
JSValue js_math_reflect(JSContext *js, JSValue self, int argc, JSValue *argv) {
size_t vlen, nlen;
float *vec = js2floats(js, argv[0], &vlen);
float *norm = js2floats(js, argv[1], &nlen);
if (!vec || !norm) {
free(vec);
free(norm);
return JS_UNDEFINED;
}
size_t len = (vlen < nlen) ? vlen : nlen;
if (len == 0) {
free(vec);
free(norm);
return JS_UNDEFINED;
}
// 1) Normalize the planeNormal
// p = planeNormal / |planeNormal|
float mag = 0.0f;
for (size_t i = 0; i < len; i++)
mag += norm[i] * norm[i];
mag = sqrtf(mag);
// If the plane normal is zero-length, no reflection is well-defined
if (mag == 0.0f) {
// Return original vec or undefined, your choice.
// Here, let's just return a copy of the original vec.
JSValue retNoReflect = floats2array(js, vec, vlen);
free(vec);
free(norm);
return retNoReflect;
}
float *pnorm = (float*)malloc(sizeof(float) * len);
if (!pnorm) {
free(vec);
free(norm);
return JS_EXCEPTION;
}
for (size_t i = 0; i < len; i++)
pnorm[i] = norm[i] / mag;
// 2) Compute dot = vec•pnorm
float dot = 0.0f;
for (size_t i = 0; i < len; i++)
dot += vec[i] * pnorm[i];
// 3) reflect = vec - 2*dot*pnorm
float *ref = (float*)malloc(sizeof(float) * len);
if (!ref) {
free(vec);
free(norm);
free(pnorm);
return JS_EXCEPTION;
}
for (size_t i = 0; i < len; i++)
ref[i] = vec[i] - 2.0f * dot * pnorm[i];
// Now return 'ref' as an array
JSValue ret = floats2array(js, ref, len);
free(vec);
free(norm);
free(pnorm);
free(ref);
return ret;
}
static const JSCFunctionListEntry js_math_funcs[] = {
MIST_FUNC_DEF(math, dot,2),
MIST_FUNC_DEF(math, project,2),
MIST_FUNC_DEF(math, rotate, 3),
MIST_FUNC_DEF(math, midpoint, 2),
MIST_FUNC_DEF(math, reflect, 0),
MIST_FUNC_DEF(math, distance, 2),
MIST_FUNC_DEF(math, direction, 2),
MIST_FUNC_DEF(math, angle, 1),
MIST_FUNC_DEF(math, norm, 1),
MIST_FUNC_DEF(math, angle_between, 2),
MIST_FUNC_DEF(math, lerp, 3),
MIST_FUNC_DEF(math, gcd, 2),
MIST_FUNC_DEF(math, lcm, 2),
MIST_FUNC_DEF(math, clamp, 3),
MIST_FUNC_DEF(math, angledist, 2),
MIST_FUNC_DEF(math, jitter, 2),
MIST_FUNC_DEF(math, mean, 1),
MIST_FUNC_DEF(math, sum, 1),
MIST_FUNC_DEF(math, sigma, 1),
MIST_FUNC_DEF(math, median, 1),
MIST_FUNC_DEF(math, length, 1),
MIST_FUNC_DEF(math, from_to, 5),
MIST_FUNC_DEF(math, rand, 0),
MIST_FUNC_DEF(math, randi, 0),
MIST_FUNC_DEF(math, srand,0),
};
#define JS_HMM_FN(OP, HMM, SIGN) \
JSC_CCALL(array_##OP, \
int len = js_arrlen(js,self); \
if (!JS_IsArray(js, argv[0])) { \
double n = js2number(js,argv[0]); \
JSValue arr = JS_NewArray(js); \
for (int i = 0; i < len; i++) \
JS_SetPropertyUint32(js, arr, i, number2js(js,js_getnum_uint32(js, self,i) SIGN n)); \
return arr; \
} \
switch(len) { \
case 2: \
return vec22js(js,HMM_##HMM##V2(js2vec2(js,self), js2vec2(js,argv[0]))); \
case 3: \
return vec32js(js, HMM_##HMM##V3(js2vec3(js,self), js2vec3(js,argv[0]))); \
case 4: \
return vec42js(js,HMM_##HMM##V4(js2vec4(js,self), js2vec4(js,argv[0]))); \
} \
\
JSValue arr = JS_NewArray(js); \
for (int i = 0; i < len; i++) { \
double a = js_getnum_uint32(js, self,i); \
double b = js_getnum_uint32(js, argv[0],i); \
JS_SetPropertyUint32(js, arr, i, number2js(js,a SIGN b)); \
} \
return arr; \
) \
JS_HMM_FN(add, Add, +)
JS_HMM_FN(sub, Sub, -)
JS_HMM_FN(div, Div, /)
JS_HMM_FN(scale, Mul, *)
JSC_CCALL(array_lerp,
double t = js2number(js,argv[1]);
int len = js_arrlen(js,self);
JSValue arr = JS_NewArray(js);
for (int i = 0; i < len; i++) {
double from = js_getnum_uint32(js, self, i);
double to = js_getnum_uint32(js, argv[0], i);
JS_SetPropertyUint32(js, arr, i, number2js(js,(to - from) * t + from));
}
return arr;
)
static const JSCFunctionListEntry js_array_funcs[] = {
PROTO_FUNC_DEF(array, add, 1),
PROTO_FUNC_DEF(array, sub, 1),
PROTO_FUNC_DEF(array, div,1),
PROTO_FUNC_DEF(array, scale, 1),
PROTO_FUNC_DEF(array, lerp, 2)
};
JSC_CCALL(number_lerp,
double a = js2number(js,self);
double b = js2number(js,argv[0]);
double t = js2number(js,argv[1]);
return number2js(js, (b-a)*t+a);
)
static const JSCFunctionListEntry js_number_funcs[] = {
PROTO_FUNC_DEF(number, lerp, 2),
};
#define JS_SDL_PROP(JS, VAL, SDLPROP, PROP) \
{ \
JSValue v = JS_GetPropertyStr(JS,VAL,#PROP); \
const char *str = JS_ToCString(JS, v); \
SDL_SetAppMetadataProperty(SDLPROP, str); \
JS_FreeCString(JS,str); \
JS_FreeValue(js,v); \
} \
JSC_CCALL(os_engine_start,
JSValue p = argv[0];
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_NAME_STRING, name)
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_VERSION_STRING, version)
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_IDENTIFIER_STRING, identifier)
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_CREATOR_STRING, creator)
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_COPYRIGHT_STRING, copyright)
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_URL_STRING, url)
JS_SDL_PROP(js, p, SDL_PROP_APP_METADATA_TYPE_STRING, type)
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_CAMERA) < 0)
return JS_ThrowReferenceError(js, "Couldn't initialize SDL: %s\n", SDL_GetError());
char *title;
JS_GETPROP(js,title,argv[0],title,cstring)
SDL_Window *new = SDL_CreateWindow(title, js2number(js, js_getproperty(js,argv[0], width_atom)), js2number(js,js_getproperty(js,argv[0], height_atom)), SDL_WINDOW_RESIZABLE);
JS_FreeCString(js,title);
if (!new) return JS_ThrowReferenceError(js, "Couldn't open window: %s\n", SDL_GetError());
SDL_StartTextInput(new);
global_window = new;
return SDL_Window2js(js,new);
)
typedef struct {
SDL_EventType key;
JSAtom value;
} SDL_EventTypePair;
struct {SDL_EventType key; JSAtom value; } *event_hash = NULL;
void fill_event_atoms(JSContext *js)
{
if (event_hash != NULL) return;
// Application events
hmput(event_hash, SDL_EVENT_QUIT, JS_NewAtom(js, "quit"));
hmput(event_hash, SDL_EVENT_TERMINATING, JS_NewAtom(js, "terminating"));
hmput(event_hash, SDL_EVENT_LOW_MEMORY, JS_NewAtom(js, "low_memory"));
hmput(event_hash, SDL_EVENT_WILL_ENTER_BACKGROUND, JS_NewAtom(js, "will_enter_background"));
hmput(event_hash, SDL_EVENT_DID_ENTER_BACKGROUND, JS_NewAtom(js, "did_enter_background"));
hmput(event_hash, SDL_EVENT_WILL_ENTER_FOREGROUND, JS_NewAtom(js, "will_enter_foreground"));
hmput(event_hash, SDL_EVENT_DID_ENTER_FOREGROUND, JS_NewAtom(js, "did_enter_foreground"));
hmput(event_hash, SDL_EVENT_LOCALE_CHANGED, JS_NewAtom(js, "locale_changed"));
hmput(event_hash, SDL_EVENT_SYSTEM_THEME_CHANGED, JS_NewAtom(js, "system_theme_changed"));
// Display events
hmput(event_hash, SDL_EVENT_DISPLAY_ORIENTATION, JS_NewAtom(js, "display_orientation"));
hmput(event_hash, SDL_EVENT_DISPLAY_ADDED, JS_NewAtom(js, "display_added"));
hmput(event_hash, SDL_EVENT_DISPLAY_REMOVED, JS_NewAtom(js, "display_removed"));
hmput(event_hash, SDL_EVENT_DISPLAY_MOVED, JS_NewAtom(js, "display_moved"));
hmput(event_hash, SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED, JS_NewAtom(js, "display_desktop_mode_changed"));
hmput(event_hash, SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED, JS_NewAtom(js, "display_current_mode_changed"));
hmput(event_hash, SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED, JS_NewAtom(js, "display_content_scale_changed"));
// Window events
hmput(event_hash, SDL_EVENT_WINDOW_SHOWN, JS_NewAtom(js, "window_shown"));
hmput(event_hash, SDL_EVENT_WINDOW_HIDDEN, JS_NewAtom(js, "window_hidden"));
hmput(event_hash, SDL_EVENT_WINDOW_EXPOSED, JS_NewAtom(js, "window_exposed"));
hmput(event_hash, SDL_EVENT_WINDOW_MOVED, JS_NewAtom(js, "window_moved"));
hmput(event_hash, SDL_EVENT_WINDOW_RESIZED, JS_NewAtom(js, "window_resized"));
hmput(event_hash, SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, JS_NewAtom(js, "window_pixel_size_changed"));
hmput(event_hash, SDL_EVENT_WINDOW_METAL_VIEW_RESIZED, JS_NewAtom(js, "window_metal_view_resized"));
hmput(event_hash, SDL_EVENT_WINDOW_MINIMIZED, JS_NewAtom(js, "window_minimized"));
hmput(event_hash, SDL_EVENT_WINDOW_MAXIMIZED, JS_NewAtom(js, "window_maximized"));
hmput(event_hash, SDL_EVENT_WINDOW_RESTORED, JS_NewAtom(js, "window_restored"));
hmput(event_hash, SDL_EVENT_WINDOW_MOUSE_ENTER, JS_NewAtom(js, "window_mouse_enter"));
hmput(event_hash, SDL_EVENT_WINDOW_MOUSE_LEAVE, JS_NewAtom(js, "window_mouse_leave"));
hmput(event_hash, SDL_EVENT_WINDOW_FOCUS_GAINED, JS_NewAtom(js, "window_focus_gained"));
hmput(event_hash, SDL_EVENT_WINDOW_FOCUS_LOST, JS_NewAtom(js, "window_focus_lost"));
hmput(event_hash, SDL_EVENT_WINDOW_CLOSE_REQUESTED, JS_NewAtom(js, "window_close_requested"));
hmput(event_hash, SDL_EVENT_WINDOW_HIT_TEST, JS_NewAtom(js, "window_hit_test"));
hmput(event_hash, SDL_EVENT_WINDOW_ICCPROF_CHANGED, JS_NewAtom(js, "window_iccprof_changed"));
hmput(event_hash, SDL_EVENT_WINDOW_DISPLAY_CHANGED, JS_NewAtom(js, "window_display_changed"));
hmput(event_hash, SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED, JS_NewAtom(js, "window_display_scale_changed"));
hmput(event_hash, SDL_EVENT_WINDOW_SAFE_AREA_CHANGED, JS_NewAtom(js, "window_safe_area_changed"));
hmput(event_hash, SDL_EVENT_WINDOW_OCCLUDED, JS_NewAtom(js, "window_occluded"));
hmput(event_hash, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, JS_NewAtom(js, "window_enter_fullscreen"));
hmput(event_hash, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, JS_NewAtom(js, "window_leave_fullscreen"));
hmput(event_hash, SDL_EVENT_WINDOW_DESTROYED, JS_NewAtom(js, "window_destroyed"));
hmput(event_hash, SDL_EVENT_WINDOW_HDR_STATE_CHANGED, JS_NewAtom(js, "window_hdr_state_changed"));
// Keyboard events
hmput(event_hash, SDL_EVENT_KEY_DOWN, JS_NewAtom(js, "key_down"));
hmput(event_hash, SDL_EVENT_KEY_UP, JS_NewAtom(js, "key_up"));
hmput(event_hash, SDL_EVENT_TEXT_EDITING, JS_NewAtom(js, "text_editing"));
hmput(event_hash, SDL_EVENT_TEXT_INPUT, JS_NewAtom(js, "text_input"));
hmput(event_hash, SDL_EVENT_KEYMAP_CHANGED, JS_NewAtom(js, "keymap_changed"));
hmput(event_hash, SDL_EVENT_KEYBOARD_ADDED, JS_NewAtom(js, "keyboard_added"));
hmput(event_hash, SDL_EVENT_KEYBOARD_REMOVED, JS_NewAtom(js, "keyboard_removed"));
hmput(event_hash, SDL_EVENT_TEXT_EDITING_CANDIDATES, JS_NewAtom(js, "text_editing_candidates"));
// Mouse events
hmput(event_hash, SDL_EVENT_MOUSE_MOTION, JS_NewAtom(js, "mouse_motion"));
hmput(event_hash, SDL_EVENT_MOUSE_BUTTON_DOWN, JS_NewAtom(js, "mouse_button_down"));
hmput(event_hash, SDL_EVENT_MOUSE_BUTTON_UP, JS_NewAtom(js, "mouse_button_up"));
hmput(event_hash, SDL_EVENT_MOUSE_WHEEL, JS_NewAtom(js, "mouse_wheel"));
hmput(event_hash, SDL_EVENT_MOUSE_ADDED, JS_NewAtom(js, "mouse_added"));
hmput(event_hash, SDL_EVENT_MOUSE_REMOVED, JS_NewAtom(js, "mouse_removed"));
// Joystick events
hmput(event_hash, SDL_EVENT_JOYSTICK_AXIS_MOTION, JS_NewAtom(js, "joystick_axis_motion"));
hmput(event_hash, SDL_EVENT_JOYSTICK_BALL_MOTION, JS_NewAtom(js, "joystick_ball_motion"));
hmput(event_hash, SDL_EVENT_JOYSTICK_HAT_MOTION, JS_NewAtom(js, "joystick_hat_motion"));
hmput(event_hash, SDL_EVENT_JOYSTICK_BUTTON_DOWN, JS_NewAtom(js, "joystick_button_down"));
hmput(event_hash, SDL_EVENT_JOYSTICK_BUTTON_UP, JS_NewAtom(js, "joystick_button_up"));
hmput(event_hash, SDL_EVENT_JOYSTICK_ADDED, JS_NewAtom(js, "joystick_added"));
hmput(event_hash, SDL_EVENT_JOYSTICK_REMOVED, JS_NewAtom(js, "joystick_removed"));
hmput(event_hash, SDL_EVENT_JOYSTICK_BATTERY_UPDATED, JS_NewAtom(js, "joystick_battery_updated"));
hmput(event_hash, SDL_EVENT_JOYSTICK_UPDATE_COMPLETE, JS_NewAtom(js, "joystick_update_complete"));
// Gamepad events
hmput(event_hash, SDL_EVENT_GAMEPAD_AXIS_MOTION, JS_NewAtom(js, "gamepad_axis_motion"));
hmput(event_hash, SDL_EVENT_GAMEPAD_BUTTON_DOWN, JS_NewAtom(js, "gamepad_button_down"));
hmput(event_hash, SDL_EVENT_GAMEPAD_BUTTON_UP, JS_NewAtom(js, "gamepad_button_up"));
hmput(event_hash, SDL_EVENT_GAMEPAD_ADDED, JS_NewAtom(js, "gamepad_added"));
hmput(event_hash, SDL_EVENT_GAMEPAD_REMOVED, JS_NewAtom(js, "gamepad_removed"));
hmput(event_hash, SDL_EVENT_GAMEPAD_REMAPPED, JS_NewAtom(js, "gamepad_remapped"));
hmput(event_hash, SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN, JS_NewAtom(js, "gamepad_touchpad_down"));
hmput(event_hash, SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION, JS_NewAtom(js, "gamepad_touchpad_motion"));
hmput(event_hash, SDL_EVENT_GAMEPAD_TOUCHPAD_UP, JS_NewAtom(js, "gamepad_touchpad_up"));
hmput(event_hash, SDL_EVENT_GAMEPAD_SENSOR_UPDATE, JS_NewAtom(js, "gamepad_sensor_update"));
hmput(event_hash, SDL_EVENT_GAMEPAD_UPDATE_COMPLETE, JS_NewAtom(js, "gamepad_update_complete"));
hmput(event_hash, SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED, JS_NewAtom(js, "gamepad_steam_handle_updated"));
// Touch events
hmput(event_hash, SDL_EVENT_FINGER_DOWN, JS_NewAtom(js, "finger_down"));
hmput(event_hash, SDL_EVENT_FINGER_UP, JS_NewAtom(js, "finger_up"));
hmput(event_hash, SDL_EVENT_FINGER_MOTION, JS_NewAtom(js, "finger_motion"));
// Clipboard events
hmput(event_hash, SDL_EVENT_CLIPBOARD_UPDATE, JS_NewAtom(js, "clipboard_update"));
// Drag and drop events
hmput(event_hash, SDL_EVENT_DROP_FILE, JS_NewAtom(js, "drop_file"));
hmput(event_hash, SDL_EVENT_DROP_TEXT, JS_NewAtom(js, "drop_text"));
hmput(event_hash, SDL_EVENT_DROP_BEGIN, JS_NewAtom(js, "drop_begin"));
hmput(event_hash, SDL_EVENT_DROP_COMPLETE, JS_NewAtom(js, "drop_complete"));
hmput(event_hash, SDL_EVENT_DROP_POSITION, JS_NewAtom(js, "drop_position"));
// Audio device events
hmput(event_hash, SDL_EVENT_AUDIO_DEVICE_ADDED, JS_NewAtom(js, "audio_device_added"));
hmput(event_hash, SDL_EVENT_AUDIO_DEVICE_REMOVED, JS_NewAtom(js, "audio_device_removed"));
hmput(event_hash, SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED, JS_NewAtom(js, "audio_device_format_changed"));
// Sensor events
hmput(event_hash, SDL_EVENT_SENSOR_UPDATE, JS_NewAtom(js, "sensor_update"));
// Pen events
hmput(event_hash, SDL_EVENT_PEN_PROXIMITY_IN, JS_NewAtom(js, "pen_proximity_in"));
hmput(event_hash, SDL_EVENT_PEN_PROXIMITY_OUT, JS_NewAtom(js, "pen_proximity_out"));
hmput(event_hash, SDL_EVENT_PEN_DOWN, JS_NewAtom(js, "pen_down"));
hmput(event_hash, SDL_EVENT_PEN_UP, JS_NewAtom(js, "pen_up"));
hmput(event_hash, SDL_EVENT_PEN_BUTTON_DOWN, JS_NewAtom(js, "pen_button_down"));
hmput(event_hash, SDL_EVENT_PEN_BUTTON_UP, JS_NewAtom(js, "pen_button_up"));
hmput(event_hash, SDL_EVENT_PEN_MOTION, JS_NewAtom(js, "pen_motion"));
hmput(event_hash, SDL_EVENT_PEN_AXIS, JS_NewAtom(js, "pen_axis"));
// Camera events
hmput(event_hash, SDL_EVENT_CAMERA_DEVICE_ADDED, JS_NewAtom(js, "camera_device_added"));
hmput(event_hash, SDL_EVENT_CAMERA_DEVICE_REMOVED, JS_NewAtom(js, "camera_device_removed"));
hmput(event_hash, SDL_EVENT_CAMERA_DEVICE_APPROVED, JS_NewAtom(js, "camera_device_approved"));
hmput(event_hash, SDL_EVENT_CAMERA_DEVICE_DENIED, JS_NewAtom(js, "camera_device_denied"));
// Render events
hmput(event_hash, SDL_EVENT_RENDER_TARGETS_RESET, JS_NewAtom(js, "render_targets_reset"));
hmput(event_hash, SDL_EVENT_RENDER_DEVICE_RESET, JS_NewAtom(js, "render_device_reset"));
hmput(event_hash, SDL_EVENT_RENDER_DEVICE_LOST, JS_NewAtom(js, "render_device_lost"));
}
static JSAtom mouse2atom(JSContext *js, int mouse)
{
switch(mouse) {
case SDL_BUTTON_LEFT: return JS_NewAtom(js,"left");
case SDL_BUTTON_MIDDLE: return JS_NewAtom(js,"middle");
case SDL_BUTTON_RIGHT: return JS_NewAtom(js,"right");
case SDL_BUTTON_X1: return JS_NewAtom(js,"x1");
case SDL_BUTTON_X2: return JS_NewAtom(js,"x2");
}
return JS_NewAtom(js,"left");
}
static JSValue js_keymod(JSContext *js)
{
SDL_Keymod modstate = SDL_GetModState();
JSValue ret = JS_NewObject(js);
if (SDL_KMOD_CTRL & modstate)
JS_SetPropertyStr(js,ret,"ctrl", JS_NewBool(js,1));
if (SDL_KMOD_SHIFT & modstate)
JS_SetPropertyStr(js,ret,"shift", JS_NewBool(js,1));
if (SDL_KMOD_ALT & modstate)
JS_SetPropertyStr(js,ret,"alt", JS_NewBool(js,1));
if (SDL_KMOD_GUI & modstate)
JS_SetPropertyStr(js,ret,"super", JS_NewBool(js,1));
if (SDL_KMOD_NUM & modstate)
JS_SetPropertyStr(js,ret,"numlock", JS_NewBool(js,1));
if (SDL_KMOD_CAPS & modstate)
JS_SetPropertyStr(js,ret,"caps", JS_NewBool(js,1));
if (SDL_KMOD_SCROLL & modstate)
JS_SetPropertyStr(js,ret,"scrolllock", JS_NewBool(js,1));
if (SDL_KMOD_MODE & modstate)
JS_SetPropertyStr(js,ret,"mode", JS_NewBool(js,1));
return ret;
}
static JSValue event2js(JSContext *js, SDL_Event event)
{
JSValue e = JS_NewObject(js);
JS_SetPropertyStr(js, e, "type", JS_AtomToString(js,hmget(event_hash, event.type)));
JS_SetPropertyStr(js,e,"timestamp", number2js(js,event.common.timestamp));
switch(event.type) {
case SDL_EVENT_AUDIO_DEVICE_ADDED:
case SDL_EVENT_AUDIO_DEVICE_REMOVED:
JS_SetPropertyStr(js,e,"which", number2js(js,event.adevice.which));
JS_SetPropertyStr(js,e,"recording", JS_NewBool(js,event.adevice.recording));
break;
case SDL_EVENT_DISPLAY_ORIENTATION:
case SDL_EVENT_DISPLAY_ADDED:
case SDL_EVENT_DISPLAY_REMOVED:
case SDL_EVENT_DISPLAY_MOVED:
case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED:
case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED:
case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED:
JS_SetPropertyStr(js,e,"which", number2js(js,event.display.displayID));
JS_SetPropertyStr(js,e,"data1", number2js(js,event.display.data1));
JS_SetPropertyStr(js,e,"data2", number2js(js,event.display.data2));
break;
case SDL_EVENT_MOUSE_MOTION:
JS_SetPropertyStr(js,e,"window", number2js(js,event.motion.windowID));
JS_SetPropertyStr(js,e,"which", number2js(js,event.motion.which));
JS_SetPropertyStr(js, e, "state", number2js(js,event.motion.state));
JS_SetPropertyStr(js,e, "pos", vec22js(js,(HMM_Vec2){event.motion.x,event.motion.y}));
JS_SetPropertyStr(js,e,"d_pos", vec22js(js,(HMM_Vec2){event.motion.xrel, event.motion.yrel}));
break;
case SDL_EVENT_MOUSE_WHEEL:
JS_SetPropertyStr(js,e,"window", number2js(js,event.wheel.windowID));
JS_SetPropertyStr(js,e,"which", number2js(js,event.wheel.which));
JS_SetPropertyStr(js,e,"scroll", vec22js(js,(HMM_Vec2){event.wheel.x,event.wheel.y}));
JS_SetPropertyStr(js,e, "mouse", vec22js(js,(HMM_Vec2){event.wheel.mouse_x,event.wheel.mouse_y}));
break;
case SDL_EVENT_MOUSE_BUTTON_UP:
case SDL_EVENT_MOUSE_BUTTON_DOWN:
JS_SetPropertyStr(js,e,"window", number2js(js,event.button.windowID));
JS_SetPropertyStr(js,e,"which", number2js(js,event.button.which));
JS_SetPropertyStr(js,e,"down", JS_NewBool(js,event.button.down));
JS_SetPropertyStr(js,e,"button", JS_AtomToString(js,mouse2atom(js,event.button.button)));
JS_SetPropertyStr(js,e,"clicks", number2js(js,event.button.clicks));
JS_SetPropertyStr(js,e,"mouse", vec22js(js,(HMM_Vec2){event.button.x,event.button.y}));
break;
case SDL_EVENT_SENSOR_UPDATE:
JS_SetPropertyStr(js,e,"which", number2js(js,event.sensor.which));
JS_SetPropertyStr(js,e, "sensor_timestamp", number2js(js,event.sensor.sensor_timestamp));
break;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
JS_SetPropertyStr(js,e,"window", number2js(js,event.key.windowID));
JS_SetPropertyStr(js,e,"which", number2js(js,event.key.which));
JS_SetPropertyStr(js,e,"down", JS_NewBool(js,event.key.down));
JS_SetPropertyStr(js,e,"repeat", JS_NewBool(js,event.key.repeat));
JS_SetPropertyStr(js,e,"key", number2js(js,event.key.key));
JS_SetPropertyStr(js,e,"scancode", number2js(js,event.key.scancode));
JS_SetPropertyStr(js,e,"mod", js_keymod(js));
break;
case SDL_EVENT_FINGER_MOTION:
case SDL_EVENT_FINGER_DOWN:
case SDL_EVENT_FINGER_UP:
JS_SetPropertyStr(js,e,"touch", number2js(js,event.tfinger.touchID));
JS_SetPropertyStr(js,e,"finger", number2js(js,event.tfinger.fingerID));
JS_SetPropertyStr(js,e,"pos", vec22js(js, (HMM_Vec2){event.tfinger.x, event.tfinger.y}));
JS_SetPropertyStr(js,e,"d_pos", vec22js(js,(HMM_Vec2){event.tfinger.x, event.tfinger.dy}));
JS_SetPropertyStr(js,e,"pressure", number2js(js,event.tfinger.pressure));
JS_SetPropertyStr(js,e,"window", number2js(js,event.key.windowID));
break;
case SDL_EVENT_DROP_BEGIN:
case SDL_EVENT_DROP_FILE:
case SDL_EVENT_DROP_TEXT:
case SDL_EVENT_DROP_COMPLETE:
case SDL_EVENT_DROP_POSITION:
JS_SetPropertyStr(js,e,"window", number2js(js,event.drop.windowID));
JS_SetPropertyStr(js,e,"pos", vec22js(js, (HMM_Vec2){event.drop.x,event.drop.y}));
JS_SetPropertyStr(js,e,"data", JS_NewString(js,event.drop.data));
JS_SetPropertyStr(js,e,"source",JS_NewString(js,event.drop.source));
break;
case SDL_EVENT_TEXT_INPUT:
JS_SetPropertyStr(js,e,"window", number2js(js,event.text.windowID));
JS_SetPropertyStr(js,e,"text", JS_NewString(js,event.text.text));
JS_SetPropertyStr(js,e,"mod", js_keymod(js));
break;
case SDL_EVENT_CAMERA_DEVICE_APPROVED:
case SDL_EVENT_CAMERA_DEVICE_REMOVED:
case SDL_EVENT_CAMERA_DEVICE_ADDED:
case SDL_EVENT_CAMERA_DEVICE_DENIED:
JS_SetPropertyStr(js, e, "which", number2js(js,event.cdevice.which));
break;
case SDL_EVENT_CLIPBOARD_UPDATE:
JS_SetPropertyStr(js, e, "owner", JS_NewBool(js,event.clipboard.owner));
break;
case SDL_EVENT_WINDOW_SHOWN:
case SDL_EVENT_WINDOW_HIDDEN:
case SDL_EVENT_WINDOW_EXPOSED:
case SDL_EVENT_WINDOW_MOVED:
case SDL_EVENT_WINDOW_RESIZED:
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
case SDL_EVENT_WINDOW_METAL_VIEW_RESIZED:
case SDL_EVENT_WINDOW_MINIMIZED:
case SDL_EVENT_WINDOW_MAXIMIZED:
case SDL_EVENT_WINDOW_RESTORED:
case SDL_EVENT_WINDOW_MOUSE_ENTER:
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
case SDL_EVENT_WINDOW_FOCUS_GAINED:
case SDL_EVENT_WINDOW_FOCUS_LOST:
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
case SDL_EVENT_WINDOW_HIT_TEST:
case SDL_EVENT_WINDOW_ICCPROF_CHANGED:
case SDL_EVENT_WINDOW_DISPLAY_CHANGED:
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED:
case SDL_EVENT_WINDOW_OCCLUDED:
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
case SDL_EVENT_WINDOW_DESTROYED:
case SDL_EVENT_WINDOW_HDR_STATE_CHANGED:
/* rest of SDL_EVENT_WINDOW_ here */
JS_SetPropertyStr(js,e,"which", number2js(js, event.window.windowID));
JS_SetPropertyStr(js,e,"data1", number2js(js, event.window.data1));
JS_SetPropertyStr(js,e,"data2", number2js(js, event.window.data2));
break;
case SDL_EVENT_JOYSTICK_ADDED:
case SDL_EVENT_JOYSTICK_REMOVED:
case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE:
JS_SetPropertyStr(js,e,"which", number2js(js,event.jdevice.which));
break;
case SDL_EVENT_JOYSTICK_AXIS_MOTION:
JS_SetPropertyStr(js,e,"which", number2js(js,event.jaxis.which));
JS_SetPropertyStr(js,e,"axis", number2js(js,event.jaxis.axis));
JS_SetPropertyStr(js,e,"value", number2js(js,event.jaxis.value));
break;
case SDL_EVENT_JOYSTICK_BALL_MOTION:
JS_SetPropertyStr(js,e,"which", number2js(js,event.jball.which));
JS_SetPropertyStr(js,e,"ball",number2js(js,event.jball.ball));
JS_SetPropertyStr(js,e, "rel", vec22js(js,(HMM_Vec2){event.jball.xrel,event.jball.yrel}));
break;
case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
case SDL_EVENT_JOYSTICK_BUTTON_UP:
JS_SetPropertyStr(js,e,"which", number2js(js,event.jbutton.which));
JS_SetPropertyStr(js,e,"button", number2js(js,event.jbutton.button));
JS_SetPropertyStr(js,e,"down", JS_NewBool(js,event.jbutton.down));
break;
case SDL_EVENT_GAMEPAD_ADDED:
case SDL_EVENT_GAMEPAD_REMOVED:
case SDL_EVENT_GAMEPAD_REMAPPED:
case SDL_EVENT_GAMEPAD_UPDATE_COMPLETE:
case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED:
JS_SetPropertyStr(js,e,"which", number2js(js,event.gdevice.which));
break;
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
JS_SetPropertyStr(js,e,"which", number2js(js,event.gaxis.which));
JS_SetPropertyStr(js,e,"axis", number2js(js,event.gaxis.axis));
JS_SetPropertyStr(js,e,"value", number2js(js,event.gaxis.value));
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
JS_SetPropertyStr(js,e,"which", number2js(js,event.gbutton.which));
JS_SetPropertyStr(js,e,"button", number2js(js,event.gbutton.button));
JS_SetPropertyStr(js,e,"down", JS_NewBool(js,event.gbutton.down));
break;
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
JS_SetPropertyStr(js,e,"which", number2js(js,event.gtouchpad.which));
JS_SetPropertyStr(js,e,"touchpad", number2js(js,event.gtouchpad.touchpad));
JS_SetPropertyStr(js,e,"finger", number2js(js,event.gtouchpad.finger));
JS_SetPropertyStr(js,e,"pos", vec22js(js,(HMM_Vec2){event.gtouchpad.x,event.gtouchpad.y}));
JS_SetPropertyStr(js,e,"pressure", number2js(js,event.gtouchpad.pressure));
break;
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
JS_SetPropertyStr(js,e,"which", number2js(js,event.gsensor.which));
JS_SetPropertyStr(js,e,"sensor", number2js(js,event.gsensor.sensor));
JS_SetPropertyStr(js,e,"sensor_timestamp", number2js(js,event.gsensor.sensor_timestamp));
break;
case SDL_EVENT_USER:
JS_SetPropertyStr(js,e,"cb", JS_DupValue(js,*(JSValue*)event.user.data1));
JS_FreeValue(js,*(JSValue*)event.user.data1);
free(event.user.data1);
event.user.data1 = NULL;
break;
}
return e;
}
JSC_CCALL(camera_list,
int num;
SDL_CameraID *ids = SDL_GetCameras(&num);
if (num == 0) return JS_UNDEFINED;
JSValue jsids = JS_NewArray(js);
for (int i = 0; i < num; i++)
JS_SetPropertyUint32(js,jsids, i, number2js(js,ids[i]));
return jsids;
)
JSC_CCALL(camera_open,
int id = js2number(js,argv[0]);
SDL_Camera *cam = SDL_OpenCamera(id, NULL);
if (!cam) ret = JS_ThrowReferenceError(js, "Could not open camera %d: %s\n", id, SDL_GetError());
else
ret = SDL_Camera2js(js,cam);
)
JSC_CCALL(camera_name,
const char *name = SDL_GetCameraName(js2number(js,argv[0]));
if (!name) return JS_ThrowReferenceError(js, "Could not get camera name from id %d.", (int)js2number(js,argv[0]));
return JS_NewString(js, name);
)
JSC_CCALL(camera_position,
SDL_CameraPosition pos = SDL_GetCameraPosition(js2number(js,argv[0]));
switch(pos) {
case SDL_CAMERA_POSITION_UNKNOWN: return JS_NewString(js,"unknown");
case SDL_CAMERA_POSITION_FRONT_FACING: return JS_NewString(js,"front");
case SDL_CAMERA_POSITION_BACK_FACING: return JS_NewString(js,"back");
}
)
static const JSCFunctionListEntry js_camera_funcs[] = {
MIST_FUNC_DEF(camera, list, 0),
MIST_FUNC_DEF(camera, open, 1),
MIST_FUNC_DEF(camera, name, 1),
MIST_FUNC_DEF(camera, position, 1),
};
JSC_SCALL(SDL_Window_make_renderer,
SDL_Window *win = js2SDL_Window(js,self);
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_PRESENT_VSYNC_NUMBER, 0);
SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, win);
SDL_SetStringProperty(props, SDL_PROP_RENDERER_CREATE_NAME_STRING, str);
SDL_Renderer *r = SDL_CreateRendererWithProperties(props);
SDL_DestroyProperties(props);
if (!r) return JS_ThrowReferenceError(js, "Error creating renderer: %s",SDL_GetError());
SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND);
return SDL_Renderer2js(js,r);
)
SDL_GPUShaderFormat js2SDL_GPUShaderFormat(JSContext *js, JSValue v)
{
const char *format_str = JS_ToCString(js, v);
SDL_GPUShaderFormat format = SDL_GPU_SHADERFORMAT_SPIRV; // default to SPIR-V
if (format_str) {
if (!strcmp(format_str, "private")) format = SDL_GPU_SHADERFORMAT_PRIVATE;
else if (!strcmp(format_str, "spv")) format = SDL_GPU_SHADERFORMAT_SPIRV;
else if (!strcmp(format_str, "dxbc")) format = SDL_GPU_SHADERFORMAT_DXBC;
else if (!strcmp(format_str, "dxil")) format = SDL_GPU_SHADERFORMAT_DXIL;
else if (!strcmp(format_str, "msl")) format = SDL_GPU_SHADERFORMAT_MSL;
else if (!strcmp(format_str, "metallib")) format = SDL_GPU_SHADERFORMAT_METALLIB;
JS_FreeCString(js, format_str);
}
return format;
}
JSC_CCALL(SDL_Window_make_gpu,
const char *name = JS_ToCString(js,argv[1]);
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING, name);
JS_FreeCString(js,name);
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, JS_ToBool(js,argv[0]));
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, 1);
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN, 1);
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN, 1);
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN, 1);
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN, 1);
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN, 1);
SDL_GPUDevice *gpu = SDL_CreateGPUDeviceWithProperties(props);
SDL_DestroyProperties(props);
if (!gpu) return JS_ThrowReferenceError(js, "Could not create GPU device! %s", SDL_GetError());
global_gpu = gpu;
return SDL_GPUDevice2js(js,gpu);
)
JSC_CCALL(SDL_Window_fullscreen,
SDL_SetWindowFullscreen(js2SDL_Window(js,self), SDL_WINDOW_FULLSCREEN)
)
JSValue js_SDL_Window_keyboard_shown(JSContext *js, JSValue self) {
SDL_Window *window = js2SDL_Window(js,self);
return JS_NewBool(js,SDL_ScreenKeyboardShown(window));
}
JSValue js_window_theme(JSContext *js, JSValue self)
{
return JS_UNDEFINED;
}
JSValue js_window_safe_area(JSContext *js, JSValue self)
{
SDL_Window *w = js2SDL_Window(js,self);
rect r;
SDL_GetWindowSafeArea(w, &r);
return rect2js(js,r);
}
JSValue js_window_bordered(JSContext *js, JSValue self, int argc, JSValue *argv)
{
SDL_Window *w = js2SDL_Window(js,self);
SDL_SetWindowBordered(w, JS_ToBool(js,argv[0]));
return JS_UNDEFINED;
}
JSValue js_window_get_title(JSContext *js, JSValue self)
{
SDL_Window *w = js2SDL_Window(js,self);
const char *title = SDL_GetWindowTitle(w);
return JS_NewString(js,title);
}
JSValue js_window_set_title(JSContext *js, JSValue self, JSValue val)
{
SDL_Window *w = js2SDL_Window(js,self);
const char *title = JS_ToCString(js,val);
SDL_SetWindowTitle(w,title);
JS_FreeCString(js,title);
return JS_UNDEFINED;
}
JSValue js_window_get_size(JSContext *js, JSValue self)
{
SDL_Window *win = js2SDL_Window(js,self);
int w, h;
SDL_GetWindowSize(win, &w, &h);
return vec22js(js, (HMM_Vec2){w,h});
}
JSValue js_window_set_size(JSContext *js, JSValue self, JSValue val)
{
SDL_Window *w = js2SDL_Window(js,self);
HMM_Vec2 size = js2vec2(js,val);
SDL_SetWindowSize(w,size.x,size.y);
return JS_UNDEFINED;
}
JSValue js_window_set_icon(JSContext *js, JSValue self, int argc, JSValue *argv)
{
SDL_Window *w = js2SDL_Window(js,self);
SDL_Surface *s = js2SDL_Surface(js,argv[0]);
if (!SDL_SetWindowIcon(w,s))
return JS_ThrowReferenceError(js, "could not set window icon: %s", SDL_GetError());
return JS_UNDEFINED;
}
JSValue js_window_mouse_grab(JSContext *js, JSValue self, int argc, JSValue *argv)
{
SDL_Window *w = js2SDL_Window(js,self);
SDL_SetWindowMouseGrab(w, JS_ToBool(js,argv[0]));
return JS_UNDEFINED;
}
static const JSCFunctionListEntry js_SDL_Window_funcs[] = {
MIST_FUNC_DEF(SDL_Window, fullscreen, 0),
MIST_FUNC_DEF(SDL_Window, make_renderer, 1),
MIST_FUNC_DEF(SDL_Window, make_gpu, 2),
MIST_FUNC_DEF(SDL_Window, keyboard_shown, 0),
MIST_FUNC_DEF(window, theme, 0),
MIST_FUNC_DEF(window, safe_area, 0),
MIST_FUNC_DEF(window, bordered, 1),
MIST_FUNC_DEF(window, set_icon, 1),
CGETSET_ADD(window, title),
CGETSET_ADD(window, size),
MIST_FUNC_DEF(window, mouse_grab, 1),
};
JSC_CCALL(SDL_Renderer_clear,
SDL_Renderer *renderer = js2SDL_Renderer(js,self);
SDL_RenderClear(renderer);
)
JSC_CCALL(SDL_Renderer_present,
// SDL_Thread *thread;
SDL_Renderer *ren = js2SDL_Renderer(js,self);
// thread = SDL_CreateThread(present_thread, "present", ren);
SDL_RenderPresent(ren);
// return SDL_Thread2js(js,thread);
)
JSC_CCALL(SDL_Renderer_draw_color,
SDL_Renderer *renderer = js2SDL_Renderer(js,self);
colorf color = js2color(js,argv[0]);
SDL_SetRenderDrawColorFloat(renderer, color.r,color.g,color.b,color.a);
)
JSC_CCALL(SDL_Renderer_rect,
SDL_Renderer *r = js2SDL_Renderer(js,self);
if (!JS_IsUndefined(argv[1])) {
colorf color = js2color(js,argv[1]);
SDL_SetRenderDrawColorFloat(r, color.r, color.g, color.b, color.a);
}
if (JS_IsArray(js,argv[0])) {
int len = js_arrlen(js,argv[0]);
rect rects[len];
for (int i = 0; i < len; i++) {
JSValue val = JS_GetPropertyUint32(js,argv[0],i);
rects[i] = transform_rect(r,js2rect(js,val), &cam_mat);
JS_FreeValue(js,val);
}
SDL_RenderRects(r,rects,len);
return JS_UNDEFINED;
}
rect rect = js2rect(js,argv[0]);
rect = transform_rect(r,rect, &cam_mat);
SDL_RenderRect(r, &rect);
)
SDL_GPUColorTargetInfo js2SDL_GPUColorTargetInfo(JSContext *js, JSValue v)
{
SDL_GPUColorTargetInfo info = {0};
JS_GETPROP(js, info.texture, v, texture, SDL_GPUTexture)
JS_GETPROP(js,info.mip_level,v,mip_level,number)
JS_GETPROP(js,info.layer_or_depth_plane, v, layer, number)
JS_GETPROP(js,info.load_op,v,load,SDL_GPULoadOp)
JS_GETPROP(js,info.store_op,v,store,SDL_GPUStoreOp)
JS_GETPROP(js,info.resolve_mip_level,v,resolve_mip_level,number)
JS_GETPROP(js,info.resolve_layer,v,resolve_layer,number)
JS_GETPROP(js,info.clear_color,v,clear,SDL_FColor)
return info;
}
JSC_CCALL(renderer_load_texture,
SDL_Renderer *r = js2SDL_Renderer(js,self);
SDL_Surface *surf = js2SDL_Surface(js,argv[0]);
if (!surf) return JS_ThrowReferenceError(js, "Surface was not a surface.");
SDL_Texture *tex = SDL_CreateTextureFromSurface(r,surf);
if (!tex) return JS_ThrowReferenceError(js, "Could not create texture from surface: %s", SDL_GetError());
ret = SDL_Texture2js(js,tex);
JS_SetProperty(js,ret,width_atom, number2js(js,tex->w));
JS_SetProperty(js,ret,height_atom, number2js(js,tex->h));
)
JSC_CCALL(SDL_Renderer_fillrect,
SDL_Renderer *r = js2SDL_Renderer(js,self);
if (!JS_IsUndefined(argv[1])) {
colorf color = js2color(js,argv[1]);
SDL_SetRenderDrawColorFloat(r, color.r, color.g, color.b, color.a);
}
if (JS_IsArray(js,argv[0])) {
int len = js_arrlen(js,argv[0]);
rect rects[len];
for (int i = 0; i < len; i++) {
JSValue val = JS_GetPropertyUint32(js,argv[0],i);
rects[i] = js2rect(js,val);
JS_FreeValue(js,val);
}
if (!SDL_RenderFillRects(r,rects,len))
return JS_ThrowReferenceError(js, "Could not render rectangle: %s", SDL_GetError());
}
rect rect = transform_rect(r,js2rect(js,argv[0]),&cam_mat);
if (!SDL_RenderFillRect(r, &rect))
return JS_ThrowReferenceError(js, "Could not render rectangle: %s", SDL_GetError());
)
JSC_CCALL(renderer_texture,
SDL_Renderer *renderer = js2SDL_Renderer(js,self);
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
rect dst = transform_rect(renderer,js2rect(js,argv[1]), &cam_mat);
if (!JS_IsUndefined(argv[3])) {
colorf color = js2color(js,argv[3]);
SDL_SetTextureColorModFloat(tex, color.r, color.g, color.b);
SDL_SetTextureAlphaModFloat(tex,color.a);
}
if (JS_IsUndefined(argv[2]))
SDL_RenderTexture(renderer,tex,NULL,&dst);
else {
rect src = js2rect(js,argv[2]);
SDL_RenderTextureRotated(renderer, tex, &src, &dst, 0, NULL, SDL_FLIP_NONE);
}
)
JSC_CCALL(renderer_tile,
SDL_Renderer *renderer = js2SDL_Renderer(js,self);
if (!renderer) return JS_ThrowTypeError(js,"self was not a renderer");
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
if (!tex) return JS_ThrowTypeError(js,"first argument was not a texture");
rect dst = js2rect(js,argv[1]);
if (!dst.w) dst.w = tex->w;
if (!dst.h) dst.h = tex->h;
float scale = js2number(js,argv[3]);
if (!scale) scale = 1;
if (JS_IsUndefined(argv[2]))
SDL_RenderTextureTiled(renderer,tex,NULL,scale, &dst);
else {
rect src = js2rect(js,argv[2]);
SDL_RenderTextureTiled(renderer,tex,&src,scale, &dst);
}
)
JSC_CCALL(renderer_slice9,
SDL_Renderer *renderer = js2SDL_Renderer(js,self);
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
lrtb bounds = js2lrtb(js,argv[2]);
rect src, dst;
src = transform_rect(renderer,js2rect(js,argv[3]),&cam_mat);
dst = transform_rect(renderer,js2rect(js,argv[1]), &cam_mat);
SDL_RenderTexture9Grid(renderer, tex,
JS_IsUndefined(argv[3]) ? NULL : &src,
bounds.l, bounds.r, bounds.t, bounds.b, 0.0,
JS_IsUndefined(argv[1]) ? NULL : &dst);
)
JSC_CCALL(renderer_get_image,
SDL_Renderer *r = js2SDL_Renderer(js,self);
SDL_Surface *surf = NULL;
if (!JS_IsUndefined(argv[0])) {
rect rect = js2rect(js,argv[0]);
surf = SDL_RenderReadPixels(r,&rect);
} else
surf = SDL_RenderReadPixels(r,NULL);
if (!surf) return JS_ThrowReferenceError(js, "could not make surface from renderer");
return SDL_Surface2js(js,surf);
)
JSC_SCALL(renderer_fasttext,
SDL_Renderer *r = js2SDL_Renderer(js,self);
if (!JS_IsUndefined(argv[2])) {
colorf color = js2color(js,argv[2]);
SDL_SetRenderDrawColorFloat(r, color.r, color.g, color.b, color.a);
}
HMM_Vec2 pos = js2vec2(js,argv[1]);
pos.y += 8;
HMM_Vec2 tpos = HMM_MulM3V3(cam_mat, (HMM_Vec3){pos.x,pos.y,1}).xy;
SDL_RenderDebugText(r, tpos.x, tpos.y, str);
)
JSC_CCALL(renderer_line,
SDL_Renderer *r = js2SDL_Renderer(js,self);
if (!JS_IsUndefined(argv[1])) {
colorf color = js2color(js,argv[1]);
SDL_SetRenderDrawColorFloat(r, color.r, color.g, color.b, color.a);
}
if (JS_IsArray(js,argv[0])) {
int len = js_arrlen(js,argv[0]);
HMM_Vec2 points[len];
assert(sizeof(HMM_Vec2) == sizeof(SDL_FPoint));
for (int i = 0; i < len; i++) {
JSValue val = JS_GetPropertyUint32(js,argv[0],i);
points[i] = js2vec2(js,val);
JS_FreeValue(js,val);
}
SDL_RenderLines(r,points,len);
}
)
JSC_CCALL(renderer_point,
SDL_Renderer *r = js2SDL_Renderer(js,self);
if (!JS_IsUndefined(argv[1])) {
colorf color = js2color(js,argv[1]);
SDL_SetRenderDrawColorFloat(r, color.r, color.g, color.b, color.a);
}
if (JS_IsArray(js,argv[0])) {
int len = js_arrlen(js,argv[0]);
HMM_Vec2 points[len];
assert(sizeof(HMM_Vec2) ==sizeof(SDL_FPoint));
for (int i = 0; i < len; i++) {
JSValue val = JS_GetPropertyUint32(js,argv[0],i);
points[i] = js2vec2(js,val);
JS_FreeValue(js,val);
}
SDL_RenderPoints(r, points, len);
return JS_UNDEFINED;
}
HMM_Vec2 point = transform_point(r, js2vec2(js,argv[0]), &cam_mat);
SDL_RenderPoint(r,point.x,point.y);
)
// Function to translate a list of 2D points
void Translate2DPoints(HMM_Vec2 *points, int count, HMM_Vec3 position, HMM_Quat rotation, HMM_Vec3 scale) {
// Precompute the 2D rotation matrix from the quaternion
float xx = rotation.x * rotation.x;
float yy = rotation.y * rotation.y;
float zz = rotation.z * rotation.z;
float xy = rotation.x * rotation.y;
float zw = rotation.z * rotation.w;
// Extract 2D affine rotation and scaling
float m00 = (1.0f - 2.0f * (yy + zz)) * scale.x; // Row 1, Column 1
float m01 = (2.0f * (xy + zw)) * scale.y; // Row 1, Column 2
float m10 = (2.0f * (xy - zw)) * scale.x; // Row 2, Column 1
float m11 = (1.0f - 2.0f * (xx + zz)) * scale.y; // Row 2, Column 2
// Translation components (ignore the z position)
float tx = position.x;
float ty = position.y;
// Transform each point
for (int i = 0; i < count; ++i) {
HMM_Vec2 p = points[i];
points[i].x = m00 * p.x + m01 * p.y + tx;
points[i].y = m10 * p.x + m11 * p.y + ty;
}
}
// Should take a single struct with pos, color, uv, and indices arrays
JSC_CCALL(renderer_geometry,
SDL_Renderer *r = js2SDL_Renderer(js,self);
JSValue pos = JS_GetPropertyStr(js,argv[1], "pos");
JSValue color = JS_GetPropertyStr(js,argv[1], "color");
JSValue uv = JS_GetPropertyStr(js,argv[1], "uv");
JSValue indices = JS_GetPropertyStr(js,argv[1], "indices");
int vertices = js_getnum_str(js, argv[1], "vertices");
int count = js_getnum_str(js, argv[1], "count");
size_t pos_stride, indices_stride, uv_stride, color_stride;
void *posdata = get_gpu_buffer(js,pos, &pos_stride, NULL);
void *idxdata = get_gpu_buffer(js,indices, &indices_stride, NULL);
void *uvdata = get_gpu_buffer(js,uv, &uv_stride, NULL);
void *colordata = get_gpu_buffer(js,color,&color_stride, NULL);
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
HMM_Vec2 *trans_pos = malloc(vertices*sizeof(HMM_Vec2));
memcpy(trans_pos,posdata, sizeof(HMM_Vec2)*vertices);
for (int i = 0; i < vertices; i++)
trans_pos[i] = HMM_MulM3V3(cam_mat, (HMM_Vec3){trans_pos[i].x, trans_pos[i].y, 1}).xy;
if (!SDL_RenderGeometryRaw(r, tex, trans_pos, pos_stride,colordata,color_stride,uvdata, uv_stride, vertices, idxdata, count, indices_stride))
ret = JS_ThrowReferenceError(js, "Error rendering geometry: %s",SDL_GetError());
free(trans_pos);
JS_FreeValue(js,pos);
JS_FreeValue(js,color);
JS_FreeValue(js,uv);
JS_FreeValue(js,indices);
)
JSC_CCALL(renderer_logical_size,
SDL_Renderer *r = js2SDL_Renderer(js,self);
HMM_Vec2 v = js2vec2(js,argv[0]);
SDL_SetRenderLogicalPresentation(r,v.x,v.y,SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
)
JSC_CCALL(renderer_viewport,
SDL_Renderer *r = js2SDL_Renderer(js,self);
if (JS_IsUndefined(argv[0]))
SDL_SetRenderViewport(r,NULL);
else {
rect view = js2rect(js,argv[0]);
SDL_SetRenderViewport(r,&view);
}
)
JSC_CCALL(renderer_get_viewport,
SDL_Renderer *r = js2SDL_Renderer(js,self);
SDL_Rect vp;
SDL_GetRenderViewport(r, &vp);
rect re;
re.x = vp.x;
re.y = vp.y;
re.h = vp.h;
re.w = vp.w;
return rect2js(js,re);
)
JSC_CCALL(renderer_clip,
SDL_Renderer *r = js2SDL_Renderer(js,self);
if (JS_IsUndefined(argv[0]))
SDL_SetRenderClipRect(r,NULL);
else {
rect view = js2rect(js,argv[0]);
SDL_SetRenderClipRect(r,&view);
}
)
JSC_CCALL(renderer_scale,
SDL_Renderer *r = js2SDL_Renderer(js,self);
HMM_Vec2 v = js2vec2(js,argv[0]);
SDL_SetRenderScale(r, v.x, v.y);
)
JSC_CCALL(renderer_vsync,
SDL_Renderer *r = js2SDL_Renderer(js,self);
SDL_SetRenderVSync(r,js2number(js,argv[0]));
)
// This returns the coordinates inside the
JSC_CCALL(renderer_coords,
SDL_Renderer *r = js2SDL_Renderer(js,self);
HMM_Vec2 pos, coord;
pos = js2vec2(js,argv[0]);
SDL_RenderCoordinatesFromWindow(r,pos.x,pos.y, &coord.x, &coord.y);
return vec22js(js,coord);
)
JSC_CCALL(renderer_camera,
int centered = JS_ToBool(js,argv[1]);
SDL_Renderer *ren = js2SDL_Renderer(js,self);
SDL_Rect vp;
SDL_GetRenderViewport(ren, &vp);
HMM_Mat3 proj;
proj.Columns[0] = (HMM_Vec3){1,0,0};
proj.Columns[1] = (HMM_Vec3){0,-1,0};
if (centered)
proj.Columns[2] = (HMM_Vec3){vp.w/2.0,vp.h/2.0,1};
else
proj.Columns[2] = (HMM_Vec3){0,vp.h,1};
transform *tra = js2transform(js,argv[0]);
HMM_Mat3 view;
view.Columns[0] = (HMM_Vec3){1,0,0};
view.Columns[1] = (HMM_Vec3){0,1,0};
view.Columns[2] = (HMM_Vec3){-tra->pos.x, -tra->pos.y,1};
cam_mat = HMM_MulM3(proj,view);
)
JSC_CCALL(renderer_screen2world,
HMM_Mat3 inv = HMM_InvGeneralM3(cam_mat);
HMM_Vec3 pos = js2vec3(js,argv[0]);
return vec22js(js, HMM_MulM3V3(inv, pos).xy);
)
JSC_CCALL(renderer_target,
SDL_Renderer *r = js2SDL_Renderer(js,self);
if (JS_IsUndefined(argv[0]))
SDL_SetRenderTarget(r, NULL);
else {
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
SDL_SetRenderTarget(r,tex);
}
)
// Given an array of sprites, make the necessary geometry
// A sprite is expected to have:
// transform: a transform encoding position and rotation. its scale is in pixels - so a scale of 1 means the image will draw only on a single pixel.
// image: a standard prosperon image of a surface, rect, and texture
// color: the color this sprite should be hued by
JSC_CCALL(renderer_make_sprite_mesh,
JSValue sprites = argv[0];
size_t quads = js_arrlen(js,argv[0]);
size_t verts = quads*4;
size_t count = quads*6;
HMM_Vec2 *posdata = malloc(sizeof(*posdata)*verts);
HMM_Vec2 *uvdata = malloc(sizeof(*uvdata)*verts);
HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts);
for (int i = 0; i < quads; i++) {
JSValue sub = JS_GetPropertyUint32(js,sprites,i);
JSValue jstransform = JS_GetProperty(js,sub,transform_atom);
transform *tr = js2transform(js,jstransform);
JSValue jssrc = JS_GetProperty(js,sub,src_atom);
JSValue jscolor = JS_GetProperty(js,sub,color_atom);
HMM_Vec4 color;
rect src;
if (JS_IsUndefined(jssrc))
src = (rect){.x = 0, .y = 0, .w = 1, .h = 1};
else
src = js2rect(js,jssrc);
if (JS_IsUndefined(jscolor))
color = (HMM_Vec4){1,1,1,1};
else
color = js2vec4(js,jscolor);
// Calculate the base index for the current quad
size_t base = i * 4;
// HMM_Mat3 trmat = transform2mat3_global(tr);
HMM_Vec3 base_quad[4] = {
{0.0,0.0,1.0},
{1.0,0.0,1.0},
{0.0,1.0,1.0},
{1.0,1.0,1.0}
};
// for (int j = 0; j < 4; j++)
// posdata[base+j] = HMM_MulM3V3(trmat, base_quad[j]).xy;
// Define the UV coordinates based on the source rectangle
uvdata[base + 0] = (HMM_Vec2){ src.x, src.y + src.h };
uvdata[base + 1] = (HMM_Vec2){ src.x + src.w, src.y + src.h };
uvdata[base + 2] = (HMM_Vec2){ src.x, src.y };
uvdata[base + 3] = (HMM_Vec2){ src.x + src.w, src.y };
colordata[base] = color;
colordata[base+1] = color;
colordata[base+2] = color;
colordata[base+3] = color;
JS_FreeValue(js,jstransform);
JS_FreeValue(js,sub);
JS_FreeValue(js,jscolor);
JS_FreeValue(js,jssrc);
}
ret = JS_NewObject(js);
JS_SetProperty(js, ret, pos_atom, make_gpu_buffer(js, posdata, sizeof(*posdata) * verts, JS_TYPED_ARRAY_FLOAT32, 2, 0,0));
JS_SetProperty(js, ret, uv_atom, make_gpu_buffer(js, uvdata, sizeof(*uvdata) * verts, JS_TYPED_ARRAY_FLOAT32, 2, 0,0));
JS_SetProperty(js, ret, color_atom, make_gpu_buffer(js, colordata, sizeof(*colordata) * verts, JS_TYPED_ARRAY_FLOAT32, 4, 0,0));
JS_SetProperty(js, ret, indices_atom, make_quad_indices_buffer(js, quads));
JS_SetProperty(js, ret, vertices_atom, number2js(js, verts));
JS_SetProperty(js, ret, count_atom, number2js(js, count));
)
static const JSCFunctionListEntry js_SDL_Renderer_funcs[] = {
MIST_FUNC_DEF(SDL_Renderer, draw_color, 1),
MIST_FUNC_DEF(SDL_Renderer, present, 0),
MIST_FUNC_DEF(SDL_Renderer, clear, 0),
MIST_FUNC_DEF(SDL_Renderer, rect, 2),
MIST_FUNC_DEF(SDL_Renderer, fillrect, 2),
MIST_FUNC_DEF(renderer, line, 2),
MIST_FUNC_DEF(renderer, point, 2),
MIST_FUNC_DEF(renderer, load_texture, 1),
MIST_FUNC_DEF(renderer, texture, 4),
MIST_FUNC_DEF(renderer, slice9, 4),
MIST_FUNC_DEF(renderer, tile, 4),
MIST_FUNC_DEF(renderer, get_image, 1),
MIST_FUNC_DEF(renderer, fasttext, 2),
MIST_FUNC_DEF(renderer, geometry, 2),
MIST_FUNC_DEF(renderer, scale, 1),
MIST_FUNC_DEF(renderer,logical_size,1),
MIST_FUNC_DEF(renderer,viewport,1),
MIST_FUNC_DEF(renderer,clip,1),
MIST_FUNC_DEF(renderer,vsync,1),
MIST_FUNC_DEF(renderer, coords, 1),
MIST_FUNC_DEF(renderer, camera, 2),
MIST_FUNC_DEF(renderer, get_viewport,0),
MIST_FUNC_DEF(renderer, screen2world, 1),
MIST_FUNC_DEF(renderer, target, 1),
MIST_FUNC_DEF(renderer, make_sprite_mesh, 2),
};
// GPU API
JSC_CCALL(gpu_claim_window,
SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
SDL_Window *win = js2SDL_Window(js, argv[0]);
SDL_ClaimWindowForGPUDevice(gpu,win);
)
JSC_CCALL(gpu_set_swapchain,
if (!SDL_SetGPUSwapchainParameters(global_gpu,global_window, js2SDL_GPUSwapchainComposition(js,argv[0]), js2SDL_GPUPresentMode(js,argv[1])))
return JS_ThrowReferenceError(js, "Could not set: %s\n", SDL_GetError());
)
static JSValue *js_swapchains;
JSC_CCALL(cmd_acquire_swapchain,
SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
Uint32 w,h;
SDL_GPUTexture *texture;
SDL_AcquireGPUSwapchainTexture(cmds,global_window, &texture, &w, &h);
if (!texture) return JS_UNDEFINED;
JSValue swap = JS_UNDEFINED;
for (int i = 0; i < arrlen(js_swapchains); i++) {
if (js2SDL_GPUTexture(js,js_swapchains[i]) == texture) {
swap = js_swapchains[i];
break;
}
}
if (JS_IsUndefined(swap)) {
swap = SDL_GPUTexture2js(js,texture);
arrput(js_swapchains,swap);
}
JS_SetPropertyStr(js,swap,"width", number2js(js,w));
JS_SetPropertyStr(js,swap,"height", number2js(js,h));
return JS_DupValue(js,swap);
)
JSC_CCALL(cmd_swapchain_pass,
SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
SDL_GPUColorTargetInfo info = {0};
Uint32 w, h;
SDL_AcquireGPUSwapchainTexture(cmds, global_window, &info.texture, &w,&h);
info.load_op = SDL_GPU_LOADOP_CLEAR;
info.store_op = SDL_GPU_STOREOP_STORE;
colorf c = js2color(js,argv[0]);
info.clear_color = (SDL_FColor){c.r,c.g,c.b,c.a};
SDL_GPURenderPass *pass = SDL_BeginGPURenderPass(
cmds,
&info,
1,
NULL
);
if (!pass) return JS_ThrowReferenceError(js, "Unable to create swapchain pass: %s", SDL_GetError());
JSValue jspass = SDL_GPURenderPass2js(js,pass);
JS_SetPropertyStr(js,jspass,"size", vec22js(js,(HMM_Vec2){w,h}));
return jspass;
)
JSC_CCALL(gpu_load_texture,
SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, self);
SDL_Surface *surf = js2SDL_Surface(js, argv[0]);
if (!surf) return JS_ThrowReferenceError(js, "Surface was not a surface.");
int compression_level = js2number(js,argv[1]);
if (compression_level < 0) compression_level = 0;
if (compression_level > 2) compression_level = 2;
int dofree = 0;
SDL_PixelFormat sfmt = surf->format;
const SDL_PixelFormatDetails *pdetails = SDL_GetPixelFormatDetails(sfmt);
if (!pdetails) {
// If we can't get pixel format details, fall back to converting to RGBA8888
surf = SDL_ConvertSurface(surf, SDL_PIXELFORMAT_RGBA8888);
dofree = 1;
sfmt = SDL_PIXELFORMAT_RGBA8888;
pdetails = SDL_GetPixelFormatDetails(sfmt);
if (!pdetails) {
// Should never happen with RGBA8888, but just in case
return JS_ThrowReferenceError(js, "Unable to get pixel format details.");
}
}
// Check if format has alpha
bool has_alpha = (pdetails->Amask != 0);
// Choose a GPU format that closely matches the surface's format
SDL_GPUTextureFormat chosen_format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
switch (sfmt) {
case SDL_PIXELFORMAT_RGBA32:
case SDL_PIXELFORMAT_RGBX8888:
case SDL_PIXELFORMAT_XRGB8888:
case SDL_PIXELFORMAT_XBGR8888:
chosen_format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
break;
case SDL_PIXELFORMAT_RGBA4444:
case SDL_PIXELFORMAT_ABGR4444:
case SDL_PIXELFORMAT_ARGB4444:
case SDL_PIXELFORMAT_BGRA4444:
// 4-4-4-4 format
chosen_format = SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM;
break;
case SDL_PIXELFORMAT_RGB565:
case SDL_PIXELFORMAT_BGR565:
// 5-6-5 format
chosen_format = SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM;
break;
case SDL_PIXELFORMAT_RGBA5551:
case SDL_PIXELFORMAT_ARGB1555:
case SDL_PIXELFORMAT_BGRA5551:
case SDL_PIXELFORMAT_ABGR1555:
// 5-5-5-1 format
chosen_format = SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM;
break;
default:
surf = SDL_ConvertSurface(surf, SDL_PIXELFORMAT_RGBA32);
dofree = 1;
sfmt = SDL_PIXELFORMAT_RGBA32;
pdetails = SDL_GetPixelFormatDetails(sfmt);
chosen_format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
has_alpha = true;
break;
}
// If compression_level > 0, we override format with BC1/BC3
bool compress = (compression_level > 0);
int stb_mode = STB_DXT_NORMAL;
SDL_GPUTextureFormat compressed_format = chosen_format;
if (compress) {
if (has_alpha)
compressed_format = SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM; // DXT5
else
compressed_format = SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM; // DXT1
if (compression_level > 1) stb_mode = STB_DXT_HIGHQUAL;
}
SDL_GPUTexture *tex = SDL_CreateGPUTexture(gpu, &(SDL_GPUTextureCreateInfo) {
.type = SDL_GPU_TEXTURETYPE_2D,
.format = compress ? compressed_format : chosen_format,
.width = surf->w,
.height = surf->h,
.layer_count_or_depth = 1,
.num_levels = 1,
.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER
});
const void *pixel_data = surf->pixels;
size_t pixel_data_size = surf->pitch * surf->h;
unsigned char *upload_data = NULL;
size_t upload_size = pixel_data_size;
if (compress) {
// Compress with stb_dxt
int block_width = (surf->w + 3) / 4;
int block_height = (surf->h + 3) / 4;
int block_size = (compressed_format == SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM) ? 8 : 16;
int blocks_count = block_width * block_height;
unsigned char *compressed_data = (unsigned char*)malloc(blocks_count * block_size);
if (!compressed_data) {
if (dofree) SDL_DestroySurface(surf);
return JS_ThrowOutOfMemory(js);
}
const unsigned char *src_pixels = (const unsigned char *)pixel_data;
for (int by = 0; by < block_height; ++by) {
for (int bx = 0; bx < block_width; ++bx) {
unsigned char block_rgba[4*4*4];
memset(block_rgba, 0, sizeof(block_rgba));
for (int y = 0; y < 4; ++y) {
for (int x = 0; x < 4; ++x) {
int sx = bx*4 + x;
int sy = by*4 + y;
if (sx < surf->w && sy < surf->h) {
const unsigned char *pixel = src_pixels + sy * surf->pitch + sx * pdetails->bytes_per_pixel;
Uint32 pixelValue = 0;
// Copy pixel data into a Uint32 for SDL_GetRGBA-like extraction
// We'll use masks/shifts from pdetails
memcpy(&pixelValue, pixel, pdetails->bytes_per_pixel);
// Extract RGBA
unsigned char r = (pixelValue & pdetails->Rmask) >> pdetails->Rshift;
unsigned char g = (pixelValue & pdetails->Gmask) >> pdetails->Gshift;
unsigned char b = (pixelValue & pdetails->Bmask) >> pdetails->Bshift;
unsigned char a = pdetails->Amask ? ((pixelValue & pdetails->Amask) >> pdetails->Ashift) : 255;
// If bits are not fully 8-bit, scale them:
// pdetails->Rbits (etc.) give how many bits each channel uses
int Rmax = (1 << pdetails->Rbits) - 1;
int Gmax = (1 << pdetails->Gbits) - 1;
int Bmax = (1 << pdetails->Bbits) - 1;
int Amax = pdetails->Abits ? ((1 << pdetails->Abits) - 1) : 255;
if (pdetails->Rbits < 8) r = (r * 255) / Rmax;
if (pdetails->Gbits < 8) g = (g * 255) / Gmax;
if (pdetails->Bbits < 8) b = (b * 255) / Bmax;
if (pdetails->Amask && pdetails->Abits < 8) a = (a * 255) / Amax;
// Set block pixel
block_rgba[(y*4+x)*4+0] = r;
block_rgba[(y*4+x)*4+1] = g;
block_rgba[(y*4+x)*4+2] = b;
block_rgba[(y*4+x)*4+3] = a;
}
}
}
unsigned char *dest_block = compressed_data + (by * block_width + bx)*block_size;
int alpha = (compressed_format == SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM) ? 1 : 0;
stb_compress_dxt_block(dest_block, block_rgba, alpha, stb_mode);
}
}
upload_data = compressed_data;
upload_size = blocks_count * block_size;
} else {
// No compression, upload directly
upload_data = (unsigned char*)pixel_data;
upload_size = pixel_data_size;
}
SDL_GPUTransferBuffer *tex_buffer = SDL_CreateGPUTransferBuffer(
gpu,
&(SDL_GPUTransferBufferCreateInfo) {
.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
.size = upload_size
});
void *tex_ptr = SDL_MapGPUTransferBuffer(gpu, tex_buffer, false);
memcpy(tex_ptr, upload_data, upload_size);
SDL_UnmapGPUTransferBuffer(gpu, tex_buffer);
SDL_GPUCommandBuffer *uploadcmd = SDL_AcquireGPUCommandBuffer(gpu);
SDL_GPUCopyPass *copypass = SDL_BeginGPUCopyPass(uploadcmd);
SDL_UploadToGPUTexture(
copypass,
&(SDL_GPUTextureTransferInfo) {
.transfer_buffer = tex_buffer,
.offset = 0
},
&(SDL_GPUTextureRegion) {
.texture = tex,
.w = surf->w,
.h = surf->h,
.d = 1
},
false
);
SDL_EndGPUCopyPass(copypass);
SDL_SubmitGPUCommandBuffer(uploadcmd);
SDL_ReleaseGPUTransferBuffer(gpu, tex_buffer);
if (compress) {
free(upload_data);
}
ret = SDL_GPUTexture2js(js, tex);
JS_SetProperty(js, ret, width_atom, number2js(js, surf->w));
JS_SetProperty(js, ret, height_atom, number2js(js, surf->h));
JS_SetPropertyStr(js,ret,"dim", vec22js(js,(HMM_Vec2){surf->w,surf->h}));
if (dofree) SDL_DestroySurface(surf);
)
static JSValue js_gpu_make_pipeline(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
if (argc < 1)
return JS_ThrowTypeError(js, "gpu_pipeline requires a pipeline object");
JSValue pipe = argv[0];
if (!JS_IsObject(pipe))
return JS_ThrowTypeError(js, "gpu_pipeline argument must be an object");
SDL_GPUGraphicsPipelineCreateInfo info = {0};
// ---------------------------------------------------
// 2. Retrieve vertex buffer descriptions array
// ---------------------------------------------------
JSValue vbd_val = JS_GetPropertyStr(js, pipe, "vertex_buffer_descriptions");
Uint32 vbd_len = js_arrlen(js,vbd_val);
SDL_GPUVertexBufferDescription vbd[vbd_len];
for (Uint32 i = 0; i < vbd_len; i++) {
JSValue elem = JS_GetPropertyUint32(js, vbd_val, i);
if (JS_IsObject(elem)) {
JSValue slot_val = JS_GetPropertyStr(js, elem, "slot");
JSValue pitch_val = JS_GetPropertyStr(js, elem, "pitch");
JSValue rate_val = JS_GetPropertyStr(js, elem, "input_rate");
JSValue step_val = JS_GetPropertyStr(js, elem, "instance_step_rate");
// Slot
Uint32 slot = 0;
JS_ToUint32(js, &slot, slot_val);
JS_FreeValue(js, slot_val);
// Pitch
Uint32 pitch = 0;
JS_ToUint32(js, &pitch, pitch_val);
JS_FreeValue(js, pitch_val);
// Input Rate
const char *rate_str = JS_ToCString(js, rate_val);
SDL_GPUVertexInputRate input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
if (rate_str) {
if (!strcmp(rate_str, "vertex")) input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
else if (!strcmp(rate_str, "instance")) input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE;
JS_FreeCString(js, rate_str);
}
JS_FreeValue(js, rate_val);
// Instance Step Rate
Uint32 step_rate = 0;
JS_ToUint32(js, &step_rate, step_val);
JS_FreeValue(js, step_val);
vbd[i].slot = slot;
vbd[i].pitch = pitch;
vbd[i].input_rate = input_rate;
vbd[i].instance_step_rate = step_rate;
}
JS_FreeValue(js, elem);
}
JS_FreeValue(js, vbd_val);
// ---------------------------------------------------
// 3. Retrieve vertex attributes array
// ---------------------------------------------------
JSValue vat_val = JS_GetPropertyStr(js, pipe, "vertex_attributes");
Uint32 vat_len = js_arrlen(js,vat_val);
SDL_GPUVertexAttribute vat[vat_len];
for (Uint32 i = 0; i < vat_len; i++) {
JSValue elem = JS_GetPropertyUint32(js, vat_val, i);
if (JS_IsObject(elem)) {
JSValue loc_val = JS_GetPropertyStr(js, elem, "location");
JSValue slot_val = JS_GetPropertyStr(js, elem, "buffer_slot");
JSValue fmt_val = JS_GetPropertyStr(js, elem, "format");
JSValue off_val = JS_GetPropertyStr(js, elem, "offset");
// location
Uint32 location = 0;
JS_ToUint32(js, &location, loc_val);
JS_FreeValue(js, loc_val);
// buffer_slot
Uint32 buffer_slot = 0;
JS_ToUint32(js, &buffer_slot, slot_val);
JS_FreeValue(js, slot_val);
// format
const char *fmt_str = JS_ToCString(js, fmt_val);
SDL_GPUVertexElementFormat format = -1; // TODO: Check for error (if this is still -1)
if (fmt_str) {
// Map from string to SDL_GPUVertexElementFormat
if (!strcmp(fmt_str, "float2")) format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
else if (!strcmp(fmt_str, "float3")) format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3;
else if (!strcmp(fmt_str, "float4")) format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
else if (!strcmp(fmt_str, "color"))
format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
JS_FreeCString(js, fmt_str);
}
JS_FreeValue(js, fmt_val);
// offset
Uint32 offset = 0;
JS_ToUint32(js, &offset, off_val);
JS_FreeValue(js, off_val);
vat[i].location = location;
vat[i].buffer_slot = buffer_slot;
vat[i].format = format;
vat[i].offset = offset;
}
JS_FreeValue(js, elem);
}
JS_FreeValue(js, vat_val);
info.vertex_input_state = (SDL_GPUVertexInputState){
.vertex_buffer_descriptions = vbd,
.num_vertex_buffers = vbd_len,
.vertex_attributes = vat,
.num_vertex_attributes = vat_len
};
// Vertex Shader
JSValue vertex_val = JS_GetPropertyStr(js, pipe, "vertex");
JSValue fragment_val = JS_GetPropertyStr(js,pipe,"fragment");
JS_GETPROP(js,info.vertex_shader, vertex_val, gpu, SDL_GPUShader)
JS_GETPROP(js, info.fragment_shader, fragment_val, gpu, SDL_GPUShader)
JS_FreeValue(js, vertex_val);
JS_FreeValue(js, fragment_val);
// Primitive Type
JS_GETPROP(js, info.primitive_type, pipe, primitive, SDL_GPUPrimitiveType)
// Rasterizer State
JS_GETPROP(js, info.rasterizer_state.fill_mode, pipe, fill, SDL_GPUFillMode)
JS_GETPROP(js, info.rasterizer_state.cull_mode, pipe, cull, SDL_GPUCullMode)
JS_GETPROP(js, info.rasterizer_state.front_face, pipe, face, SDL_GPUFrontFace)
// Depth Stencil State
JSValue depth_val = JS_GetPropertyStr(js, pipe, "depth");
if (JS_IsObject(depth_val)) {
JS_GETPROP(js,info.depth_stencil_state.compare_op, depth_val, compare, SDL_GPUCompareOp)
JS_GETPROP(js,info.depth_stencil_state.enable_depth_test, depth_val, test, bool)
JS_GETPROP(js,info.depth_stencil_state.enable_depth_write, depth_val, write, bool)
JS_GETPROP(js,info.rasterizer_state.depth_bias_constant_factor, depth_val, bias, number)
JS_GETPROP(js,info.rasterizer_state.depth_bias_slope_factor, depth_val,bias_slope_scale, number)
JS_GETPROP(js,info.rasterizer_state.depth_bias_clamp, depth_val,bias_clamp_val, number)
}
JS_FreeValue(js, depth_val);
// Stencil State
JSValue stencil_val = JS_GetPropertyStr(js, pipe, "stencil");
if (JS_IsObject(stencil_val)) {
JS_GETPROP(js,info.depth_stencil_state.enable_stencil_test, stencil_val, enabled, bool)
if (info.depth_stencil_state.enable_stencil_test) {
JS_GETPROP(js, info.depth_stencil_state.front_stencil_state, stencil_val, front, SDL_GPUStencilOpState)
JS_GETPROP(js, info.depth_stencil_state.back_stencil_state, stencil_val, back, SDL_GPUStencilOpState)
JSValue compare_mask_val = JS_GetPropertyStr(js, stencil_val, "compare_mask");
uint32_t tmp;
JS_ToUint32(js, &tmp, compare_mask_val);
info.depth_stencil_state.compare_mask = tmp;
JS_FreeValue(js, compare_mask_val);
// Write Mask
JSValue write_mask_val = JS_GetPropertyStr(js, stencil_val, "write_mask");
JS_ToUint32(js, &tmp, write_mask_val);
info.depth_stencil_state.write_mask = tmp;
}
}
JS_FreeValue(js, stencil_val);
JSValue js_tar = JS_GetPropertyStr(js,pipe,"target");
SDL_GPUGraphicsPipelineTargetInfo target_info = {0};
JSValue color_tars = JS_GetPropertyStr(js,js_tar,"color_targets");
target_info.num_color_targets = js_arrlen(js,color_tars);
SDL_GPUColorTargetDescription dsc[target_info.num_color_targets];
target_info.color_target_descriptions = dsc;
for (int i = 0; i < target_info.num_color_targets; i++) {
JSValue c = JS_GetPropertyUint32(js,color_tars,i);
dsc[i] = js2SDL_GPUColorTargetDescription(js,c);
JS_FreeValue(js,c);
}
JS_FreeValue(js,color_tars);
JS_GETPROP(js,target_info.depth_stencil_format,js_tar,depth,SDL_GPUTextureFormat);
if (target_info.depth_stencil_format) target_info.has_depth_stencil_target = 1;
info.target_info = target_info;
JS_FreeValue(js, js_tar);
// Create the pipeline
SDL_GPUGraphicsPipeline *pipeline = SDL_CreateGPUGraphicsPipeline(gpu, &info);
if (!pipeline) return JS_ThrowInternalError(js, "Failed to create GPU pipeline");
return SDL_GPUGraphicsPipeline2js(js, pipeline);
}
// Now the function to create the sampler
static JSValue js_gpu_make_sampler(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
SDL_GPUSamplerCreateInfo info = {0};
JSValue sampler = argv[0];
JS_GETPROP(js,info.min_filter,sampler,min_filter,SDL_GPUFilter)
JS_GETPROP(js,info.mag_filter,sampler,mag_filter,SDL_GPUFilter)
JS_GETPROP(js,info.mipmap_mode, sampler, mipmap, SDL_GPUSamplerMipmapMode)
JS_GETPROP(js,info.address_mode_u, sampler, u, SDL_GPUSamplerAddressMode)
JS_GETPROP(js,info.address_mode_v, sampler, v, SDL_GPUSamplerAddressMode)
JS_GETPROP(js,info.address_mode_w, sampler, w, SDL_GPUSamplerAddressMode)
JS_GETPROP(js,info.mip_lod_bias, sampler, mip_bias, number)
JS_GETPROP(js,info.max_anisotropy, sampler, max_anisotropy, number)
JS_GETPROP(js,info.compare_op,sampler,compare_op,SDL_GPUCompareOp)
JS_GETPROP(js,info.min_lod,sampler,min_lod,number)
JS_GETPROP(js,info.max_lod,sampler,max_lod,number)
JS_GETPROP(js, info.enable_anisotropy, sampler, anistropy, bool)
JS_GETPROP(js, info.enable_compare, sampler, compare, bool)
// Create the sampler
SDL_GPUSampler *sdl_sampler = SDL_CreateGPUSampler(gpu, &info);
if (!sdl_sampler) return JS_ThrowInternalError(js, "Failed to create GPU sampler");
return SDL_GPUSampler2js(js, sdl_sampler); // You need to implement SDL_GPUSampler2js similar to pipeline
}
JSC_CCALL(gpu_texture,
SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
SDL_GPUTextureCreateInfo info = {0};
JSValue fmt = argv[0];
JS_GETPROP(js, info.width, fmt, width, number)
JS_GETPROP(js, info.height, fmt, height, number)
JS_GETPROP(js, info.layer_count_or_depth, fmt, layers, number)
JS_GETPROP(js, info.num_levels, fmt, mip_levels, number)
JS_GETPROP(js, info.sample_count, fmt, samples, number)
JS_GETPROP(js, info.type, fmt, type, SDL_GPUTextureType)
JS_GETPROP(js, info.format, fmt, format, SDL_GPUTextureFormat)
if (JS_GETBOOL(js,fmt,"sampler"))
info.usage |= SDL_GPU_TEXTUREUSAGE_SAMPLER;
if (JS_GETBOOL(js,fmt,"color_target"))
info.usage |= SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
if (JS_GETBOOL(js,fmt,"depth_target"))
info.usage |= SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET;
if (JS_GETBOOL(js,fmt,"read"))
info.usage |= (SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ | SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ);
if (JS_GETBOOL(js,fmt,"write"))
info.usage |= SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE;
if (JS_GETBOOL(js,fmt,"readwrite"))
info.usage |= SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE;
SDL_GPUTexture *tex = SDL_CreateGPUTexture(gpu,&info);
if (!tex) return JS_ThrowReferenceError(js, "Unable to create texture: %s", SDL_GetError());
JSValue jstex = SDL_GPUTexture2js(js,tex);
JS_SetPropertyStr(js,jstex,"width", number2js(js,info.width));
JS_SetPropertyStr(js,jstex,"height", number2js(js,info.height));
JS_SetPropertyStr(js,jstex,"dim", vec22js(js,(HMM_Vec2){info.width, info.height}));
return jstex;
)
static HMM_Vec3 base_quad[4] = {
{0.0,0.0,1.0},
{1.0,0.0,1.0},
{0.0,1.0,1.0},
{1.0,1.0,1.0}
};
static HMM_Vec4 base_quad_4[4] = {
{ 0.0,0.0, 1.0f, 1.0f },
{ 1,0,0.0, 1.0f, 1.0f },
{ 0.0,1.0, 1.0f, 1.0f },
{ 1.0,1.0, 1.0f, 1.0f }
};
static inline void add_quad(text_vert **verts, rect *restrict src, rect *restrict dst)
{
text_vert v = (text_vert){
.pos = (HMM_Vec2){dst->x, dst->y},
.uv = (HMM_Vec2){src->x,src->y},
.color = (HMM_Vec4){1,1,1,1}
};
arrput(*verts, v);
v = (text_vert){
.pos = (HMM_Vec2){dst->x+dst->w, dst->y},
.uv = (HMM_Vec2){src->x+src->w,src->y},
.color = (HMM_Vec4){1,1,1,1}
};
arrput(*verts, v);
v = (text_vert){
.pos = (HMM_Vec2){dst->x, dst->y+dst->h},
.uv = (HMM_Vec2){src->x,src->y+src->h},
.color = (HMM_Vec4){1,1,1,1}
};
arrput(*verts, v);
v = (text_vert){
.pos = (HMM_Vec2){dst->x+dst->w, dst->y+dst->h},
.uv = (HMM_Vec2){src->x+src->w,src->y+src->h},
.color = (HMM_Vec4){1,1,1,1}
};
arrput(*verts, v);
}
typedef struct {
text_vert vert[4];
} quad;
// Comparator inline for potential compiler inlining
static inline int sprite_compare(const sprite *a, const sprite *b) {
if (a->layer != b->layer) return a->layer - b->layer;
if (a->affine.y != b->affine.y)
return (b->affine.y - a->affine.y);
if (a->tex != b->tex)
return (a->tex < b->tex) ? -1 : 1;
return 0;
}
// Merge two sorted sub-ranges [left..mid] and [mid+1..right]
static void merge_sprites(sprite *arr, sprite *temp, size_t left, size_t mid, size_t right) {
size_t i = left, j = mid + 1, k = left;
while (i <= mid && j <= right)
if (sprite_compare(&arr[i], &arr[j]) <= 0) temp[k++] = arr[i++];
else temp[k++] = arr[j++];
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];
for (size_t p = left; p <= right; p++)
arr[p] = temp[p];
}
// Recursive mergesort
static void merge_sort_recursive(sprite *arr, sprite *temp, size_t left, size_t right) {
if (left >= right) return;
size_t mid = (left + right) / 2;
merge_sort_recursive(arr, temp, left, mid);
merge_sort_recursive(arr, temp, mid + 1, right);
merge_sprites(arr, temp, left, mid, right);
}
// Public function to sort the array of sprites with mergesort
void better_sort_sprites(sprite *arr, size_t length) {
if (length < 2) return;
sprite *temp = malloc(length * sizeof(sprite));
if (!temp) return; // fallback or handle error
merge_sort_recursive(arr, temp, 0, length - 1);
free(temp);
}
int sort_sprite_backtofront(const sprite *a, const sprite *b)
{
if (a->layer != b->layer) return a->layer - b->layer;
if (a->tex != b->tex) return ((uintptr_t)a->tex < (uintptr_t)b->tex) ? -1 : 1;
return 0;
}
int sort_sprite_fronttoback(const sprite *a, const sprite *b)
{
if (a->layer != b->layer) return b->layer - a->layer;
if (a->tex != b->tex) return ((uintptr_t)a->tex < (uintptr_t)b->tex) ? -1 : 1;
return 0;
}
int sort_sprite_texture(const sprite *a, const sprite *b)
{
if (a->tex != b->tex) return ((uintptr_t)a->tex < (uintptr_t)b->tex) ? -1 : 1;
return 0;
}
int sort_sprite(const sprite *a, const sprite *b)
{
if (a->layer != b->layer) return a->layer - b->layer;
if (a->affine.y != b->affine.y) return b->affine.y - a->affine.y;
if (a->tex != b->tex) return ((uintptr_t)a->tex < (uintptr_t)b->tex) ? -1 : 1;
return 0;
}
JSC_CCALL(gpu_sort_sprite,
JSValue a = argv[0];
JSValue b = argv[1];
int alayer, blayer;
JS_GETATOM(js,alayer,a,layer_atom,number)
JS_GETATOM(js,blayer,b,layer_atom,number)
if (alayer != blayer) return number2js(js,alayer - blayer);
rect ar, br;
JS_GETATOM(js,ar,a,rect_atom,rect)
JS_GETATOM(js,br,b,rect_atom,rect)
if (ar.y != br.y) return number2js(js,br.y-ar.y);
JSValue aimg,bimg;
aimg = JS_GetProperty(js,a,image_atom);
bimg = JS_GetProperty(js,b,image_atom);
JS_FreeValue(js,aimg);
JS_FreeValue(js,bimg);
if (!JS_SameValue(js,aimg,bimg)) return number2js(js,JS_VALUE_GET_PTR(aimg) < JS_VALUE_GET_PTR(bimg) ? -1 : 1);
return number2js(js,0);
)
JSC_CCALL(gpu_make_sprite_queue,
sprite *sprites = NULL;
size_t quads = 0;
int needfree = 1;
int sort = js2number(js,argv[3]);
// test for fastest
size_t size;
sprites = JS_GetArrayBuffer(js,&size,argv[0]);
if (sprites) {
quads = size/sizeof(*sprites);
needfree = 0;
for (int i = 0; i < quads; i++)
JS_DupValue(js,sprites[i].image);
} else {
quads = js_arrlen(js, argv[0]);
arrsetcap(sprites, quads);
for (int i = 0; i < quads; i++) {
JSValue sub = JS_GetPropertyUint32(js, argv[0], i);
sprite *jsp = js2sprite(js, sub);
if (jsp) {
arrput(sprites, *jsp);
JS_DupValue(js,jsp->image);
}
else {
sprite sp = {0};
JS_GETATOM(js,sp.affine, sub, rect_atom, rect)
JS_GETATOM(js,sp.color,sub,color_atom,color)
JS_GETATOM(js,sp.layer,sub,layer_atom,number)
JS_GETATOM(js,sp.uv,sub,src_atom,rect)
sp.image = JS_GetProperty(js,sub,image_atom);
JS_GETATOM(js,sp.tex,sp.image,texture_atom,SDL_GPUTexture)
arrput(sprites,sp);
}
JS_FreeValue(js, sub);
}
}
if (sort) qsort(sprites, quads, sizeof(sprite), sort_sprite);
// else qsort(sprites, quads, sizeof(sprite), sort_sprite_texture);
struct quad_buffers buffers = quad_buffers_new(quads*4);
for (int i = 0; i < quads; i++) {
rect pr = sprites[i].affine;
rect uv = sprites[i].uv;
HMM_Vec4 c = sprites[i].color;
int idx = i * 4;
buffers.pos[idx + 0] = (HMM_Vec2){ pr.x, pr.y };
buffers.pos[idx + 1] = (HMM_Vec2){ pr.x+pr.w, pr.y };
buffers.pos[idx + 2] = (HMM_Vec2){ pr.x, pr.y+pr.h };
buffers.pos[idx + 3] = (HMM_Vec2){ pr.x+pr.w, pr.y+pr.h };
buffers.uv[idx + 0] = (HMM_Vec2){ uv.x, uv.y+uv.h };
buffers.uv[idx + 1] = (HMM_Vec2){ uv.x+uv.w, uv.y+uv.h };
buffers.uv[idx + 2] = (HMM_Vec2){ uv.x, uv.y };
buffers.uv[idx + 3] = (HMM_Vec2){ uv.x+uv.w, uv.y };
buffers.color[idx + 0] = c;
buffers.color[idx + 1] = c;
buffers.color[idx + 2] = c;
buffers.color[idx + 3] = c;
}
JSValue mesh = quadbuffers_to_mesh(js, buffers);
ret = JS_NewArray(js);
int first_index = 0;
int count = 0;
int n = 0;
JSValue img = JS_UNDEFINED;
for (int i = 0; i < quads; i++) {
if (!JS_SameValue(js, sprites[i].image, img)) {
if (count > 0) {
JSValue q = JS_NewObject(js);
JS_SetPropertyStr(js, q, "type", JS_NewString(js, "geometry"));
JS_SetPropertyStr(js, q, "mesh", JS_DupValue(js, mesh));
JS_SetPropertyStr(js, q, "pipeline", JS_DupValue(js, argv[2]));
JS_SetPropertyStr(js, q, "image", img);
JS_SetPropertyStr(js, q, "first_index", number2js(js, first_index));
JS_SetPropertyStr(js, q, "num_indices", number2js(js, count * 6));
JS_SetPropertyUint32(js, ret, n++, q);
}
first_index = i*6;
count = 1;
img = JS_DupValue(js, sprites[i].image);
} else count++;
JS_FreeValue(js,sprites[i].image);
}
if (count > 0) {
JSValue q = JS_NewObject(js);
JS_SetPropertyStr(js, q, "type", JS_NewString(js, "geometry"));
JS_SetPropertyStr(js, q, "mesh", JS_DupValue(js, mesh));
JS_SetPropertyStr(js, q, "pipeline", JS_DupValue(js, argv[2]));
JS_SetPropertyStr(js, q, "image", img);
JS_SetPropertyStr(js, q, "first_index", number2js(js, first_index));
JS_SetPropertyStr(js, q, "num_indices", number2js(js, count * 6));
JS_SetPropertyUint32(js, ret, n++, q);
}
if (needfree)
arrfree(sprites);
JS_FreeValue(js, mesh);
)
JSC_CCALL(gpu_make_sprite_mesh,
size_t quads = js_arrlen(js, argv[0]);
size_t verts = quads*4;
size_t count = quads*6;
// Prepare arrays on CPU
HMM_Vec2 *posdata = malloc(sizeof(*posdata)*verts);
HMM_Vec2 *uvdata = malloc(sizeof(*uvdata)*verts);
HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts);
for (int i = 0; i < quads; i++) {
JSValue sub = JS_GetPropertyUint32(js,argv[0],i);
transform *tr;
rect src;
HMM_Vec4 color;
JS_GETATOM(js,src,sub,src_atom,rect)
JS_GETATOM(js,color,sub,color_atom,color)
JS_GETATOM(js,tr,sub,transform_atom,transform)
JS_FreeValue(js,sub);
size_t base = i*4;
if (tr) {
HMM_Mat3 trmat = transform2mat3(tr);
for (int j = 0; j < 4; j++)
posdata[base+j] = HMM_MulM3V3(trmat, base_quad[j]).xy;
} else {
rect dst;
JS_GETATOM(js,dst,sub,rect_atom,rect);
posdata[base+0] = (HMM_Vec2){dst.x,dst.y};
posdata[base + 1] = (HMM_Vec2){ dst.x+dst.w, dst.y };
posdata[base + 2] = (HMM_Vec2){ dst.x, dst.y+dst.h };
posdata[base + 3] = (HMM_Vec2){ dst.x+dst.w, dst.y+dst.h };
}
uvdata[base+0] = (HMM_Vec2){src.x, src.y+src.h};
uvdata[base+1] = (HMM_Vec2){src.x+src.w, src.y+src.h};
uvdata[base+2] = (HMM_Vec2){src.x, src.y};
uvdata[base+3] = (HMM_Vec2){src.x+src.w, src.y};
colordata[base+0] = color;
colordata[base+1] = color;
colordata[base+2] = color;
colordata[base+3] = color;
}
// Check old mesh
JSValue old_mesh = JS_UNDEFINED;
if (argc > 1)
old_mesh = argv[1];
// Needed sizes
size_t pos_size = sizeof(*posdata)*verts;
size_t uv_size = sizeof(*uvdata)*verts;
size_t color_size = sizeof(*colordata)*verts;
BufferCheckResult pos_chk = get_or_extend_buffer(js, old_mesh, pos_atom, pos_size, JS_TYPED_ARRAY_FLOAT32, 2, 1, 0);
BufferCheckResult uv_chk = get_or_extend_buffer(js, old_mesh, uv_atom, uv_size, JS_TYPED_ARRAY_FLOAT32, 2, 1, 0);
BufferCheckResult color_chk = get_or_extend_buffer(js, old_mesh, color_atom, color_size, JS_TYPED_ARRAY_FLOAT32, 4, 1, 0);
int need_new_all = pos_chk.need_new || uv_chk.need_new || color_chk.need_new;
ret = JS_NewObject(js);
if (need_new_all) {
// Create all new buffers
JSValue new_pos = make_gpu_buffer(js, posdata, pos_size, JS_TYPED_ARRAY_FLOAT32, 2, 1,0);
JSValue new_uv = make_gpu_buffer(js, uvdata, uv_size, JS_TYPED_ARRAY_FLOAT32, 2, 1,0);
JSValue new_color = make_gpu_buffer(js, colordata, color_size, JS_TYPED_ARRAY_FLOAT32, 0, 1,0);
JS_SetProperty(js, ret, pos_atom, new_pos);
JS_SetProperty(js, ret, uv_atom, new_uv);
JS_SetProperty(js, ret, color_atom, new_color);
// Indices
JSValue indices = make_quad_indices_buffer(js, quads);
JS_SetProperty(js, ret, indices_atom, indices);
} else {
// Reuse the old buffers
// Just copy data into existing buffers via their pointers
memcpy(pos_chk.ptr, posdata, pos_size);
memcpy(uv_chk.ptr, uvdata, uv_size);
memcpy(color_chk.ptr, colordata, color_size);
// Duplicate old references since we're returning a new object
JS_SetProperty(js, ret, pos_atom, JS_DupValue(js, pos_chk.val));
JS_SetProperty(js, ret, uv_atom, JS_DupValue(js, uv_chk.val));
JS_SetProperty(js, ret, color_atom, JS_DupValue(js, color_chk.val));
// Indices can remain the same if they were also large enough. If using a shared global index buffer:
JSValue indices = make_quad_indices_buffer(js, quads);
JS_SetProperty(js, ret, indices_atom, indices);
}
JS_SetProperty(js, ret, vertices_atom, number2js(js, verts));
JS_SetProperty(js, ret, count_atom, number2js(js, count));
JS_SetPropertyStr(js,ret,"num_indices", number2js(js,count));
// Free temporary CPU arrays
free(posdata);
free(uvdata);
free(colordata);
// Free old buffer values if they were fetched
if (!JS_IsUndefined(pos_chk.val)) JS_FreeValue(js, pos_chk.val);
if (!JS_IsUndefined(uv_chk.val)) JS_FreeValue(js, uv_chk.val);
if (!JS_IsUndefined(color_chk.val)) JS_FreeValue(js, color_chk.val);
return ret;
)
JSC_CCALL(gpu_make_quad,
size_t quads = 1;
size_t verts = quads*4;
size_t count = quads*6;
// Prepare arrays on CPU
HMM_Vec2 *posdata = malloc(sizeof(*posdata)*verts);
HMM_Vec2 *uvdata = malloc(sizeof(*uvdata)*verts);
HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts);
HMM_Vec4 usecolor = (HMM_Vec4){1,1,1,1};
posdata[0] = (HMM_Vec2){0,1};
posdata[1] = (HMM_Vec2){1,1};
posdata[2] = (HMM_Vec2){0,0};
posdata[3] = (HMM_Vec2){1,0};
uvdata[0] = (HMM_Vec2){0,0};
uvdata[1] = (HMM_Vec2){1,0};
uvdata[2] = (HMM_Vec2){0,1};
uvdata[3] = (HMM_Vec2){1,1};
colordata[0] = usecolor;
colordata[1] = usecolor;
colordata[2] = usecolor;
colordata[3] = usecolor;
ret = JS_NewObject(js);
JSValue pos = make_gpu_buffer(js, posdata, sizeof(*posdata)*verts, JS_TYPED_ARRAY_FLOAT32, 2, 1,0);
JSValue uv = make_gpu_buffer(js, uvdata, sizeof(*uvdata)*verts, JS_TYPED_ARRAY_FLOAT32, 2, 1,0);
JSValue color = make_gpu_buffer(js, colordata, sizeof(*colordata)*verts, JS_TYPED_ARRAY_FLOAT32, 0, 1,0);
JS_SetProperty(js, ret, pos_atom, pos);
JS_SetProperty(js, ret, uv_atom, uv);
JS_SetProperty(js, ret, color_atom, color);
JSValue indices = make_quad_indices_buffer(js, quads);
JS_SetProperty(js, ret, indices_atom, indices);
JS_SetProperty(js, ret, vertices_atom, number2js(js, verts));
JS_SetProperty(js, ret, count_atom, number2js(js, count));
JS_SetPropertyStr(js,ret,"num_indices", number2js(js,count));
// Free temporary CPU arrays
free(posdata);
free(uvdata);
free(colordata);
return ret;
)
JSC_CCALL(gpu_driver,
SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
ret = JS_NewString(js, SDL_GetGPUDeviceDriver(gpu));
)
static JSValue js_gpu_make_shader(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
if (argc < 1 || !JS_IsObject(argv[0]))
return JS_ThrowTypeError(js, "make_shader expects an object with code, stage, num_samplers, num_textures, num_storage_buffers, num_uniform_buffers");
SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, self);
JSValue obj = argv[0];
SDL_GPUShaderCreateInfo info = {0};
// code
JSValue code_val = JS_GetPropertyStr(js, obj, "code");
size_t code_size;
void *code_data = JS_GetArrayBuffer(js, &code_size, code_val);
JS_FreeValue(js, code_val);
if (!code_data)
return JS_ThrowTypeError(js, "shader.code must be an ArrayBuffer");
// stage
JSValue stage_val = JS_GetPropertyStr(js, obj, "stage");
const char *stage_str = JS_ToCString(js, stage_val);
if (stage_str) {
if (!strcmp(stage_str, "vertex")) info.stage = SDL_GPU_SHADERSTAGE_VERTEX;
else if (!strcmp(stage_str, "fragment")) info.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
JS_FreeCString(js, stage_str);
} // TODO: Make this its own function
JS_FreeValue(js, stage_val);
// num_samplers
JS_GETPROP(js, info.num_samplers, obj, num_samplers, number)
JS_GETPROP(js, info.num_storage_textures, obj, num_textures, number)
JS_GETPROP(js, info.num_storage_buffers, obj, num_storage_buffers, number)
JS_GETPROP(js, info.num_uniform_buffers, obj, num_uniform_buffers, number)
JS_GETPROP(js, info.format, obj, format, SDL_GPUShaderFormat)
JSValue entry_val = JS_GetPropertyStr(js,obj,"entrypoint");
info.entrypoint = JS_ToCString(js,entry_val);
JS_FreeValue(js,entry_val);
info.code_size = code_size;
info.code = code_data;
info.props = 0; // No extension properties by default
JS_FreeCString(js,info.entrypoint);
SDL_GPUShader *shader = SDL_CreateGPUShader(gpu, &info);
if (!shader)
return JS_ThrowReferenceError(js, "Unable to create shader: %s", SDL_GetError());
return SDL_GPUShader2js(js, shader);
}
JSC_CCALL(gpu_acquire_cmd_buffer,
SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, self);
SDL_GPUCommandBuffer *cb = SDL_AcquireGPUCommandBuffer(gpu);
if (!cb) return JS_ThrowReferenceError(js,"Unable to acquire command buffer: %s", SDL_GetError());
// Wrap cb in a JS object
return SDL_GPUCommandBuffer2js(js, cb);
)
/* takes argv
0: a command buffer to write to
1: a buffer or array of buffers to upload
2: an optional transfer buffer to use; if undefined a temporary one is used
*/
JSC_CCALL(gpu_upload,
Uint64 ss = SDL_GetTicksNS();
JSValue js_cmd = argv[0];
JSValue js_buffers = argv[1];
JSValue js_transfer = argv[2];
SDL_GPUDevice *gpu = js2SDL_GPUDevice(js, self);
if (!gpu)
return JS_ThrowTypeError(js, "Invalid GPU device");
SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, js_cmd);
if (!cmds)
return JS_ThrowTypeError(js, "Invalid command buffer");
if (!JS_IsArray(js, js_buffers))
return JS_ThrowTypeError(js, "buffers must be an array");
size_t len = js_arrlen(js, js_buffers);
if (len == 0)
return JS_DupValue(js, js_transfer); // No data to upload, just return existing transfer buffer
struct {
SDL_GPUBuffer *gpu_buffer;
void *data;
size_t size;
} *items = malloc(sizeof(*items) * len);
if (!items)
return JS_ThrowOutOfMemory(js);
size_t total_size_needed = 0;
for (size_t i = 0; i < len; i++) {
JSValue js_buf = JS_GetPropertyUint32(js, js_buffers, i);
if (JS_IsUndefined(js_buf))
continue;
gpu_buffer_unpack(js, gpu, js_buf, &items[i].size, &items[i].data, &items[i].gpu_buffer);
total_size_needed += items[i].size;
JS_FreeValue(js, js_buf);
}
SDL_GPUTransferBuffer *transfer = js2SDL_GPUTransferBuffer(js,js_transfer);
if (transfer) {
// ensure it's large enough
size_t transfer_size = js_getnum_str(js,js_transfer, "size");
if (transfer_size < total_size_needed) {
total_size_needed *= 1.5;
transfer = SDL_CreateGPUTransferBuffer( gpu, &(SDL_GPUTransferBufferCreateInfo){
.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
.size = total_size_needed
}
);
ret = SDL_GPUTransferBuffer2js(js,transfer);
JS_SetPropertyStr(js,ret,"size", number2js(js,total_size_needed));
} else
ret = JS_DupValue(js,js_transfer); // supplied transfer buffer is fine so we use it
} else {
transfer = SDL_CreateGPUTransferBuffer( gpu, &(SDL_GPUTransferBufferCreateInfo){
.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
.size = total_size_needed
}
);
ret = SDL_GPUTransferBuffer2js(js,transfer);
JS_SetPropertyStr(js,ret,"size", number2js(js,total_size_needed));
}
SDL_GPUCopyPass *copy_pass = SDL_BeginGPUCopyPass(cmds);
if (!copy_pass) {
free(items);
return JS_ThrowReferenceError(js, "Failed to begin copy pass");
}
void *mapped_data = SDL_MapGPUTransferBuffer(gpu, transfer, false);
if (!mapped_data) {
SDL_EndGPUCopyPass(copy_pass);
free(items);
return JS_ThrowReferenceError(js, "Failed to map transfer buffer: %s", SDL_GetError());
}
Uint64 sy = SDL_GetTicksNS();
// Copy all data into the mapped transfer buffer
size_t current_offset = 0;
for (size_t i = 0; i < len; i++) {
memcpy(mapped_data + current_offset, items[i].data, items[i].size);
current_offset += items[i].size;
}
SDL_UnmapGPUTransferBuffer(gpu, transfer);
// Issue uploads for each item
current_offset = 0;
for (size_t i = 0; i < len; i++) {
SDL_UploadToGPUBuffer(
copy_pass,
&(SDL_GPUTransferBufferLocation){
.transfer_buffer = transfer,
.offset = current_offset
},
&(SDL_GPUBufferRegion){
.buffer = items[i].gpu_buffer,
.offset = 0,
.size = items[i].size
},
false
);
current_offset += items[i].size;
}
SDL_EndGPUCopyPass(copy_pass);
free(items);
)
JSC_CCALL(gpu_wait_for_fences,
SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
int n = js_arrlen(js,argv[0]);
SDL_GPUFence *fences[n];
for (int i = 0; i < n; i++) {
JSValue a = JS_GetPropertyUint32(js,argv[0],i);
fences[i] = js2SDL_GPUFence(js,a);
JS_FreeValue(js,a);
}
int wait_all = JS_ToBool(js,argv[1]);
return JS_NewBool(js,SDL_WaitForGPUFences(gpu,wait_all,fences,n));
)
JSC_CCALL(gpu_query_fence,
SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
SDL_GPUFence *fence = js2SDL_GPUFence(js,argv[0]);
return JS_NewBool(js,SDL_QueryGPUFence(gpu,fence));
)
JSC_CCALL(gpu_shader_format,
SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
SDL_GPUShaderFormat fmt = SDL_GetGPUShaderFormats(gpu);
if (!fmt) return JS_ThrowReferenceError(js, "Shader format available invalid.");
JSValue arr = JS_NewArray(js);
int i = 0;
if (fmt & SDL_GPU_SHADERFORMAT_PRIVATE) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, ".private"));
if (fmt & SDL_GPU_SHADERFORMAT_SPIRV) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, "spv"));
if (fmt & SDL_GPU_SHADERFORMAT_DXBC) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, "dxbc"));
if (fmt & SDL_GPU_SHADERFORMAT_DXIL) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, "dxil"));
if (fmt & SDL_GPU_SHADERFORMAT_MSL) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, "msl"));
if (fmt & SDL_GPU_SHADERFORMAT_METALLIB) JS_SetPropertyUint32(js, arr, i++, JS_NewString(js, "metallib"));
return arr;
)
JSC_CCALL(gpu_compute_pipeline,
SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self);
SDL_GPUComputePipelineCreateInfo info = {0};
JSValue pipe = argv[0];
JS_GETPROP(js, info.num_samplers, pipe, num_samplers, number)
JS_GETPROP(js,info.num_readonly_storage_textures,pipe,num_readonly_storage_textures,number)
JS_GETPROP(js,info.num_readonly_storage_buffers,pipe,num_readonly_storage_buffers,number)
JS_GETPROP(js,info.num_readwrite_storage_textures,pipe,num_readwrite_storage_textures,number)
JS_GETPROP(js,info.num_readwrite_storage_buffers,pipe,num_readwrite_storage_buffers,number)
JS_GETPROP(js,info.threadcount_x,pipe,threadcount_x,number)
JS_GETPROP(js,info.threadcount_y,pipe,threadcount_y,number)
JS_GETPROP(js,info.threadcount_z,pipe,threadcount_z,number)
JS_GETPROP(js,info.entrypoint,pipe,entrypoint,cstring)
JSValue shader = JS_GetPropertyStr(js,pipe,"shader");
info.code = JS_GetArrayBuffer(js,&info.code_size, shader);
JS_FreeValue(js,shader);
SDL_GPUComputePipeline *pipeline = SDL_CreateGPUComputePipeline(gpu, &info);
JS_FreeCString(js,info.entrypoint);
if (!pipeline) return JS_ThrowReferenceError(js,"Could not create compute pipeline: %s", SDL_GetError());
return SDL_GPUComputePipeline2js(js,pipeline);
)
static inline void tile_region(text_vert **verts, rect src_uv, rect dst, float tex_w, float tex_h, bool tile_x, bool tile_y)
{
// Convert the incoming UV rect into pixel coords
rect src_px = {
.x = src_uv.x * tex_w,
.y = src_uv.y * tex_h,
.w = src_uv.w * tex_w,
.h = src_uv.h * tex_h
};
// If src_px or dst is degenerate, early out
if (src_px.w <= 0.0f || src_px.h <= 0.0f || dst.w <= 0.0f || dst.h <= 0.0f)
return;
// How many full tiles horizontally/vertically?
// If not tiling in a dimension, we treat it as exactly 1 tile (no leftover).
float cols_f, rows_f;
float remain_wf = 0.0f;
float remain_hf = 0.0f;
if (tile_x) {
remain_wf = modff(dst.w / src_px.w, &cols_f);
} else {
// Only 1 "column" covering entire width, no leftover
cols_f = 1.0f;
remain_wf = 0.0f;
}
if (tile_y) {
remain_hf = modff(dst.h / src_px.h, &rows_f);
} else {
// Only 1 "row" covering entire height, no leftover
rows_f = 1.0f;
remain_hf = 0.0f;
}
int cols = (int)cols_f;
int rows = (int)rows_f;
// The leftover portion in screen coords (pixels)
float remain_dst_w = remain_wf * src_px.w;
float remain_dst_h = remain_hf * src_px.h;
// Build the UV rect for a “full” tile
rect src_full = src_uv;
// Partial leftover in UV
float remain_src_w_uv = remain_dst_w / tex_w;
float remain_src_h_uv = remain_dst_h / tex_h;
// For partial leftover in X dimension
rect src_partial_x = src_full;
src_partial_x.w = remain_src_w_uv;
// For partial leftover in Y dimension
rect src_partial_y = src_full;
src_partial_y.h = remain_src_h_uv;
// For partial leftover in both X & Y
rect src_partial_xy = src_full;
src_partial_xy.w = remain_src_w_uv;
src_partial_xy.h = remain_src_h_uv;
// Each tile is drawn 1:1 in screen coords
float tile_w = tile_x ? src_px.w : dst.w; // If not tiling horizontally, match the entire dst width
float tile_h = tile_y ? src_px.h : dst.h; // If not tiling vertically, match the entire dst height
rect curr_dst;
curr_dst.w = tile_w;
curr_dst.h = tile_h;
curr_dst.y = dst.y;
// Loop over rows
for (int y = 0; y < rows; y++) {
curr_dst.x = dst.x;
// Loop over columns
for (int x = 0; x < cols; x++) {
add_quad(verts, &src_full, &curr_dst);
curr_dst.x += tile_w;
}
// Right-side leftover tile (only if tile_x is true)
if (tile_x && remain_dst_w > 0.0f) {
rect partial_dst = {
.x = curr_dst.x, .y = curr_dst.y,
.w = remain_dst_w, .h = tile_h
};
add_quad(verts, &src_partial_x, &partial_dst);
}
curr_dst.y += tile_h;
}
// Bottom leftover row (only if tile_y is true)
if (tile_y && remain_dst_h > 0.0f) {
rect partial_row_dst;
partial_row_dst.w = tile_w;
partial_row_dst.h = remain_dst_h;
partial_row_dst.y = curr_dst.y;
partial_row_dst.x = dst.x;
// Full columns in leftover row
for (int x = 0; x < cols; x++) {
add_quad(verts, &src_partial_y, &partial_row_dst);
partial_row_dst.x += tile_w;
}
// Partial leftover corner (both X & Y leftover)
if (tile_x && remain_dst_w > 0.0f) {
rect partial_corner_dst = {
.x = partial_row_dst.x, .y = partial_row_dst.y,
.w = remain_dst_w, .h = remain_dst_h
};
add_quad(verts, &src_partial_xy, &partial_corner_dst);
}
}
}
JSC_CCALL(gpu_slice9,
JSValue jstex = argv[0];
rect dst = js2rect(js, argv[1]);
// Full texture in UV coords
rect src = {
.x = 0, .y = 0,
.w = 1, .h = 1
};
// The “slice” LRTB in PIXELS, but we convert to UV below
lrtb src_slice = js2lrtb(js, argv[2]);
lrtb dst_slice = src_slice;
HMM_Vec2 size;
JS_GETPROP(js, size.x, jstex, width, number)
JS_GETPROP(js, size.y, jstex, height, number)
JSValue info = argv[3];
int tile_top, tile_bottom, tile_left, tile_right, center_x, center_y;
JS_GETPROP(js,tile_top, info, tile_top, bool)
JS_GETPROP(js,tile_bottom,info,tile_bottom,bool)
JS_GETPROP(js,tile_left,info,tile_left,bool)
JS_GETPROP(js,tile_right,info,tile_right,bool)
JS_GETPROP(js, center_x, info, tile_center_x, bool)
JS_GETPROP(js, center_y, info, tile_center_y, bool)
// Convert the slice edges from pixel to UV
src_slice.l /= size.x;
src_slice.r /= size.x;
src_slice.t /= size.y;
src_slice.b /= size.y;
text_vert *verts = NULL;
rect curr_src;
rect curr_dst;
// bottom-left corner (single quad)
curr_src = src;
curr_src.w = src_slice.l;
curr_src.h = src_slice.b;
curr_dst = dst;
curr_dst.w = dst_slice.l;
curr_dst.h = dst_slice.b;
add_quad(&verts, &curr_src, &curr_dst);
// top-left corner (single quad)
curr_src = src;
curr_src.x = src.x;
curr_src.y = src.y + src.h - src_slice.t;
curr_src.w = src_slice.l;
curr_src.h = src_slice.t;
curr_dst = dst;
curr_dst.x = dst.x;
curr_dst.y = dst.y + dst.h - dst_slice.t;
curr_dst.w = dst_slice.l;
curr_dst.h = dst_slice.t;
add_quad(&verts, &curr_src, &curr_dst);
// bottom-right corner (single quad)
curr_src = src;
curr_src.x = src.x + src.w - src_slice.r;
curr_src.y = src.y;
curr_src.w = src_slice.r;
curr_src.h = src_slice.b;
curr_dst = dst;
curr_dst.x = dst.x + dst.w - dst_slice.r;
curr_dst.y = dst.y;
curr_dst.w = dst_slice.r;
curr_dst.h = dst_slice.b;
add_quad(&verts, &curr_src, &curr_dst);
// top-right corner (single quad)
curr_src = src;
curr_src.x = src.x + src.w - src_slice.r;
curr_src.y = src.y + src.h - src_slice.t;
curr_src.w = src_slice.r;
curr_src.h = src_slice.t;
curr_dst = dst;
curr_dst.x = dst.x + dst.w - dst_slice.r;
curr_dst.y = dst.y + dst.h - dst_slice.t;
curr_dst.w = dst_slice.r;
curr_dst.h = dst_slice.t;
add_quad(&verts, &curr_src, &curr_dst);
// left bar (tiled)
curr_src = src;
curr_src.x = src.x;
curr_src.y = src.y + src_slice.b;
curr_src.w = src_slice.l;
curr_src.h = src.h - src_slice.t - src_slice.b;
curr_dst = dst;
curr_dst.x = dst.x;
curr_dst.y = dst.y + dst_slice.b;
curr_dst.w = dst_slice.l;
curr_dst.h = dst.h - dst_slice.t - dst_slice.b;
tile_region(&verts, curr_src, curr_dst, size.x, size.y,tile_left,tile_left);
// right bar (tiled)
curr_src = src;
curr_src.x = src.x + src.w - src_slice.r;
curr_src.y = src.y + src_slice.b;
curr_src.w = src_slice.r;
curr_src.h = src.h - src_slice.t - src_slice.b;
curr_dst = dst;
curr_dst.x = dst.x + dst.w - dst_slice.r;
curr_dst.y = dst.y + dst_slice.b;
curr_dst.w = dst_slice.r;
curr_dst.h = dst.h - dst_slice.t - dst_slice.b;
tile_region(&verts, curr_src, curr_dst, size.x, size.y,tile_right,tile_right);
// bottom bar (tiled)
curr_src = src;
curr_src.x = src.x + src_slice.l;
curr_src.y = src.y;
curr_src.w = src.w - src_slice.l - src_slice.r;
curr_src.h = src_slice.b;
curr_dst = dst;
curr_dst.x = dst.x + dst_slice.l;
curr_dst.y = dst.y;
curr_dst.w = dst.w - dst_slice.l - dst_slice.r;
curr_dst.h = dst_slice.b;
tile_region(&verts, curr_src, curr_dst, size.x, size.y,tile_bottom,tile_bottom);
// top bar (tiled)
curr_src = src;
curr_src.x = src.x + src_slice.l;
curr_src.y = src.y + src.h - src_slice.t;
curr_src.w = src.w - src_slice.l - src_slice.r;
curr_src.h = src_slice.t;
curr_dst = dst;
curr_dst.x = dst.x + dst_slice.l;
curr_dst.y = dst.y + dst.h - dst_slice.t;
curr_dst.w = dst.w - dst_slice.l - dst_slice.r;
curr_dst.h = dst_slice.t;
tile_region(&verts, curr_src, curr_dst, size.x, size.y,tile_top,tile_top);
// center (tiled)
curr_src = src;
curr_src.x = src.x + src_slice.l;
curr_src.y = src.y + src_slice.b;
curr_src.w = src.w - src_slice.l - src_slice.r;
curr_src.h = src.h - src_slice.t - src_slice.b;
curr_dst = dst;
curr_dst.x = dst.x + dst_slice.l;
curr_dst.y = dst.y + dst_slice.b;
curr_dst.w = dst.w - dst_slice.l - dst_slice.r;
curr_dst.h = dst.h - dst_slice.t - dst_slice.b;
tile_region(&verts, curr_src, curr_dst, size.x, size.y, center_x,center_y);
JSValue mesh = quads_to_mesh(js, verts);
arrfree(verts);
ret = mesh;
)
JSC_CCALL(gpu_tile,
HMM_Vec2 size;
JSValue jstex = argv[0];
JS_GETATOM(js,size.x,jstex,width_atom,number)
JS_GETATOM(js, size.y, jstex, height_atom, number)
rect src_pixels = js2rect(js, argv[1]); // 'src' as pixel dimensions
rect dst = js2rect(js, argv[2]); // 'dst' as screen coords
int tilex, tiley;
JSValue jstile = argv[3];
JS_GETPROP(js,tilex,jstile,repeat_x,bool)
JS_GETPROP(js,tiley,jstile,repeat_y,bool)
text_vert *verts = NULL;
tile_region(&verts, src_pixels, dst, size.x, size.y,tilex,tiley);
ret = quads_to_mesh(js,verts);
arrfree(verts);
)
static const JSCFunctionListEntry js_SDL_GPUCopyPass_funcs[] = {};
static const JSCFunctionListEntry js_SDL_GPUFence_funcs[] = {};
static const JSCFunctionListEntry js_SDL_GPUTransferBuffer_funcs[] = {};
static const JSCFunctionListEntry js_SDL_GPUShader_funcs[] = {};
static const JSCFunctionListEntry js_SDL_GPUSampler_funcs[] = {};
static const JSCFunctionListEntry js_SDL_GPUGraphicsPipeline_funcs[] = {};
static const JSCFunctionListEntry js_SDL_GPUComputePipeline_funcs[] = {};
static const JSCFunctionListEntry js_SDL_GPUDevice_funcs[] = {
MIST_FUNC_DEF(gpu, claim_window, 1),
MIST_FUNC_DEF(gpu, make_pipeline, 1), // loads pipeline state into an object
MIST_FUNC_DEF(gpu,compute_pipeline,1),
MIST_FUNC_DEF(gpu, set_swapchain, 2),
MIST_FUNC_DEF(gpu,sort_sprite,2),
MIST_FUNC_DEF(gpu, make_sampler,1),
MIST_FUNC_DEF(gpu, load_texture, 2),
MIST_FUNC_DEF(gpu, texture, 1),
MIST_FUNC_DEF(gpu, make_quad, 0),
MIST_FUNC_DEF(gpu, driver, 0),
MIST_FUNC_DEF(gpu, make_shader, 1),
MIST_FUNC_DEF(gpu, acquire_cmd_buffer, 0),
MIST_FUNC_DEF(gpu, upload, 3),
MIST_FUNC_DEF(gpu, wait_for_fences, 2),
MIST_FUNC_DEF(gpu, query_fence, 1),
MIST_FUNC_DEF(gpu, shader_format, 0),
MIST_FUNC_DEF(gpu, slice9, 3),
MIST_FUNC_DEF(gpu, tile, 4),
};
JSC_CCALL(renderpass_bind_pipeline,
SDL_GPURenderPass *r = js2SDL_GPURenderPass(js,self);
SDL_GPUGraphicsPipeline *pipe = js2SDL_GPUGraphicsPipeline(js,argv[0]);
SDL_BindGPUGraphicsPipeline(r,pipe);
)
JSC_CCALL(renderpass_draw_indexed,
SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
SDL_DrawGPUIndexedPrimitives(pass, js2number(js,argv[0]), js2number(js,argv[1]), js2number(js,argv[2]), js2number(js,argv[3]), js2number(js,argv[4]));
)
JSC_CCALL(renderpass_draw,
SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
SDL_DrawGPUPrimitives(pass, js2number(js,argv[0]), js2number(js,argv[1]), js2number(js,argv[2]), js2number(js,argv[3]));
)
JSC_CCALL(renderpass_bind_buffers,
SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
int first = js2number(js,argv[0]);
JSValue buffers = argv[1];
int len = js_arrlen(js,buffers);
SDL_GPUBufferBinding bindings[len];
for (int i = 0; i < len; i++) {
JSValue buffer = JS_GetPropertyUint32(js,buffers,i);
bindings[i].offset = 0;
gpu_buffer_unpack(js,global_gpu, buffer, NULL, NULL,&bindings[i].buffer);
JS_FreeValue(js,buffer);
}
SDL_BindGPUVertexBuffers(
pass,
first,
bindings,
len);
)
JSC_CCALL(renderpass_bind_index_buffer,
SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
SDL_GPUBufferBinding bind;
bind.offset = 0;
gpu_buffer_unpack(js,global_gpu,argv[0], NULL, NULL, &bind.buffer);
int elen;
JSValue b = argv[0];
JS_GETPROP(js, elen, b, elen, number)
SDL_BindGPUIndexBuffer(pass,&bind,elen == 2 ? SDL_GPU_INDEXELEMENTSIZE_16BIT : SDL_GPU_INDEXELEMENTSIZE_32BIT);
)
JSC_CCALL(renderpass_end,
SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
SDL_EndGPURenderPass(pass);
)
JSC_CCALL(renderpass_bind_samplers,
SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
int first_slot = js2number(js,argv[1]);
JSValue arr = argv[2];
int num = js_arrlen(js,arr);
SDL_GPUTextureSamplerBinding binds[num];
for (int i = 0; i < num; i++) {
JSValue val = JS_GetPropertyUint32(js,arr,i);
JSValue tex = JS_GetPropertyStr(js,val,"texture");
JSValue smp = JS_GetPropertyStr(js,val,"sampler");
binds[i].texture = js2SDL_GPUTexture(js,tex);
binds[i].sampler = js2SDL_GPUSampler(js,smp);
JS_FreeValue(js,tex);
JS_FreeValue(js,smp);
JS_FreeValue(js,val);
}
int vertex = JS_ToBool(js,argv[0]);
if (vertex)
SDL_BindGPUVertexSamplers(pass, first_slot, binds, num);
else
SDL_BindGPUFragmentSamplers(pass, first_slot, binds, num);
)
JSC_CCALL(renderpass_bind_storage_buffers,
)
JSC_CCALL(renderpass_bind_storage_textures,
)
JSC_CCALL(renderpass_viewport,
SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
rect r = js2rect(js,argv[0]);
SDL_GPUViewport vp ={0};
vp.x = r.x;
vp.y = r.y;
vp.w = r.w;
vp.h = r.h;
vp.min_depth = 0;
vp.max_depth = 1;
SDL_SetGPUViewport(pass,&vp);
)
JSC_CCALL(renderpass_scissor,
SDL_GPURenderPass *pass = js2SDL_GPURenderPass(js,self);
rect r = js2rect(js,argv[0]);
SDL_Rect rr;
rr.x = r.x;
rr.y = r.y;
rr.w = r.w;
rr.h = r.h;
SDL_SetGPUScissor(pass,&rr);
)
static const JSCFunctionListEntry js_SDL_GPURenderPass_funcs[] = {
MIST_FUNC_DEF(renderpass, bind_pipeline, 1),
MIST_FUNC_DEF(renderpass, viewport, 1),
MIST_FUNC_DEF(renderpass, scissor, 1),
MIST_FUNC_DEF(renderpass, draw, 4),
MIST_FUNC_DEF(renderpass, draw_indexed, 5),
MIST_FUNC_DEF(renderpass, end, 0),
MIST_FUNC_DEF(renderpass, bind_index_buffer, 1),
MIST_FUNC_DEF(renderpass, bind_buffers, 2),
MIST_FUNC_DEF(renderpass, bind_samplers, 3),
MIST_FUNC_DEF(renderpass, bind_storage_buffers, 2),
MIST_FUNC_DEF(renderpass, bind_storage_textures, 2),
};
JSC_CCALL(cmd_render_pass,
SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
if (!JS_IsObject(argv[0])) return JS_ThrowTypeError(js, "render_pass: Expected a render pass descriptor object");
JSValue passObj = argv[0];
JSValue colorTargetsVal = JS_GetPropertyStr(js, passObj, "color_targets");
if (!JS_IsArray(js, colorTargetsVal))
return JS_ThrowTypeError(js, "render_pass: colorTargets must be an array");
uint32_t colorCount = js_arrlen(js, colorTargetsVal);
SDL_GPUColorTargetInfo colortars[colorCount] = {};
SDL_GPUDepthStencilTargetInfo depthtar;
int has_depth = 0;
// Fill colorInfos from JS array
for (uint32_t i = 0; i < colorCount; i++) {
JSValue ctargetVal = JS_GetPropertyUint32(js, colorTargetsVal, i);
colortars[i] = js2SDL_GPUColorTargetInfo(js,ctargetVal);
JS_FreeValue(js, ctargetVal);
}
// Optional depth_stencil
JSValue depthval = JS_GetPropertyStr(js, passObj, "depth_stencil");
if (!JS_IsUndefined(depthval)) {
has_depth = 1;
JS_GETPROP(js, depthtar.texture, depthval, texture, SDL_GPUTexture)
JS_GETPROP(js, depthtar.load_op, depthval, load, SDL_GPULoadOp)
JS_GETPROP(js, depthtar.store_op, depthval, store, SDL_GPUStoreOp)
JS_GETPROP(js, depthtar.stencil_load_op, depthval, stencil_load, SDL_GPULoadOp)
JS_GETPROP(js, depthtar.stencil_store_op, depthval, stencil_store, SDL_GPUStoreOp)
JS_GETPROP(js,depthtar.clear_depth, depthval, clear, number)
JS_GETPROP(js,depthtar.clear_stencil,depthval,clear_stencil,number)
}
JS_FreeValue(js, depthval);
SDL_GPURenderPass *pass = SDL_BeginGPURenderPass(
cmds,
colortars,
colorCount,
has_depth ? &depthtar : NULL
);
JS_FreeValue(js, colorTargetsVal);
if (!pass) return JS_ThrowInternalError(js, "render_pass: Failed to begin render pass");
return SDL_GPURenderPass2js(js, pass);
)
JSC_CCALL(cmd_bind_vertex_buffer,
SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
int slot;
JS_ToInt32(js, &slot, argv[0]);
SDL_GPUBuffer *buffer = js2SDL_GPUBuffer(js, argv[1]);
size_t offset = 0;
if (argc > 2) JS_ToIndex(js, &offset, argv[2]);
SDL_BindGPUVertexBuffers(cmds, slot, &(SDL_GPUBufferBinding){.buffer = buffer, .offset = offset}, 1);
)
JSC_CCALL(cmd_bind_index_buffer,
SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
SDL_GPUBuffer *buffer = js2SDL_GPUBuffer(js, argv[0]);
size_t offset = 0;
if (argc > 1) JS_ToIndex(js, &offset, argv[1]);
SDL_BindGPUIndexBuffer(cmds, &(SDL_GPUBufferBinding){.buffer = buffer, .offset = offset}, SDL_GPU_INDEXELEMENTSIZE_16BIT);
)
JSC_CCALL(cmd_bind_fragment_sampler,
SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
int slot;
JS_ToInt32(js, &slot, argv[0]);
SDL_GPUTexture *tex = js2SDL_GPUTexture(js, argv[1]);
SDL_GPUSampler *sampler = js2SDL_GPUSampler(js, argv[2]);
SDL_BindGPUFragmentSamplers(cmds, slot, &(SDL_GPUTextureSamplerBinding){.texture = tex, .sampler = sampler}, 1);
)
JSC_CCALL(cmd_push_vertex_uniform_data,
SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
int slot;
JS_ToInt32(js, &slot, argv[0]);
size_t buf_size;
void *data = JS_GetArrayBuffer(js, &buf_size, argv[1]);
SDL_PushGPUVertexUniformData(cmds, slot, data, buf_size);
)
JSC_CCALL(cmd_push_fragment_uniform_data,
SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
int slot;
JS_ToInt32(js, &slot, argv[0]);
size_t buf_size;
void *data = JS_GetArrayBuffer(js, &buf_size, argv[1]);
SDL_PushGPUFragmentUniformData(cmds, slot, data, buf_size);
)
JSC_CCALL(cmd_push_compute_uniform_data,
SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
int slot;
JS_ToInt32(js, &slot, argv[0]);
size_t buf_size;
void *data = JS_GetArrayBuffer(js, &buf_size, argv[1]);
SDL_PushGPUComputeUniformData(cmds, slot, data, buf_size);
)
JSC_CCALL(cmd_submit,
SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js,self);
SDL_GPUFence *fence = SDL_SubmitGPUCommandBufferAndAcquireFence(cmds);
return SDL_GPUFence2js(js,fence);
)
JSC_CCALL(cmd_hud,
SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js,self);
HMM_Vec2 size = js2vec2(js,argv[0]);
HMM_Mat4 proj = HMM_Orthographic_RH_NO(0,size.x,0,size.y,-1,1);
shader_globals data = {0};
data.world_to_projection = proj;
data.projection_to_world = HMM_InvGeneralM4(proj);
data.viewport_min_z = -1,
data.viewport_max_z = 1;
data.render_size = size;
data.view_to_projection = proj;
data.viewport_size = (HMM_Vec2){1,1};
data.viewport_offset = (HMM_Vec2){0,0};
data.time = SDL_GetTicksNS() / 1000000000.0f;
SDL_PushGPUVertexUniformData(cmds, js2number(js,argv[1]), &data, sizeof(data));
)
JSC_CCALL(cmd_camera,
SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self);
shader_globals data = camera_globals(js, argv[0]);
SDL_PushGPUVertexUniformData(cmds, js2number(js,argv[1]), &data, sizeof(data));
)
JSC_SCALL(cmd_push_debug_group,
SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self);
SDL_PushGPUDebugGroup(cmd,str);
)
JSC_CCALL(cmd_pop_debug_group,
SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self);
SDL_PopGPUDebugGroup(cmd);
)
JSC_SCALL(cmd_debug_label,
SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self);
SDL_InsertGPUDebugLabel(cmd, str);
)
SDL_GPUBlitRegion js2SDL_GPUBlitRegion(JSContext *js, JSValue v)
{
SDL_GPUBlitRegion info = {0};
if (JS_GetClassID(v) == js_SDL_GPUTexture_id) {
// texture path
JSValue tex = v;
info.texture = js2SDL_GPUTexture(js,tex);
info.mip_level = 0,
info.layer_or_depth_plane = 0;
info.x = 0;
info.y = 0;
JS_GETPROP(js,info.w,tex,width,number)
JS_GETPROP(js,info.h,tex,height,number)
return info;
}
JSValue tex = JS_GetPropertyStr(js, v, "texture");
info.texture = js2SDL_GPUTexture(js,tex);
JS_GETPROP(js,info.mip_level,v,mip_level, number)
JS_GETPROP(js,info.mip_level,v,layer,number)
JS_GETPROP(js,info.x,v,x,number)
JS_GETPROP(js,info.y,v,y,number)
JS_GETPROP(js,info.w,v,width,number)
JS_GETPROP(js,info.h,v,height,number)
if (!info.w) JS_GETPROP(js,info.w,tex,width,number)
if (!info.h) JS_GETPROP(js,info.h,tex,height,number)
JS_FreeValue(js,tex);
return info;
}
JSC_CCALL(cmd_blit,
SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self);
SDL_GPUBlitInfo info = {0};
JSValue v = argv[0];
JS_GETPROP(js,info.source,v,src,SDL_GPUBlitRegion)
JS_GETPROP(js,info.destination,v,dst,SDL_GPUBlitRegion)
JS_GETPROP(js,info.load_op,v,load,SDL_GPULoadOp)
JS_GETPROP(js,info.flip_mode,v,flip,SDL_FlipMode)
JS_GETPROP(js,info.filter,v,filter,SDL_GPUFilter)
JS_GETPROP(js,info.clear_color,v,color,SDL_FColor)
SDL_BlitGPUTexture(cmd,&info);
)
JSC_CCALL(cmd_cancel,
SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self);
SDL_CancelGPUCommandBuffer(cmd);
)
JSC_CCALL(cmd_compute_pass,
SDL_GPUCommandBuffer *cmd = js2SDL_GPUCommandBuffer(js,self);
JSValue textures = argv[0];
JSValue buffers = argv[1];
int t_n = js_arrlen(js,textures);
SDL_GPUStorageTextureReadWriteBinding t_bind[t_n];
for (int i = 0; i < t_n; i++) {
JSValue T = JS_GetPropertyUint32(js,textures,i);
JS_GETPROP(js, t_bind[i].texture, T, texture, SDL_GPUTexture)
JS_GETPROP(js,t_bind[i].mip_level,T,mip, number)
JS_GETPROP(js,t_bind[i].layer,T,layer, number)
JS_FreeValue(js,T);
}
int b_n = js_arrlen(js,buffers);
SDL_GPUStorageBufferReadWriteBinding b_bind[b_n];
for (int i = 0; i < b_n; i++) {
JSValue T = JS_GetPropertyUint32(js,buffers,i);
JS_GETPROP(js,b_bind[i].buffer, T,buffer,SDL_GPUBuffer)
}
SDL_GPUComputePass *pass = SDL_BeginGPUComputePass(cmd,t_bind,t_n,b_bind,b_n);
if (!pass) return JS_ThrowReferenceError(js, "Unable to begin compute pass: %s", SDL_GetError());
return SDL_GPUComputePass2js(js,pass);
)
static const JSCFunctionListEntry js_SDL_GPUCommandBuffer_funcs[] = {
MIST_FUNC_DEF(cmd, render_pass, 1),
MIST_FUNC_DEF(cmd, compute_pass, 2),
MIST_FUNC_DEF(cmd, swapchain_pass, 1),
MIST_FUNC_DEF(cmd, acquire_swapchain,0),
MIST_FUNC_DEF(cmd, bind_vertex_buffer, 2),
MIST_FUNC_DEF(cmd, bind_index_buffer, 1),
MIST_FUNC_DEF(cmd, bind_fragment_sampler, 3),
MIST_FUNC_DEF(cmd, push_vertex_uniform_data, 2),
MIST_FUNC_DEF(cmd, push_fragment_uniform_data, 2),
MIST_FUNC_DEF(cmd, push_compute_uniform_data, 2),
MIST_FUNC_DEF(cmd, submit, 0),
MIST_FUNC_DEF(cmd, cancel, 0),
MIST_FUNC_DEF(cmd, camera, 2),
MIST_FUNC_DEF(cmd, hud, 2),
MIST_FUNC_DEF(cmd, push_debug_group, 1),
MIST_FUNC_DEF(cmd, pop_debug_group, 0),
MIST_FUNC_DEF(cmd, debug_label, 1),
MIST_FUNC_DEF(cmd, blit, 1),
};
JSC_SCALL(buffer_name,
SDL_GPUBuffer *buffer = js2SDL_GPUBuffer(js,self);
SDL_GPUDevice *gpu;
JS_GETPROP(js, gpu, self, gpu, SDL_GPUDevice)
SDL_SetGPUBufferName(gpu,buffer,str);
)
static const JSCFunctionListEntry js_SDL_GPUBuffer_funcs[] = {
MIST_FUNC_DEF(buffer, name, 1),
};
JSC_CCALL(compute_dispatch,
SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self);
SDL_DispatchGPUCompute(pass,js2number(js,argv[0]), js2number(js,argv[1]), js2number(js,argv[2]));
)
JSC_CCALL(compute_end,
SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self);
SDL_EndGPUComputePass(pass);
)
JSC_CCALL(compute_pipeline,
SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self);
SDL_GPUComputePipeline *pipeline = js2SDL_GPUComputePipeline(js,argv[0]);
SDL_BindGPUComputePipeline(pass,pipeline);
)
JSC_CCALL(compute_samplers,
SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self);
JSValue samplers = argv[0];
int n = js_arrlen(js,samplers);
SDL_GPUTextureSamplerBinding b[n];
for (int i = 0; i < n; i++) {
JSValue s = JS_GetPropertyUint32(js,samplers,i);
b[i] = js2SDL_GPUTextureSamplerBinding(js,s);
JS_FreeValue(js,s);
}
SDL_BindGPUComputeSamplers(pass,js2number(js,argv[1]),b,n);
)
JSC_CCALL(compute_storage_textures,
SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self);
JSValue textures = argv[0];
int n = js_arrlen(js,textures);
SDL_GPUTexture *b[n];
for (int i = 0; i < n; i++) {
JSValue s = JS_GetPropertyUint32(js,textures,i);
b[i] = js2SDL_GPUTexture(js,s);
JS_FreeValue(js,s);
}
SDL_BindGPUComputeStorageTextures(pass,js2number(js,argv[1]),b,n);
)
JSC_CCALL(compute_storage_buffers,
SDL_GPUComputePass *pass = js2SDL_GPUComputePass(js,self);
JSValue buffers = argv[0];
int n = js_arrlen(js,buffers);
SDL_GPUBuffer *b[n];
for (int i = 0; i < n; i++) {
JSValue s = JS_GetPropertyUint32(js,buffers,i);
b[i] = js2SDL_GPUBuffer(js,s);
JS_FreeValue(js,s);
}
SDL_BindGPUComputeStorageTextures(pass,js2number(js,argv[1]),b,n);
)
static const JSCFunctionListEntry js_SDL_GPUComputePass_funcs[] = {
MIST_FUNC_DEF(compute, dispatch, 3),
MIST_FUNC_DEF(compute, end, 0),
MIST_FUNC_DEF(compute, pipeline, 1),
MIST_FUNC_DEF(compute, samplers, 2),
MIST_FUNC_DEF(compute, storage_buffers, 2),
MIST_FUNC_DEF(compute, storage_textures, 2),
};
JSC_CCALL(surface_blit,
SDL_Surface *dst = js2SDL_Surface(js,self);
rect dstrect = js2rect(js,argv[0]);
SDL_Surface *src = js2SDL_Surface(js,argv[1]);
rect srcrect = js2rect(js,argv[2]);
SDL_BlitSurfaceScaled(src, &srcrect, dst, &dstrect, SDL_SCALEMODE_LINEAR);
)
JSC_CCALL(surface_scale,
SDL_Surface *src = js2SDL_Surface(js,self);
HMM_Vec2 wh = js2vec2(js,argv[0]);
SDL_Surface *new = SDL_CreateSurface(wh.x,wh.y, SDL_PIXELFORMAT_RGBA32);
SDL_BlitSurfaceScaled(src, NULL, new, NULL, SDL_SCALEMODE_LINEAR);
ret = SDL_Surface2js(js,new);
)
static SDL_PixelFormatDetails pdetails = {
.format = SDL_PIXELFORMAT_RGBA8888, // Standard RGBA8888 format
.bits_per_pixel = 32, // 8 bits per channel, 4 channels = 32 bits
.bytes_per_pixel = 4, // 4 bytes per pixel
.padding = {0, 0}, // Unused padding
.Rmask = 0xFF000000, // Red mask
.Gmask = 0x00FF0000, // Green mask
.Bmask = 0x0000FF00, // Blue mask
.Amask = 0x000000FF, // Alpha mask
.Rbits = 8, // 8 bits for Red
.Gbits = 8, // 8 bits for Green
.Bbits = 8, // 8 bits for Blue
.Abits = 8, // 8 bits for Alpha
.Rshift = 24, // Red shift
.Gshift = 16, // Green shift
.Bshift = 8, // Blue shift
.Ashift = 0 // Alpha shift
};
JSC_CCALL(surface_fill,
SDL_Surface *src = js2SDL_Surface(js,self);
colorf color = js2color(js,argv[0]);
rect r = {
.x = 0,
.y = 0,
.w = src->w,
.h = src->h
};
SDL_FillSurfaceRect(src, &r, SDL_MapRGBA(&pdetails, NULL, color.r*255,color.g*255,color.b*255,color.a*255));
)
JSC_CCALL(surface_rect,
SDL_Surface *dst = js2SDL_Surface(js,self);
rect r = js2rect(js,argv[0]);
colorf color = js2color(js,argv[1]);
SDL_FillSurfaceRect(dst,&r,SDL_MapRGBA(&pdetails,NULL, color.r*255,color.g*255,color.b*255,color.a*255));
)
JSC_CCALL(surface_dup,
SDL_Surface *surf = js2SDL_Surface(js,self);
SDL_Surface *conv = SDL_ConvertSurface(surf, SDL_PIXELFORMAT_RGBA8888);
if (!conv)
return JS_ThrowReferenceError(js, "could not blit to dup'd surface: %s", SDL_GetError());
return SDL_Surface2js(js,conv);
)
static const JSCFunctionListEntry js_SDL_Surface_funcs[] = {
MIST_FUNC_DEF(surface, blit, 3),
MIST_FUNC_DEF(surface, scale, 1),
MIST_FUNC_DEF(surface,fill,1),
MIST_FUNC_DEF(surface,rect,2),
MIST_FUNC_DEF(surface, dup, 0),
};
JSC_CCALL(thread_wait,
SDL_Thread *th = js2SDL_Thread(js,self);
SDL_WaitThread(th, NULL);
)
static const JSCFunctionListEntry js_SDL_Thread_funcs[] = {
MIST_FUNC_DEF(thread,wait,0),
};
JSC_CCALL(camera_frame,
SDL_ClearError();
SDL_Camera *cam = js2SDL_Camera(js,self);
if (!cam) return JS_ThrowReferenceError(js,"Self was not a camera: %s", SDL_GetError());
SDL_Surface *surf = SDL_AcquireCameraFrame(cam, NULL);
if (!surf) {
const char *msg = SDL_GetError();
if (msg[0] != 0)
return JS_ThrowReferenceError(js,"Could not get camera frame: %s", SDL_GetError());
else return JS_UNDEFINED;
}
return SDL_Surface2js(js,surf);
SDL_Surface *newsurf = SDL_CreateSurface(surf->w, surf->h, surf->format);
SDL_ReleaseCameraFrame(cam,surf);
int didit = SDL_BlitSurface(surf, NULL, newsurf, NULL);
if (!didit) {
SDL_DestroySurface(newsurf);
return JS_ThrowReferenceError(js, "Could not blit: %s", SDL_GetError());
}
return SDL_Surface2js(js,newsurf);
)
JSC_CCALL(camera_release_frame,
SDL_Camera *cam = js2SDL_Camera(js,self);
SDL_Surface *surf = js2SDL_Surface(js,argv[0]);
SDL_ReleaseCameraFrame(cam,surf);
)
static const JSCFunctionListEntry js_SDL_Camera_funcs[] =
{
MIST_FUNC_DEF(camera, frame, 0),
MIST_FUNC_DEF(camera, release_frame, 1),
};
static const JSCFunctionListEntry js_SDL_Cursor_funcs[] = {};
JSC_CCALL(texture_mode,
SDL_Texture *tex = js2SDL_Texture(js,self);
SDL_SetTextureScaleMode(tex,js2number(js,argv[0]));
)
static const JSCFunctionListEntry js_SDL_Texture_funcs[] = {
MIST_FUNC_DEF(texture, mode, 1),
};
JSC_SCALL(texture_name,
SDL_GPUTexture *texture = js2SDL_GPUTexture(js,self);
SDL_GPUDevice *gpu;
JS_GETPROP(js,gpu,self,gpu,SDL_GPUDevice)
SDL_SetGPUTextureName(gpu,texture,str);
)
static const JSCFunctionListEntry js_SDL_GPUTexture_funcs[] = {
MIST_FUNC_DEF(texture, name, 1),
};
JSC_CCALL(input_mouse_lock, SDL_CaptureMouse(JS_ToBool(js,argv[0])))
JSC_CCALL(input_mouse_show,
if (JS_ToBool(js,argv[0]))
SDL_ShowCursor();
else
SDL_HideCursor();
)
JSC_CCALL(input_cursor_set,
SDL_Cursor *c = js2SDL_Cursor(js,argv[0]);
if (!SDL_SetCursor(c))
return JS_ThrowReferenceError(js, "could not set cursor: %s", SDL_GetError());
)
JSC_CCALL(input_keyname,
return JS_NewString(js, SDL_GetKeyName(js2number(js,argv[0])));
)
JSC_CCALL(input_keymod,
return js_keymod(js);
)
static const JSCFunctionListEntry js_input_funcs[] = {
MIST_FUNC_DEF(input, mouse_show, 1),
MIST_FUNC_DEF(input, mouse_lock, 1),
MIST_FUNC_DEF(input, cursor_set, 1),
MIST_FUNC_DEF(input, keyname, 1),
MIST_FUNC_DEF(input, keymod, 0),
};
JSC_CCALL(os_guid,
char guid[33];
for (int i = 0; i < 4; i++) {
int r = rand();
for (int j = 0; j < 8; j++) {
guid[i*8+j] = "0123456789abcdef"[r%16];
r /= 16;
}
}
guid[32] = 0;
return JS_NewString(js,guid);
)
JSC_SCALL(os_openurl,
if (!SDL_OpenURL(str))
ret = JS_ThrowReferenceError(js, "unable to open url %s: %s\n", str, SDL_GetError());
)
JSC_CCALL(time_now,
struct timeval ct;
gettimeofday(&ct, NULL);
return number2js(js,(double)ct.tv_sec+(double)(ct.tv_usec/1000000.0));
)
JSValue js_time_computer_dst(JSContext *js, JSValue self) {
time_t t = time(NULL);
return JS_NewBool(js,localtime(&t)->tm_isdst);
}
JSValue js_time_computer_zone(JSContext *js, JSValue self) {
time_t t = time(NULL);
time_t local_t = mktime(localtime(&t));
double diff = difftime(t, local_t);
return number2js(js,diff/3600);
}
static const JSCFunctionListEntry js_time_funcs[] = {
MIST_FUNC_DEF(time, now, 0),
MIST_FUNC_DEF(time, computer_dst, 0),
MIST_FUNC_DEF(time, computer_zone, 0)
};
JSC_SCALL(console_print,
printf("%s", str);
)
static const JSCFunctionListEntry js_console_funcs[] = {
MIST_FUNC_DEF(console,print,1),
};
JSC_CCALL(profile_gather_rate,
JS_SetInterruptRate(js2number(js,argv[0]));
)
JSC_CCALL(profile_gather_stop,
JS_SetInterruptHandler(JS_GetRuntime(js),NULL,NULL);
)
JSC_CCALL(profile_best_t,
char* result[50];
double seconds = js2number(js,argv[0]);
if (seconds < 1e-6)
snprintf(result, 50, "%.2f ns", seconds * 1e9);
else if (seconds < 1e-3)
snprintf(result, 50, "%.2f µs", seconds * 1e6);
else if (seconds < 1)
snprintf(result, 50, "%.2f ms", seconds * 1e3);
else
snprintf(result, 50, "%.2f s", seconds);
return JS_NewString(js,result);
)
static const JSCFunctionListEntry js_profile_funcs[] = {
MIST_FUNC_DEF(profile,best_t, 1),
MIST_FUNC_DEF(profile,gather_rate,1),
MIST_FUNC_DEF(profile,gather_stop,0),
};
JSC_CCALL(debug_stack_depth, return number2js(js,js_debugger_stack_depth(js)))
JSC_CCALL(debug_build_backtrace, return js_debugger_build_backtrace(js,NULL))
JSC_CCALL(debug_closure_vars, return js_debugger_closure_variables(js,argv[0]))
JSC_CCALL(debug_local_vars, return js_debugger_local_variables(js, js2number(js,argv[0])))
JSC_CCALL(debug_fn_info, return js_debugger_fn_info(js, argv[0]));
JSC_CCALL(debug_backtrace_fns, return js_debugger_backtrace_fns(js,NULL));
JSC_CCALL(debug_dump_obj, return js_dump_value(js, argv[0]));
static const JSCFunctionListEntry js_debug_funcs[] = {
MIST_FUNC_DEF(debug, stack_depth, 0),
MIST_FUNC_DEF(debug, build_backtrace, 0),
MIST_FUNC_DEF(debug, closure_vars, 1),
MIST_FUNC_DEF(debug, local_vars, 1),
MIST_FUNC_DEF(debug, fn_info, 1),
MIST_FUNC_DEF(debug, backtrace_fns,0),
MIST_FUNC_DEF(debug, dump_obj, 1),
};
JSC_SCALL(io_rm,
if (!PHYSFS_delete(str)) ret = JS_ThrowReferenceError(js,"could not remove %s. %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
JSC_SCALL(io_mkdir,
if (!PHYSFS_mkdir(str)) ret = JS_ThrowReferenceError(js,"could not make directory %s. %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));)
JSC_SCALL(io_exists, ret = JS_NewBool(js,PHYSFS_exists(str)); )
JSC_SCALL(io_stat,
PHYSFS_Stat stat;
if (!PHYSFS_stat(str, &stat))
return JS_ThrowReferenceError(js, "%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
ret = JS_NewObject(js);
JS_SetPropertyStr(js,ret,"filesize", number2js(js,stat.filesize));
JS_SetPropertyStr(js,ret,"modtime", number2js(js,stat.modtime));
JS_SetPropertyStr(js,ret,"createtime", number2js(js,stat.createtime));
JS_SetPropertyStr(js,ret,"accesstime", number2js(js,stat.accesstime));
)
JSC_SCALL(io_slurpbytes,
PHYSFS_File *f = PHYSFS_openRead(str);
if (!f) {
ret = JS_ThrowReferenceError(js,"physfs error when slurping %s: %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
goto END;
}
PHYSFS_Stat stat;
PHYSFS_stat(str,&stat);
void *data = malloc(stat.filesize);
PHYSFS_readBytes(f,data,stat.filesize);
PHYSFS_close(f);
ret = JS_NewArrayBufferCopy(js,data,stat.filesize);
END:
)
JSC_SCALL(io_slurp,
PHYSFS_File *f = PHYSFS_openRead(str);
if (!f) {
ret = JS_ThrowReferenceError(js,"physfs error when slurping %s: %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
goto END;
}
PHYSFS_Stat stat;
PHYSFS_stat(str,&stat);
void *data = malloc(stat.filesize);
PHYSFS_readBytes(f,data,stat.filesize);
PHYSFS_close(f);
ret = JS_NewStringLen(js,data, stat.filesize);
END:
)
JSC_SCALL(io_slurpwrite,
PHYSFS_File *f = PHYSFS_openWrite(str);
if (!f) {
ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
goto END;
}
size_t len;
unsigned char *data;
if (JS_IsString(argv[1]))
data = JS_ToCStringLen(js,&len,argv[1]);
else
data = JS_GetArrayBuffer(js,&len, argv[1]);
size_t wrote = PHYSFS_writeBytes(f,data, len);
PHYSFS_close(f);
if (wrote == -1 || wrote < len)
ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
END:
)
JSC_SSCALL(io_mount,
if (!PHYSFS_mount(str,str2,0)) ret = JS_ThrowReferenceError(js,"Unable to mount %s at %s: %s", str, str2, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
JSC_SCALL(io_unmount,
if (!PHYSFS_unmount(str)) ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
JSC_SCALL(io_writepath,
if (!PHYSFS_setWriteDir(str)) ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
struct globdata {
JSContext *js;
JSValue arr;
char **globs;
char *glob;
int idx;
int recurse;
};
int globfs_cb(struct globdata *data, char *dir, char *file)
{
int needfree = 0;
char *path;
if (dir[0] == 0) path = file;
else {
path = malloc(strlen(dir) + strlen(file) + 2);
path[0] = 0;
strcat(path,dir);
strcat(path,"/");
strcat(path,file);
needfree = 1;
}
char **glob = data->globs;
while (*glob != NULL) {
if (wildmatch(*glob, path, WM_WILDSTAR) == WM_MATCH)
goto END;
*glob++;
}
PHYSFS_Stat stat;
PHYSFS_stat(path, &stat);
if (stat.filetype == PHYSFS_FILETYPE_DIRECTORY) {
PHYSFS_enumerate(path, globfs_cb, data);
goto END;
}
JS_SetPropertyUint32(data->js, data->arr, data->idx++, JS_NewString(data->js,path));
END:
if (needfree) free(path);
return 1;
}
JSC_SSCALL(io_match,
if (wildmatch(str, str2, WM_PATHNAME | WM_PERIOD | WM_WILDSTAR) == WM_MATCH)
ret = JS_NewBool(js,1);
else
ret = JS_NewBool(js,0);
)
JSC_CCALL(io_globfs,
ret = JS_NewArray(js);
struct globdata data;
data.js = js;
data.arr = ret;
data.idx = 0;
int globs_len = js_arrlen(js,argv[0]);
char *globs[globs_len+1];
for (int i = 0; i < globs_len; i++) {
JSValue g = JS_GetPropertyUint32(js,argv[0],i);
globs[i] = JS_ToCString(js,g);
JS_FreeValue(js,g);
}
globs[globs_len] = NULL;
data.globs = globs;
PHYSFS_enumerate("", globfs_cb, &data);
for (int i = 0; i < globs_len; i++)
JS_FreeCString(js,globs[i]);
ret = data.arr;
)
static int enumerate_cb(void *udata, const char *dir, const char *fname)
{
struct globdata *data = (struct globdata*)udata;
char *path;
int needfree = 0;
if (dir[0] == 0) path = (char*)fname;
else {
size_t dlen = strlen(dir);
int ends_slash = (dlen && dir[dlen - 1] == '/');
path = malloc(dlen + strlen(fname) + (ends_slash ? 1 : 2));
if (ends_slash) sprintf(path, "%s%s", dir, fname); else sprintf(path, "%s/%s", dir, fname);
needfree = 1;
}
PHYSFS_Stat st;
if (!PHYSFS_stat(path, &st)) {
if (needfree) free(path);
return 1;
}
/* If it's a directory and we're recursing, enumerate it further. */
if (st.filetype == PHYSFS_FILETYPE_DIRECTORY && data->recurse)
PHYSFS_enumerate(path, enumerate_cb, data);
/* Add this item (file or directory) to the JS array. */
JS_SetPropertyUint32(data->js, data->arr, data->idx++, JS_NewString(data->js, path));
if (needfree) free(path);
return 1; /* continue enumerating */
}
JSC_SCALL(io_enumerate,
/* First argument: str (directory name) */
/* Second argument: boolean => recurse or not */
ret = JS_NewArray(js);
struct globdata data;
data.js = js;
data.arr = ret;
data.idx = 0;
data.glob = NULL; /* not used here */
data.globs = NULL; /* not used here */
data.recurse = 0;
if (!JS_IsUndefined(argv[1])) /* parse second arg if provided */
data.recurse = JS_ToBool(js, argv[1]);
/* Enumerate the directory given by 'str'. */
PHYSFS_enumerate(str, enumerate_cb, &data);
/* Return the JS array we filled. */
ret = data.arr;
)
JSC_CCALL(io_basedir, return JS_NewString(js,PHYSFS_getBaseDir()))
JSC_CCALL(io_userdir, return JS_NewString(js,PHYSFS_getUserDir()))
JSC_SCALL(io_open,
PHYSFS_File *f = PHYSFS_openWrite(str);
if (!f)
ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
else
ret = PHYSFS_File2js(js,f);
)
JSC_SCALL(io_realdir,
const char *real = PHYSFS_getRealDir(str);
if (!real)
ret = JS_UNDEFINED;
else
ret = JS_NewString(js,real);
)
JSC_CCALL(io_searchpath,
ret = JS_NewArray(js);
char **paths = PHYSFS_getSearchPath();
for (int i = 0; paths[i] != NULL; i++)
JS_SetPropertyUint32(js,ret,i,JS_NewString(js,paths[i]));
)
static const JSCFunctionListEntry js_io_funcs[] = {
MIST_FUNC_DEF(io, rm, 1),
MIST_FUNC_DEF(io, mkdir, 1),
MIST_FUNC_DEF(io,stat,1),
MIST_FUNC_DEF(io, globfs, 1),
MIST_FUNC_DEF(io, match, 2),
MIST_FUNC_DEF(io, exists, 1),
MIST_FUNC_DEF(io, mount, 2),
MIST_FUNC_DEF(io,unmount,1),
MIST_FUNC_DEF(io,slurp,1),
MIST_FUNC_DEF(io,slurpbytes,1),
MIST_FUNC_DEF(io,slurpwrite,2),
MIST_FUNC_DEF(io,writepath, 1),
MIST_FUNC_DEF(io,basedir, 0),
MIST_FUNC_DEF(io, userdir, 0),
MIST_FUNC_DEF(io, realdir, 1),
MIST_FUNC_DEF(io, open, 1),
MIST_FUNC_DEF(io, searchpath, 0),
MIST_FUNC_DEF(io, enumerate, 2),
};
JSC_CCALL(file_close,
PHYSFS_File *f = js2PHYSFS_File(js,self);
PHYSFS_close(f);
)
JSC_CCALL(file_write,
PHYSFS_File *f = js2PHYSFS_File(js,self);
size_t len;
unsigned char *data;
if (JS_IsString(argv[0]))
data = JS_ToCStringLen(js,&len,argv[0]);
else
data = JS_GetArrayBuffer(js,&len, argv[0]);
size_t wrote = PHYSFS_writeBytes(f,data,len);
if (wrote == -1 || wrote < len)
return JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
JSC_CCALL(file_buffer,
PHYSFS_File *f = js2PHYSFS_File(js,self);
size_t size = js2number(js,argv[0]);
if (!PHYSFS_setBuffer(f,size))
return JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
)
JSC_CCALL(file_tell,
PHYSFS_File *f = js2PHYSFS_File(js,self);
size_t tell = PHYSFS_tell(f);
if (tell == -1)
return JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
return number2js(js,tell);
)
JSC_CCALL(file_eof,
PHYSFS_File *f = js2PHYSFS_File(js,self);
return JS_NewBool(js, PHYSFS_eof(f));
)
static const JSCFunctionListEntry js_PHYSFS_File_funcs[] = {
MIST_FUNC_DEF(file, close, 0),
MIST_FUNC_DEF(file, write, 1),
MIST_FUNC_DEF(file, buffer, 1),
MIST_FUNC_DEF(file, tell, 0),
MIST_FUNC_DEF(file, eof, 0),
};
JSC_GETSET_APPLY(transform, pos, vec3)
JSC_GETSET_APPLY(transform, scale, vec3f)
JSC_GETSET_APPLY(transform, rotation, quat)
JSC_CCALL(transform_move,
transform *t = js2transform(js,self);
transform_move(t, js2vec3(js,argv[0]));
)
JSC_CCALL(transform_lookat,
HMM_Vec3 point = js2vec3(js,argv[0]);
transform *go = js2transform(js,self);
HMM_Mat4 m = HMM_LookAt_RH(go->pos, point, vUP);
go->rotation = HMM_M4ToQ_RH(m);
transform_apply(go);
)
JSC_CCALL(transform_rotate,
HMM_Vec3 axis = js2vec3(js,argv[0]);
transform *t = js2transform(js,self);
HMM_Quat rot = HMM_QFromAxisAngle_RH(axis, js2angle(js,argv[1]));
t->rotation = HMM_MulQ(t->rotation,rot);
transform_apply(t);
)
JSC_CCALL(transform_angle,
HMM_Vec3 axis = js2vec3(js,argv[0]);
transform *t = js2transform(js,self);
if (axis.x) return angle2js(js,HMM_Q_Roll(t->rotation));
if (axis.y) return angle2js(js,HMM_Q_Pitch(t->rotation));
if (axis.z) return angle2js(js,HMM_Q_Yaw(t->rotation));
return angle2js(js,0);
)
JSC_CCALL(transform_direction,
transform *t = js2transform(js,self);
return vec32js(js, HMM_QVRot(js2vec3(js,argv[0]), t->rotation));
)
JSC_CCALL(transform_phys2d,
transform *t = js2transform(js,self);
HMM_Vec2 v = js2vec2(js,argv[0]);
float av = js2number(js,argv[1]);
float dt = js2number(js,argv[2]);
transform_move(t, (HMM_Vec3){v.x*dt,v.y*dt,0});
HMM_Quat rot = HMM_QFromAxisAngle_RH((HMM_Vec3){0,0,1}, av*dt);
t->rotation = HMM_MulQ(t->rotation, rot);
transform_apply(t);
)
JSC_CCALL(transform_unit,
transform *t = js2transform(js,self);
t->pos = v3zero;
t->rotation = QUAT1;
t->scale = v3one;
transform_apply(t);
)
JSC_CCALL(transform_trs,
transform *t = js2transform(js,self);
t->pos = JS_IsUndefined(argv[0]) ? v3zero : js2vec3(js,argv[0]);
t->rotation = JS_IsUndefined(argv[1]) ? QUAT1 : js2quat(js,argv[1]);
t->scale = JS_IsUndefined(argv[2]) ? v3one : js2vec3(js,argv[2]);
transform_apply(t);
)
JSC_CCALL(transform_rect,
transform *t = js2transform(js,self);
rect r = js2rect(js,argv[0]);
t->pos = (HMM_Vec3){r.x,r.y,0};
t->scale = (HMM_Vec3){r.w,r.h,1};
t->rotation = QUAT1;
transform_apply(t);
)
JSC_CCALL(transform_array,
transform *t = js2transform(js,self);
HMM_Mat4 m= transform2mat(t);
ret = JS_NewArray(js);
for (int i = 0; i < 16; i++)
JS_SetPropertyUint32(js,ret,i, number2js(js,m.em[i]));
)
static JSValue js_transform_get_change_hook(JSContext *js, JSValueConst self)
{
transform *t = js2transform(js,self);
return JS_DupValue(js,t->change_hook);
}
static JSValue js_transform_set_change_hook(JSContext *js, JSValueConst self, JSValue v)
{
transform *t = js2transform(js,self);
if (!JS_IsUndefined(v) && !JS_IsFunction(js,v)) return JS_ThrowReferenceError(js, "Hook must be a function.");
JS_FreeValue(js,t->change_hook);
t->change_hook = JS_DupValue(js,v);
}
static JSValue js_transform_get_parent(JSContext *js, JSValueConst self)
{
transform *t = js2transform(js,self);
if (t->parent) return JS_DupValue(js,t->jsparent);
return JS_UNDEFINED;
}
static JSValue js_transform_set_parent(JSContext *js, JSValueConst self, JSValue v)
{
transform *p = js2transform(js,v);
if (!JS_IsUndefined(v) && !p)
return JS_ThrowReferenceError(js,"Parent must be another transform.");
transform *t = js2transform(js,self);
if (t == p)
return JS_ThrowReferenceError(js, "A transform cannot be its own parent.");
if (t->parent) {
transform *cur_parent = t->parent;
JS_FreeValue(js,t->jsparent);
t->jsparent = JS_UNDEFINED;
for (int i = 0; i < arrlen(cur_parent->children); i++) {
if (cur_parent->children[i] == t) {
arrdelswap(cur_parent->children,i);
break;
}
}
for (int i = 0; i < arrlen(cur_parent->jschildren); i++) {
if (JS_SameValue(js,cur_parent->jschildren[i],self)) {
JS_FreeValue(js,cur_parent->jschildren[i]);
arrdelswap(cur_parent->jschildren,i);
break;
}
}
}
t->parent = p;
t->jsparent = JS_DupValue(js,v);
if (p) {
arrput(p->children, t);
JSValue child = JS_DupValue(js,self);
arrput(p->jschildren,child);
}
transform_apply(t);
return JS_UNDEFINED;
}
JSC_CCALL(transform_torect,
transform *t = js2transform(js,self);
return rect2js(js,transform2rect(t));
)
JSC_CCALL(transform_children,
transform *t = js2transform(js,self);
ret = JS_NewArray(js);
for (int i = 0; i < arrlen(t->jschildren); i++)
JS_SetPropertyUint32(js,ret,i,JS_DupValue(js,t->jschildren[i]));
)
static const JSCFunctionListEntry js_transform_funcs[] = {
CGETSET_ADD(transform, pos),
CGETSET_ADD(transform, scale),
CGETSET_ADD(transform, rotation),
CGETSET_ADD(transform, parent),
CGETSET_ADD(transform, change_hook),
MIST_FUNC_DEF(transform, trs, 3),
MIST_FUNC_DEF(transform, phys2d, 3),
MIST_FUNC_DEF(transform, move, 1),
MIST_FUNC_DEF(transform, rotate, 2),
MIST_FUNC_DEF(transform, angle, 1),
MIST_FUNC_DEF(transform, lookat, 1),
MIST_FUNC_DEF(transform, direction, 1),
MIST_FUNC_DEF(transform, unit, 0),
MIST_FUNC_DEF(transform, rect, 1),
MIST_FUNC_DEF(transform, array, 0),
MIST_FUNC_DEF(transform, torect, 0),
MIST_FUNC_DEF(transform, children, 0),
};
JSC_CCALL(datastream_time, return number2js(js,plm_get_time(js2datastream(js,self)->plm)); )
JSC_CCALL(datastream_seek, ds_seek(js2datastream(js,self), js2number(js,argv[0])))
JSC_CCALL(datastream_advance, ds_advance(js2datastream(js,self), js2number(js,argv[0])))
JSC_CCALL(datastream_duration, return number2js(js,ds_length(js2datastream(js,self))))
JSC_CCALL(datastream_framerate, return number2js(js,plm_get_framerate(js2datastream(js,self)->plm)))
JSC_GETSET_CALLBACK(datastream, callback)
static const JSCFunctionListEntry js_datastream_funcs[] = {
MIST_FUNC_DEF(datastream, time, 0),
MIST_FUNC_DEF(datastream, seek, 1),
MIST_FUNC_DEF(datastream, advance, 1),
MIST_FUNC_DEF(datastream, duration, 0),
MIST_FUNC_DEF(datastream, framerate, 0),
CGETSET_ADD(datastream, callback),
};
JSC_GETSET_CALLBACK(timer, fn)
JSC_GETSET(timer, remain, number)
static const JSCFunctionListEntry js_timer_funcs[] = {
CGETSET_ADD(timer, remain),
CGETSET_ADD(timer, fn),
};
JSC_GETSET(font, linegap, number)
JSC_GET(font, height, number)
JSC_GET(font, ascent, number)
JSC_GET(font, descent, number)
JSC_SCALL(font_text_size,
font *f = js2font(js,self);
float size = js2number(js,argv[0]);
if (!size) size = f->height;
float letterSpacing = js2number(js,argv[1]);
float wrap = js2number(js,argv[2]);
ret = vec22js(js,measure_text(str, f, size, letterSpacing, wrap));
)
static const JSCFunctionListEntry js_font_funcs[] = {
CGETSET_ADD(font, linegap),
MIST_GET(font, height),
MIST_GET(font, ascent),
MIST_GET(font, descent),
MIST_FUNC_DEF(font, text_size, 3),
};
const char *STRTEST = "TEST STRING";
JSC_CCALL(performance_barecall,)
JSC_CCALL(performance_unpack_array,
void *v = js2cpvec2arr(js,argv[0]);
arrfree(v);
)
JSC_CCALL(performance_pack_num, return number2js(js,1.0))
JSC_CCALL(performance_pack_string, return JS_NewStringLen(js, STRTEST, sizeof(*STRTEST)))
JSC_CCALL(performance_unpack_string, JS_ToCString(js, argv[0]))
JSC_CCALL(performance_call_fn_n,
for (int i = 0; i < js2number(js,argv[1]); i++) {
JSValue r = JS_Call(js, argv[0], JS_UNDEFINED, 0, NULL);
uncaught_exception(js,r);
}
uncaught_exception(js,JS_Call(js,argv[2], JS_UNDEFINED, 0, NULL));
)
static const JSCFunctionListEntry js_performance_funcs[] = {
MIST_FUNC_DEF(performance, barecall,0),
MIST_FUNC_DEF(performance, unpack_array, 1),
MIST_FUNC_DEF(performance, pack_num, 0),
MIST_FUNC_DEF(performance, pack_string, 0),
MIST_FUNC_DEF(performance, unpack_string, 1),
MIST_FUNC_DEF(performance, call_fn_n, 3)
};
JSC_CCALL(geometry_rect_intersection,
rect a = js2rect(js,argv[0]);
rect b = js2rect(js,argv[1]);
rect c;
SDL_GetRectIntersectionFloat(&a, &b, &c);
return rect2js(js,c);
)
JSC_CCALL(geometry_rect_intersects,
rect a = js2rect(js,argv[0]);
rect b = js2rect(js,argv[1]);
return JS_NewBool(js, SDL_HasRectIntersectionFloat(&a,&b));
)
JSC_CCALL(geometry_rect_inside,
rect inner = js2rect(js,argv[0]);
rect outer = js2rect(js,argv[1]);
return JS_NewBool(js,
inner.x >= outer.x &&
inner.x + inner.w <= outer.x + outer.w &&
inner.y >= outer.y &&
inner.y + inner.h <= outer.y + outer.h
);
)
JSC_CCALL(geometry_rect_random,
rect a = js2rect(js,argv[0]);
return vec22js(js,(HMM_Vec2){
a.x + rand_range(-0.5,0.5)*a.w,
a.y + rand_range(-0.5,0.5)*a.h
});
)
JSC_CCALL(geometry_rect_point_inside,
rect a = js2rect(js,argv[0]);
HMM_Vec2 p = js2vec2(js,argv[1]);
return JS_NewBool(js,p.x >= a.x && p.x <= a.x+a.w && p.y <= a.y+a.h && p.y >= a.y);
)
JSC_CCALL(geometry_cwh2rect,
HMM_Vec2 c = js2vec2(js,argv[0]);
HMM_Vec2 wh = js2vec2(js,argv[1]);
rect r;
r.x = c.x;
r.y = c.y;
r.w = wh.x;
r.h = wh.y;
return rect2js(js,r);
)
JSC_CCALL(geometry_rect_pos,
rect r = js2rect(js,argv[0]);
return vec22js(js,(HMM_Vec2){
.x = r.x,
.y = r.y
});
)
JSC_CCALL(geometry_rect_move,
rect r = js2rect(js,argv[0]);
HMM_Vec2 move = js2vec2(js,argv[1]);
// cblas_saxpy(2, 1.0f, move.e, 1, &r, 1);
r.x += move.x;
r.y += move.y;
return rect2js(js,r);
)
/*static inline float fmin(float a, float b)
{
if (a < b) return a;
return b;
}
static inline float fmax(float a, float b)
{
if (a > b) return a;
return b;
}
*/
JSC_CCALL(geometry_rect_expand,
rect a = js2rect(js,argv[0]);
rect b = js2rect(js,argv[1]);
rect c = {0};
c.x = fmin(a.x,b.x);
c.y = fmin(a.y,b.y);
c.w = fmax(a.x+a.w,b.x+b.w);
c.h = fmax(a.y+a.h,b.y+b.h);
return rect2js(js,c);
)
static const JSCFunctionListEntry js_geometry_funcs[] = {
MIST_FUNC_DEF(geometry, rect_intersection, 2),
MIST_FUNC_DEF(geometry, rect_intersects, 2),
MIST_FUNC_DEF(geometry, rect_expand, 2),
MIST_FUNC_DEF(geometry, rect_inside, 2),
MIST_FUNC_DEF(geometry, rect_random, 1),
MIST_FUNC_DEF(geometry, cwh2rect, 2),
MIST_FUNC_DEF(geometry, rect_point_inside, 2),
MIST_FUNC_DEF(geometry, rect_pos, 1),
MIST_FUNC_DEF(geometry, rect_move, 2),
};
JSC_SCALL(os_env,
char *env = getenv(str);
if (env) ret = JS_NewString(js,env);
)
JSValue js_os_sys(JSContext *js, JSValue self, int argc, JSValue *argv)
{
return JS_NewString(js, SDL_GetPlatform());
}
JSC_CCALL(os_exit, exit(js2number(js,argv[0]));)
JSC_CCALL(os_gc,
return JS_RunGC(JS_GetRuntime(js), js)
)
JSC_CCALL(os_now, return number2js(js, (double)SDL_GetTicksNS()/1000000000.0))
JSC_CCALL(os_mem_limit, JS_SetMemoryLimit(JS_GetRuntime(js), js2number(js,argv[0])))
JSC_CCALL(os_gc_threshold, JS_SetGCThreshold(JS_GetRuntime(js), js2number(js,argv[0])))
JSC_CCALL(os_max_stacksize, JS_SetMaxStackSize(JS_GetRuntime(js), js2number(js,argv[0])))
JSC_CCALL(os_rt_info, return JS_GetRTInfo(JS_GetRuntime(js),js))
static JSValue tmp2js(JSContext *js,FILE *tmp)
{
size_t size = ftell(tmp);
rewind(tmp);
char *buffer = calloc(size+1, sizeof(char));
fread(buffer, sizeof(char),size, tmp);
JSValue ret = JS_NewString(js,buffer);
free(buffer);
return ret;
}
JSC_CCALL(os_dump_atoms,
FILE *tmp = tmpfile();
quickjs_set_dumpout(tmp);
JS_PrintAtoms(JS_GetRuntime(js));
ret = tmp2js(js,tmp);
)
JSC_CCALL(os_dump_shapes,
FILE *tmp = tmpfile();
quickjs_set_dumpout(tmp);
JS_PrintShapes(JS_GetRuntime(js));
size_t size = ftell(tmp);
rewind(tmp);
char buffer[size];
fgets(buffer, sizeof(char)*size, tmp);
fclose(tmp);
ret = JS_NewString(js,buffer);
)
JSC_CCALL(os_calc_mem,
return number2js(js,JS_MyValueSize(JS_GetRuntime(js), argv[0]));
)
#define JSOBJ_ADD_FIELD(OBJ, STRUCT, FIELD, TYPE) \
JS_SetPropertyStr(js, OBJ, #FIELD, TYPE##2js(js,STRUCT.FIELD));\
#define JSJMEMRET(FIELD) JSOBJ_ADD_FIELD(ret, jsmem, FIELD, number)
JSC_CCALL(os_memstate,
JSMemoryUsage jsmem;
JS_FillMemoryState(JS_GetRuntime(js), &jsmem);
ret = JS_NewObject(js);
JSJMEMRET(malloc_size)
JSJMEMRET(malloc_limit)
)
JSC_CCALL(os_mallinfo,
ret = JS_UNDEFINED;
/*struct mallinfo jsmem = mallinfo();
ret = JS_NewObject(js);
JSJMEMRET(arena);
JSJMEMRET(ordblks);
JSJMEMRET(smblks);
JSJMEMRET(hblks);
JSJMEMRET(hblkhd);
JSJMEMRET(usmblks);
JSJMEMRET(uordblks);
JSJMEMRET(fordblks);
JSJMEMRET(keepcost);*/
)
JSC_CCALL(os_rusage,
ret = JS_NewObject(js);
#ifndef _WIN32
struct rusage jsmem;
getrusage(RUSAGE_SELF, &jsmem);
JSJMEMRET(ru_maxrss);
JSJMEMRET(ru_ixrss);
JSJMEMRET(ru_idrss);
JSJMEMRET(ru_isrss);
JSJMEMRET(ru_minflt);
JSJMEMRET(ru_majflt);
JSJMEMRET(ru_nswap);
JSJMEMRET(ru_inblock);
JSJMEMRET(ru_oublock);
JSJMEMRET(ru_msgsnd);
JSJMEMRET(ru_msgrcv);
JSJMEMRET(ru_nsignals);
JSJMEMRET(ru_nvcsw);
JSJMEMRET(ru_nivcsw);
#endif
)
JSC_CCALL(os_mem,
JSMemoryUsage jsmem;
JS_ComputeMemoryUsage(JS_GetRuntime(js), &jsmem);
ret = JS_NewObject(js);
JSJMEMRET(malloc_size)
JSJMEMRET(malloc_limit)
JSJMEMRET(memory_used_size)
JSJMEMRET(memory_used_count)
JSJMEMRET(atom_count)
JSJMEMRET(atom_size)
JSJMEMRET(str_count)
JSJMEMRET(str_size)
JSJMEMRET(obj_count)
JSJMEMRET(prop_count)
JSJMEMRET(prop_size)
JSJMEMRET(shape_count)
JSJMEMRET(shape_size)
JSJMEMRET(js_func_count)
JSJMEMRET(js_func_size)
JSJMEMRET(js_func_code_size)
JSJMEMRET(js_func_pc2line_count)
JSJMEMRET(js_func_pc2line_size)
JSJMEMRET(c_func_count)
JSJMEMRET(array_count)
JSJMEMRET(fast_array_count)
JSJMEMRET(fast_array_elements)
JSJMEMRET(binary_object_count)
JSJMEMRET(binary_object_size)
)
JSC_CCALL(os_dump_mem,
FILE *tmp = tmpfile();
JSMemoryUsage mem = {0};
JS_DumpMemoryUsage(tmp, &mem, JS_GetRuntime(js));
ret = tmp2js(js,tmp);
)
JSC_CCALL(os_value_id,
return number2js(js,(intptr_t)JS_VALUE_GET_PTR(self));
)
static double gc_t = 0;
static double gc_mem = 0;
static double gc_startmem = 0;
void script_report_gc_time(double t, double startmem, double mem)
{
gc_t = t;
gc_mem = mem;
gc_startmem = startmem;
}
JSC_SSCALL(os_eval, return ret = script_eval(js,str, str2))
JSC_CCALL(os_make_timer, return timer2js(js,timer_make(js,argv[0])))
JSC_CCALL(os_update_timers, timer_update(js, js2number(js,argv[0])))
// input: (encoded image data of jpg, png, bmp, tiff)
JSC_CCALL(os_make_texture,
size_t len;
void *raw = JS_GetArrayBuffer(js, &len, argv[0]);
if (!raw) return JS_ThrowReferenceError(js, "could not load texture with array buffer");
int n, width, height;
void *data = stbi_load_from_memory(raw, len, &width, &height, &n, 0);
if (data == NULL)
return JS_ThrowReferenceError(js, "no known image type from pixel data");
int FMT = 0;
if (n == 4)
FMT = SDL_PIXELFORMAT_RGBA32;
else if (n == 3)
FMT = SDL_PIXELFORMAT_RGB24;
else {
free(data);
return JS_ThrowReferenceError(js, "unknown pixel format. got %d channels", n);
}
SDL_Surface *surf = SDL_CreateSurfaceFrom(width,height,FMT, data, width*n);
if (!surf) {
free(data);
return JS_ThrowReferenceError(js, "Error creating surface from data: %s",SDL_GetError());
}
ret = SDL_Surface2js(js,surf);
)
// input: (gif image data)
JSC_CCALL(os_make_gif,
size_t rawlen;
void *raw = JS_GetArrayBuffer(js, &rawlen, argv[0]);
if (!raw) return JS_ThrowReferenceError(js, "could not load gif from supplied array buffer");
int n;
int frames;
int *delays;
int width;
int height;
void *pixels = stbi_load_gif_from_memory(raw, rawlen, &delays, &width, &height, &frames, &n, 4);
JSValue gif = JS_NewObject(js);
ret = gif;
if (frames == 1) {
// still image, so return just that
JS_SetPropertyStr(js, gif, "surface", SDL_Surface2js(js,SDL_CreateSurfaceFrom(width,height,SDL_PIXELFORMAT_RGBA32, pixels, width*4)));
return gif;
}
JSValue delay_arr = JS_NewArray(js);
for (int i = 0; i < frames; i++) {
JSValue frame = JS_NewObject(js);
JS_SetPropertyStr(js, frame, "time", number2js(js,(float)delays[i]/1000.0));
void *frame_pixels = malloc(width*height*4);
if (!frame_pixels) {
JS_FreeValue(js,gif);
ret = JS_ThrowOutOfMemory(js);
goto CLEANUP;
}
memcpy(frame_pixels, (unsigned char*)pixels+(width*height*4*i), width*height*4);
SDL_Surface *framesurf = SDL_CreateSurfaceFrom(width,height,SDL_PIXELFORMAT_RGBA32,frame_pixels, width*4);
if (!framesurf) {
ret = JS_ThrowReferenceError(js, "failed to create SDL_Surface: %s", SDL_GetError());
goto CLEANUP;
}
JS_SetPropertyStr(js, frame, "surface", SDL_Surface2js(js,framesurf));
JS_SetPropertyUint32(js, delay_arr, i, frame);
}
JS_SetPropertyStr(js, gif, "frames", delay_arr);
CLEANUP:
free(delays);
free(pixels);
)
JSValue aseframe2js(JSContext *js, ase_frame_t aframe)
{
JSValue frame = JS_NewObject(js);
void *frame_pixels = malloc(aframe.ase->w*aframe.ase->h*4);
memcpy(frame_pixels, aframe.pixels, aframe.ase->w*aframe.ase->h*4);
SDL_Surface *surf = SDL_CreateSurfaceFrom(aframe.ase->w, aframe.ase->h, SDL_PIXELFORMAT_RGBA32, frame_pixels, aframe.ase->w*4);
JS_SetPropertyStr(js, frame, "surface", SDL_Surface2js(js,surf));
JS_SetPropertyStr(js, frame, "rect", rect2js(js,(rect){.x=0,.y=0,.w=1,.h=1}));
JS_SetPropertyStr(js, frame, "time", number2js(js,(float)aframe.duration_milliseconds/1000.0));
return frame;
}
// input: (aseprite data)
JSC_CCALL(os_make_aseprite,
size_t rawlen;
void *raw = JS_GetArrayBuffer(js,&rawlen,argv[0]);
ase_t *ase = cute_aseprite_load_from_memory(raw, rawlen, NULL);
if (!ase)
return JS_ThrowReferenceError(js, "could not load aseprite from supplied array buffer: %s", aseprite_GetError());
if (ase->tag_count == 0) {
// we're dealing with a single frame image, or single animation
if (ase->frame_count == 1) {
JSValue obj = aseframe2js(js,ase->frames[0]);
cute_aseprite_free(ase);
return obj;
}
}
JSValue obj = JS_NewObject(js);
for (int t = 0; t < ase->tag_count; t++) {
ase_tag_t tag = ase->tags[t];
JSValue anim = JS_NewObject(js);
JS_SetPropertyStr(js, anim, "repeat", number2js(js,tag.repeat));
switch(tag.loop_animation_direction) {
case ASE_ANIMATION_DIRECTION_FORWARDS:
JS_SetPropertyStr(js, anim, "loop", JS_NewString(js,"forward"));
break;
case ASE_ANIMATION_DIRECTION_BACKWORDS:
JS_SetPropertyStr(js, anim, "loop", JS_NewString(js,"backward"));
break;
case ASE_ANIMATION_DIRECTION_PINGPONG:
JS_SetPropertyStr(js, anim, "loop", JS_NewString(js,"pingpong"));
break;
}
int _frame = 0;
JSValue frames = JS_NewArray(js);
for (int f = tag.from_frame; f <= tag.to_frame; f++) {
JSValue frame = aseframe2js(js,ase->frames[f]);
JS_SetPropertyUint32(js, frames, _frame, frame);
_frame++;
}
JS_SetPropertyStr(js, anim, "frames", frames);
JS_SetPropertyStr(js, obj, tag.name, anim);
}
ret = obj;
cute_aseprite_free(ase);
)
JSC_CCALL(os_make_surface,
HMM_Vec2 wh = js2vec2(js,argv[0]);
SDL_Surface *surface = SDL_CreateSurface(wh.x, wh.y, SDL_PIXELFORMAT_RGBA32);
ret = SDL_Surface2js(js, surface);
)
JSC_CCALL(os_make_cursor,
SDL_Surface *s = js2SDL_Surface(js,argv[0]);
HMM_Vec2 hot = js2vec2(js,argv[1]);
SDL_Cursor *c = SDL_CreateColorCursor(s, hot.x, hot.y);
if (!c) return JS_ThrowReferenceError(js,"couldn't make cursor: %s", SDL_GetError());
return SDL_Cursor2js(js,c);
)
JSC_CCALL(os_make_font,
size_t len;
void *data = JS_GetArrayBuffer(js,&len,argv[0]);
if (!data) return JS_ThrowReferenceError(js, "could not get array buffer data");
font *f = MakeFont(data, len, js2number(js,argv[1]));
if (!f) return JS_ThrowReferenceError(js, "could not create font");
ret = font2js(js,f);
JS_SetPropertyStr(js, ret, "surface", SDL_Surface2js(js,f->surface));
)
JSC_CCALL(os_make_transform,
transform *t = make_transform();
ret = transform2js(js,t);
// t->self = JS_DupValue(js,ret);
t->self = ret;
)
JSC_CCALL(os_make_sprite, return sprite2js(js,make_sprite()))
JSC_SCALL(os_system, ret = number2js(js,system(str)); )
JSValue make_color_buffer(JSContext *js, colorf c, int verts)
{
HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts);
for (int i = 0; i < verts; i++)
colordata[i] = c;
return make_gpu_buffer(js, colordata, sizeof(*colordata)*verts, JS_TYPED_ARRAY_FLOAT32, 4, 0, 0);
}
JSC_CCALL(os_make_line_prim,
JSValue prim = JS_NewObject(js);
HMM_Vec2 *v = js2cpvec2arr(js,argv[0]);
parsl_context *par_ctx = parsl_create_context((parsl_config){
.thickness = js2number(js,argv[1]),
.flags= PARSL_FLAG_ANNOTATIONS,
.u_mode = js2number(js,argv[2])
});
uint16_t spine_lens[] = {arrlen(v)};
parsl_mesh *m = parsl_mesh_from_lines(par_ctx, (parsl_spine_list){
.num_vertices = arrlen(v),
.num_spines = 1,
.vertices = v,
.spine_lengths = spine_lens,
.closed = JS_ToBool(js,argv[3])
});
JS_SetPropertyStr(js, prim, "pos", make_gpu_buffer(js,m->positions,sizeof(*m->positions)*m->num_vertices, JS_TYPED_ARRAY_FLOAT32, 2,1,0));
JS_SetPropertyStr(js, prim, "indices", make_gpu_buffer(js,m->triangle_indices,sizeof(*m->triangle_indices)*m->num_triangles*3, JS_TYPED_ARRAY_UINT32, 1,1,1));
float uv[m->num_vertices*2];
for (int i = 0; i < m->num_vertices; i++) {
uv[i*2] = m->annotations[i].u_along_curve;
uv[i*2+1] = m->annotations[i].v_across_curve;
}
JS_SetPropertyStr(js, prim, "uv", make_gpu_buffer(js, uv, sizeof(uv), JS_TYPED_ARRAY_FLOAT32,2,1,0));
JS_SetPropertyStr(js,prim,"vertices", number2js(js,m->num_vertices));
JS_SetPropertyStr(js,prim,"color",make_color_buffer(js,js2color(js,argv[4]), m->num_vertices));
JS_SetPropertyStr(js,prim,"num_indices", number2js(js,m->num_triangles*3));
JS_SetPropertyStr(js,prim,"first_index", number2js(js,0));
parsl_destroy_context(par_ctx);
return prim;
)
static void render_frame(plm_t *mpeg, plm_frame_t *frame, datastream *ds) {
if (JS_IsUndefined(ds->callback)) return;
uint8_t *rgb = malloc(frame->height*frame->width*4);
memset(rgb,255,frame->height*frame->width*4);
plm_frame_to_rgba(frame, rgb, frame->width*4);
SDL_Surface *surf = SDL_CreateSurfaceFrom(frame->width,frame->height, SDL_PIXELFORMAT_RGBA32, rgb, frame->width*4);
JSValue s[1];
s[0] = SDL_Surface2js(ds->js,surf);
JSValue cb = JS_DupValue(ds->js,ds->callback);
JSValue ret = JS_Call(ds->js, cb, JS_UNDEFINED, 1, s);
JS_FreeValue(ds->js,cb);
free(rgb);
uncaught_exception(ds->js,ret);
}
JSC_CCALL(os_make_video,
size_t len;
void *data = JS_GetArrayBuffer(js,&len,argv[0]);
datastream *ds = ds_openvideo(data, len);
if (!ds) return JS_ThrowReferenceError(js, "Video file was not valid.");
ds->js = js;
ds->callback = JS_UNDEFINED;
plm_set_video_decode_callback(ds->plm, render_frame, ds);
return datastream2js(js,ds);
)
JSC_CCALL(os_rectpack,
int width = js2number(js,argv[0]);
int height = js2number(js,argv[1]);
int num = js_arrlen(js,argv[2]);
stbrp_context ctx[1];
stbrp_rect rects[num];
for (int i = 0; i < num; i++) {
HMM_Vec2 wh = js2vec2(js,js_getpropertyuint32(js, argv[2], i));
rects[i].w = wh.x;
rects[i].h = wh.y;
rects[i].id = i;
}
stbrp_node nodes[width];
stbrp_init_target(ctx, width, height, nodes, width);
int packed = stbrp_pack_rects(ctx, rects, num);
if (!packed) {
return JS_UNDEFINED;
}
ret = JS_NewArray(js);
for (int i = 0; i < num; i++) {
HMM_Vec2 pos;
pos.x = rects[i].x;
pos.y = rects[i].y;
JS_SetPropertyUint32(js, ret, i, vec22js(js,pos));
}
)
JSC_SCALL(os_kill,
int sig = 0;
if (!strcmp(str, "SIGABRT")) sig = SIGABRT;
else if (!strcmp(str, "SIGFPE")) sig = SIGFPE;
else if (!strcmp(str, "SIGILL")) sig = SIGILL;
else if (!strcmp(str, "SIGINT")) sig = SIGINT;
else if (!strcmp(str, "SIGSEGV")) sig = SIGSEGV;
else if (!strcmp(str, "SIGTERM")) sig = SIGTERM;
if (!sig) return JS_ThrowReferenceError(js, "string %s is not a valid signal", str);
raise(sig);
)
JSC_CCALL(os_sleep,
double time = js2number(js,argv[0]);
time *= 1000000000.;
SDL_DelayNS(time);
)
JSC_CCALL(os_battery_pct,
int pct;
SDL_GetPowerInfo(NULL, &pct);
return number2js(js,pct);
)
JSC_CCALL(os_battery_voltage,
)
JSC_CCALL(os_battery_seconds,
int seconds;
SDL_GetPowerInfo(&seconds, NULL);
return number2js(js,seconds);
)
JSC_CCALL(os_insertion_sort,
JSValue arr = argv[0];
JSValue cmp = argv[1];
int len = js_arrlen(js, arr);
for (int i = 1; i < len; i++) {
JSValue key = JS_GetPropertyUint32(js, arr, i);
int j = i - 1;
while (j >= 0) {
JSValue arr_j = JS_GetPropertyUint32(js, arr, j);
JSValue ret = JS_Call(js, cmp, JS_UNDEFINED, 2, (JSValue[]){ arr_j, key });
if (JS_IsException(ret)) {
JS_FreeValue(js,arr_j);
JS_FreeValue(js,key);
return ret;
}
double c = js2number(js, ret);
JS_FreeValue(js, ret);
if (c > 0) {
JS_SetPropertyUint32(js, arr, j + 1, arr_j);
j--;
} else {
JS_FreeValue(js, arr_j);
break;
}
}
JS_SetPropertyUint32(js, arr, j + 1, key);
}
ret = JS_DupValue(js,arr);
)
JSC_CCALL(os_power_state,
SDL_PowerState state = SDL_GetPowerInfo(NULL, NULL);
switch(state) {
case SDL_POWERSTATE_ERROR: return JS_ThrowTypeError(js, "Error determining power status");
case SDL_POWERSTATE_UNKNOWN: return JS_UNDEFINED;
case SDL_POWERSTATE_ON_BATTERY: return JS_NewString(js, "on battery");
case SDL_POWERSTATE_NO_BATTERY: return JS_NewString(js, "no battery");
case SDL_POWERSTATE_CHARGING: return JS_NewString(js, "charging");
case SDL_POWERSTATE_CHARGED: return JS_NewString(js, "charged");
}
return JS_UNDEFINED;
)
JSC_CCALL(os_cull_sprites,
ret = JS_NewArray(js);
int n = 0;
JSValue sprites = argv[0];
shader_globals info = camera_globals(js,argv[1]);
rect camera_rect = {0};
camera_rect.x = info.camera_pos_world.x - info.render_size.x/2.0;
camera_rect.y = info.camera_pos_world.y - info.render_size.y/2.0;
camera_rect.w = info.render_size.x;
camera_rect.h = info.render_size.y;
int len = js_arrlen(js,sprites);
for (int i = 0; i < len; i++) {
JSValue sub = JS_GetPropertyUint32(js,sprites,i);
transform *t;
JS_GETATOM(js,t,sub,transform_atom,transform)
rect sprite = transform2rect(t);
if (SDL_HasRectIntersectionFloat(&sprite, &camera_rect)) {
JS_SetPropertyUint32(js,ret,n,JS_DupValue(js,sub));
n++;
}
JS_FreeValue(js,sub);
}
)
JSC_CCALL(os_make_rtree,
struct rtree *tree = rtree_new();
if (!tree) return JS_ThrowOutOfMemory(js);
return rtree2js(js,tree);
)
JSC_CCALL(os_rects_to_sprites,
ret = JS_NewArray(js);
JSValue image = argv[0];
JSValue jstex = JS_GetProperty(js,image,texture_atom);
double w, h = 0;
JS_GETATOM(js,w,jstex,width_atom,number)
JS_GETATOM(js,h,jstex,height_atom,number)
SDL_GPUTexture *tex = js2SDL_GPUTexture(js,jstex);
JS_FreeValue(js,jstex);
JSValue rects = argv[1];
rect uv;
JS_GETATOM(js,uv,image,rect_atom,rect)
ret = JS_NewArray(js);
int n = js_arrlen(js,rects);
for (int i = 0; i < n; i++) {
JSValue sub = JS_GetPropertyUint32(js,rects,i);
sprite *s = make_sprite();
s->affine = js2rect(js,sub);
s->affine.w = w;
s->affine.h = h;
s->image = JS_DupValue(js,image);
s->tex = tex;
s->uv = uv;
JS_SetPropertyUint32(js,ret,i,sprite2js(js,s));
JS_FreeValue(js,sub);
}
)
JSC_CCALL(os_on,
on_exception = JS_DupValue(js,argv[1]);
)
JSC_CCALL(os_clean_transforms,
clean_all();
)
JSC_CCALL(os_totalmem, return number2js(js, SDL_GetSystemRAM()))
JSC_CCALL(os_platform, return JS_NewString(js,SDL_GetPlatform()))
JSC_CCALL(os_hostname,
char buf[256];
if (gethostname(buf,sizeof(buf)) == 0) return JS_NewString(js,buf);
return JS_NewString(js,"");
)
JSC_CCALL(os_freemem,
#ifdef _WIN32
MEMORYSTATUSEX statex;
statex.dwLength = sizeof(statex);
if (!GlobalMemoryStatusEx(&statex)) return JS_ThrowInternalError(js,"GlobalMemoryStatusEx failed");
return JS_NewInt64(js,(int64_t)statex.ullAvailPhys);
#elif defined(__linux__)
struct sysinfo info;
if (sysinfo(&info) == 0)
return JS_NewInt64(js,(int64_t)info.freeram * info.mem_unit);
return JS_NewInt64(js,0);
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
// A very rough fallback using the same sysconf approach
// (macOS or *BSD typically need specialized APIs to get free mem accurately)
// This is often only "unused" pages, ignoring caches, etc.
long pages = sysconf(_SC_AVPHYS_PAGES);
long page_size = sysconf(_SC_PAGE_SIZE);
if (pages < 0 || page_size < 0) return JS_NewInt64(js,0);
return JS_NewInt64(js,(int64_t)pages * (int64_t)page_size);
#else
// Fallback: unknown
return JS_NewInt64(js,0);
#endif
)
JSC_CCALL(os_arch,
#if defined(__x86_64__) || defined(_M_X64)
return JS_NewString(js,"x64");
#elif defined(__aarch64__) || defined(_M_ARM64)
return JS_NewString(js,"arm64");
#elif defined(__arm__) || defined(_M_ARM)
return JS_NewString(js,"arm");
#elif defined(__i386__) || defined(_M_IX86)
return JS_NewString(js,"ia32");
#elif defined(__loongarch__) || defined(__loongarch32) || defined(__loongarch64)
return JS_NewString(js,"loong64");
#elif defined(__mips__) || defined(__mips) || defined(_M_MIPS)
// You might want to distinguish mips vs mipsel
return JS_NewString(js,"mips");
#elif defined(__ppc64__) || defined(__powerpc64__) || defined(_M_PPC)
// You might want to distinguish ppc vs ppc64, big-endian vs little-endian
return JS_NewString(js,"ppc64");
#elif defined(__riscv) && __riscv_xlen == 64
return JS_NewString(js,"riscv64");
#elif defined(__s390x__)
return JS_NewString(js,"s390x");
#else
return JS_NewString(js,"unknown");
#endif
)
JSC_CCALL(os_version,
#ifdef _WIN32
typedef LONG (WINAPI *RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
HMODULE h = GetModuleHandleA("ntdll.dll");
if (h) {
RtlGetVersionPtr fx = (RtlGetVersionPtr)GetProcAddress(h, "RtlGetVersion");
if (fx) {
RTL_OSVERSIONINFOW ver;
memset(&ver, 0, sizeof(ver));
ver.dwOSVersionInfoSize = sizeof(ver);
if (!fx(&ver)) {
char buf[128];
sprintf(buf, "%u.%u.%u",
(unsigned)ver.dwMajorVersion,
(unsigned)ver.dwMinorVersion,
(unsigned)ver.dwBuildNumber
);
return JS_NewString(js, buf);
}
}
}
OSVERSIONINFOW wver;
memset(&wver, 0, sizeof(wver));
wver.dwOSVersionInfoSize = sizeof(wver);
if (GetVersionExW(&wver)) {
char buf[128];
sprintf(buf, "%u.%u.%u",
(unsigned)wver.dwMajorVersion,
(unsigned)wver.dwMinorVersion,
(unsigned)wver.dwBuildNumber
);
return JS_NewString(js, buf);
}
return JS_NewString(js, "Windows_Unknown");
#else
struct utsname info;
if (!uname(&info)) return JS_NewString(js, info.release);
return JS_NewString(js, "");
#endif
)
static const JSCFunctionListEntry js_os_funcs[] = {
MIST_FUNC_DEF(os, make_transform, 0),
MIST_FUNC_DEF(os, clean_transforms, 0),
MIST_FUNC_DEF(os, platform, 0),
MIST_FUNC_DEF(os, arch, 0),
MIST_FUNC_DEF(os, totalmem, 0),
MIST_FUNC_DEF(os, freemem, 0),
MIST_FUNC_DEF(os, hostname, 0),
MIST_FUNC_DEF(os, version, 0),
MIST_FUNC_DEF(os, kill, 1),
MIST_FUNC_DEF(os, exit, 1),
MIST_FUNC_DEF(os, now, 0),
MIST_FUNC_DEF(os, openurl, 1),
MIST_FUNC_DEF(os, make_timer, 1),
MIST_FUNC_DEF(os, update_timers, 1),
MIST_FUNC_DEF(os, sleep, 1),
MIST_FUNC_DEF(os, battery_pct, 0),
MIST_FUNC_DEF(os, battery_voltage, 0),
MIST_FUNC_DEF(os, battery_seconds, 0),
MIST_FUNC_DEF(os, power_state, 0),
MIST_FUNC_DEF(os, on, 2),
MIST_FUNC_DEF(os, rt_info, 0),
MIST_FUNC_DEF(os, rusage, 0),
MIST_FUNC_DEF(os, mallinfo, 0),
// dangerous ones that need disabled for shipping
MIST_FUNC_DEF(os, env, 1),
MIST_FUNC_DEF(os, system, 1),
};
static const JSCFunctionListEntry js_js_funcs[] = {
MIST_FUNC_DEF(os, dump_mem, 0),
MIST_FUNC_DEF(os, dump_shapes, 0),
MIST_FUNC_DEF(os, dump_atoms,0),
MIST_FUNC_DEF(os, calc_mem, 1),
MIST_FUNC_DEF(os, mem, 1),
MIST_FUNC_DEF(os, mem_limit, 1),
MIST_FUNC_DEF(os, gc_threshold, 1),
MIST_FUNC_DEF(os, max_stacksize, 1),
MIST_FUNC_DEF(os, memstate, 0),
MIST_FUNC_DEF(os, gc, 0),
MIST_FUNC_DEF(os, eval, 2),
};
static const JSCFunctionListEntry js_util_funcs[] = {
MIST_FUNC_DEF(os, guid, 0),
MIST_FUNC_DEF(os, insertion_sort, 2),
};
static const JSCFunctionListEntry js_graphics_funcs[] = {
MIST_FUNC_DEF(gpu, make_sprite_mesh, 2),
MIST_FUNC_DEF(gpu, make_sprite_queue, 4),
MIST_FUNC_DEF(os, make_text_buffer, 6),
MIST_FUNC_DEF(os, rectpack, 3),
MIST_FUNC_DEF(os, make_rtree, 0),
MIST_FUNC_DEF(os, make_texture, 1),
MIST_FUNC_DEF(os, make_gif, 1),
MIST_FUNC_DEF(os, make_aseprite, 1),
MIST_FUNC_DEF(os, cull_sprites, 2),
MIST_FUNC_DEF(os, rects_to_sprites,2),
MIST_FUNC_DEF(os, make_surface, 1),
MIST_FUNC_DEF(os, make_cursor, 1),
MIST_FUNC_DEF(os, make_font, 2),
MIST_FUNC_DEF(os, make_sprite, 0),
MIST_FUNC_DEF(os, make_line_prim, 5),
};
static const JSCFunctionListEntry js_video_funcs[] = {
MIST_FUNC_DEF(os, make_video, 1),
};
void gui_input(SDL_Event *e);
// Polls and handles all input events
JSC_CCALL(os_engine_input,
SDL_Event event;
while (SDL_PollEvent(&event)) {
#ifndef NEDITOR
gui_input(&event);
#endif
JSValue e = event2js(js,event);
JSValue ret = JS_Call(js,argv[0], JS_UNDEFINED, 1, &e);
uncaught_exception(js,ret);
}
)
JSC_CCALL(os_push_event,
SDL_UserEvent e;
SDL_zero(e);
e.type = SDL_EVENT_USER;
e.timestamp = SDL_GetTicksNS();
e.code = 0;
JSValue fn = JS_DupValue(js,argv[0]);
e.data1 = malloc(sizeof(JSValue));
*(JSValue*)e.data1 = fn;
SDL_PushEvent(&e);
)
static const JSCFunctionListEntry js_event_funcs[] = {
MIST_FUNC_DEF(os, push_event, 1),
MIST_FUNC_DEF(os, engine_input, 1),
};
typedef JSValue (*MODULEFN)(JSContext *js);
typedef struct {
const char *name;
MODULEFN fn;
} ModuleEntry;
static ModuleEntry *module_registry = NULL;
JSC_SCALL(os_use_embed,
for (int i = 0; i < arrlen(module_registry); i++) {
if (strcmp(str,module_registry[i].name) == 0) {
ret = module_registry[i].fn(js);
break;
}
}
if (JS_IsUndefined(ret))
ret = JS_ThrowReferenceError(js,"Library %s could not be found embedded", str);
)
JSC_SCALL(os_use_dyn,
SDL_SharedObject *ptr = SDL_LoadObject(str);
if (!ptr)
return JS_ThrowReferenceError(js, "Shared library %s could not be loaded", SDL_GetError());
JSValue (*js_use)(JSContext*);
js_use = (JSValue (*)(JSContext*))SDL_LoadFunction(ptr, "use");
if (!js_use)
ret = JS_ThrowReferenceError(js, "Shared library %s has no use function", str);
else
ret = js_use(js);
SDL_UnloadObject(ptr);
)
JSC_CCALL(rtree_add,
rtree *tree = js2rtree(js,self);
JSValue v = argv[0];
rect r;
JS_GETATOM(js,r,v,rect_atom,rect)
NUMTYPE min[3];
NUMTYPE max[3];
min[0] = r.x;
min[1] = r.y;
min[2] = 0;
max[0] = r.x+r.w;
max[1] = r.y+r.h;
max[2] = 0;
JSValue *ins = malloc(sizeof(*ins));
*ins = JS_DupValue(js,v);
if (!rtree_insert(tree, min, max, ins)) {
JS_FreeValue(js,*ins);
return JS_ThrowOutOfMemory(js);
}
)
int rtree_cmp(const JSValue *a, const JSValue *b, JSContext *js)
{
int same = JS_SameValue(js, *a, *b);
if (same)
JS_FreeValue(js,*a);
return !same;
}
JSC_CCALL(rtree_delete,
rtree *tree = js2rtree(js,self);
JSValue v = argv[0];
rect r;
JS_GETATOM(js,r,v,rect_atom,rect)
NUMTYPE min[3];
NUMTYPE max[3];
min[0] = r.x;
min[1] = r.y;
min[2] = 0;
max[0] = r.x+r.w;
max[1] = r.y+r.h;
max[2] = 0;
if (!rtree_delete_with_comparator(tree, min, max, &v, rtree_cmp, js))
return JS_ThrowOutOfMemory(js);
)
struct rtree_iter_data {
JSContext *js;
JSValue arr;
int n;
};
bool rtree_iter(const NUMTYPE *min, const NUMTYPE *max, const JSValue *data, struct rtree_iter_data *ctx)
{
JS_SetPropertyUint32(ctx->js,ctx->arr,ctx->n, JS_DupValue(ctx->js,*data));
ctx->n++;
return 1;
}
bool rtree_array_iter(const NUMTYPE *min, const NUMTYPE *max, const JSValue *data, sprite **arr)
{
sprite *sp = js2sprite(global_js, *data);
arrput(*arr, *sp);
return 1;
}
JSC_CCALL(rtree_query,
rtree *tree = js2rtree(js,self);
rect r = js2rect(js,argv[0]);
NUMTYPE min[3];
NUMTYPE max[3];
min[0] = r.x;
min[1] = r.y;
min[2] = 0;
max[0] = r.x+r.w;
max[1] = r.y+r.h;
max[2] = 0;
struct rtree_iter_data data = {0};
data.js = js;
data.arr = JS_NewArray(js);
data.n = 0;
rtree_search(tree, min, max, rtree_iter, &data);
ret = data.arr;
/*
sprite *arr = NULL;
rtree_search(tree, min, max, rtree_array_iter, &arr);
ret = JS_NewArrayBufferCopy(js,arr,arrlen(arr)*sizeof(*arr));
arrfree(arr);
*/
)
struct rtree_each
{
JSValue fn;
JSContext *js;
};
int rtree_foreach(const NUMTYPE *min, const NUMTYPE *max, const JSValue *value, struct rtree_each *each)
{
JSValue ret = JS_Call(each->js, each->fn, JS_UNDEFINED, 0, NULL);
uncaught_exception(each->js, ret);
return 1;
}
JSC_CCALL(rtree_forEach,
rtree *tree = js2rtree(js,self);
struct rtree_each each;
each.fn = JS_DupValue(js,argv[0]);
each.js = js;
rtree_scan(tree, rtree_foreach, &each);
JS_FreeValue(js,each.fn);
)
typedef struct {
JSContext *js;
JSValue v;
int has;
} rtree_has;
int rtree_hasfn(const NUMTYPE *min, const NUMTYPE *max, const JSValue *value, rtree_has *has)
{
if (JS_SameValue(has->js, has->v, *value)) {
has->has = 1;
return 0;
}
return 1;
}
JSC_CCALL(rtree_has,
rtree *tree = js2rtree(js,self);
rtree_has has;
has.js = js;
has.v = JS_DupValue(js,argv[0]);
has.has = 0;
rtree_scan(tree, rtree_hasfn, &has);
JS_FreeValue(js,argv[0]);
return JS_NewBool(js,has.has);
)
JSValue js_rtree_get_size(JSContext *js, JSValue self, int magic)
{
rtree *tree = js2rtree(js,self);
return number2js(js,rtree_count(tree));
}
int rtree_valuefn(const NUMTYPE *min, const NUMTYPE *max, const JSValue *value, struct rtree_iter_data *data)
{
JS_SetPropertyUint32(data->js, data->arr, data->n, JS_DupValue(data->js, *value));
data->n++;
return 1;
}
JSC_CCALL(rtree_values,
rtree *tree = js2rtree(js,self);
struct rtree_iter_data data = {0};
data.js = js;
data.arr = JS_NewArray(js);
data.n = 0;
rtree_scan(tree, rtree_valuefn, &data);
ret = data.arr;
)
static const JSCFunctionListEntry js_rtree_funcs[] = {
MIST_FUNC_DEF(rtree, add, 1),
MIST_FUNC_DEF(rtree, delete, 1),
MIST_FUNC_DEF(rtree, query, 1),
JS_CGETSET_DEF("size", js_rtree_get_size,NULL),
MIST_FUNC_DEF(rtree, forEach, 1),
MIST_FUNC_DEF(rtree, has, 1),
MIST_FUNC_DEF(rtree,values,0),
};
JSC_GETSET(sprite, layer, number)
JSC_GETSET(sprite, color, color)
JSC_CCALL(sprite_set_affine,
sprite *sp = js2sprite(js,self);
transform *t = js2transform(js,argv[0]);
if (t)
sp->affine = transform2rect(t);
)
JSC_CCALL(sprite_set_rect,
sprite *sp = js2sprite(js,self);
sp->affine = js2rect(js,argv[0]);
)
JSC_CCALL(sprite_set_image,
sprite *sp = js2sprite(js,self);
if (!JS_IsUndefined(sp->image))
JS_FreeValue(js,sp->image);
sp->image = JS_DupValue(js,argv[0]);
if (JS_IsUndefined(sp->image)) return JS_UNDEFINED;
JSValue img = sp->image;
JS_GETATOM(js, sp->uv, img, rect_atom, rect)
JS_GETATOM(js, sp->tex, img, texture_atom, SDL_GPUTexture);
)
static const JSCFunctionListEntry js_sprite_funcs[] = {
MIST_FUNC_DEF(sprite, set_affine, 1),
MIST_FUNC_DEF(sprite, set_rect, 1),
MIST_FUNC_DEF(sprite, set_image, 1),
CGETSET_ADD(sprite, layer),
CGETSET_ADD(sprite, color),
};
#define JSSTATIC(NAME, PARENT) \
js_##NAME = JS_NewObject(js); \
JS_SetPropertyFunctionList(js, js_##NAME, js_##NAME##_funcs, countof(js_##NAME##_funcs)); \
JS_SetPrototype(js, js_##NAME, PARENT); \
JSValue js_layout_use(JSContext *js);
JSValue js_soloud_use(JSContext *js);
#ifdef TRACY_ENABLE
JSValue js_tracy_use(JSContext *js);
#endif
static void signal_handler(int sig) {
const char *str = NULL;
switch(sig) {
case SIGABRT:
str = "SIGABRT";
break;
case SIGFPE:
str = "SIGFPE";
break;
case SIGILL:
str = "SIGILL";
break;
case SIGINT:
str = "SIGINT";
break;
case SIGSEGV:
str = "SIGSEGV";
break;
case SIGTERM:
str = "SIGTERM";
break;
}
if (!str) return;
script_evalf("prosperon.dispatch('%s')", str);
}
static void exit_handler()
{
script_evalf("prosperon.dispatch('exit')");
script_stop();
}
MISTUSE(io)
MISTUSE(os)
MISTUSE(input)
MISTUSE(time)
MISTUSE(math)
MISTUSE(spline)
MISTUSE(geometry)
MISTUSE(js)
MISTUSE(graphics)
MISTUSE(util)
MISTUSE(video)
MISTUSE(event)
#ifndef NEDITOR
JSValue js_imgui_use(JSContext *js);
#endif
#define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use}
void ffi_load(JSContext *js, int argc, char **argv) {
arrput(module_registry, MISTLINE(io));
arrput(module_registry, MISTLINE(os));
arrput(module_registry, MISTLINE(input));
arrput(module_registry, MISTLINE(time));
arrput(module_registry, MISTLINE(math));
arrput(module_registry, MISTLINE(spline));
arrput(module_registry, MISTLINE(geometry));
arrput(module_registry, MISTLINE(graphics));
arrput(module_registry, MISTLINE(js));
arrput(module_registry, MISTLINE(util));
arrput(module_registry, MISTLINE(video));
arrput(module_registry, MISTLINE(event));
arrput(module_registry, MISTLINE(soloud));
arrput(module_registry,MISTLINE(layout));
#ifndef NEDITOR
arrput(module_registry, MISTLINE(imgui));
#endif
#ifdef TRACY_ENABLE
arrput(module_registry, MISTLINE(tracy));
#endif
JSValue globalThis = JS_GetGlobalObject(js);
JSValue prosp = JS_NewObject(js);
JSValue c_types = JS_NewObject(js);
JS_SetPropertyStr(js,prosp, "c_types", c_types);
QJSCLASSPREP_FUNCS(rtree)
QJSCLASSPREP_FUNCS(SDL_Window)
QJSCLASSPREP_FUNCS(SDL_Surface)
QJSCLASSPREP_FUNCS(SDL_Thread)
QJSCLASSPREP_FUNCS(SDL_Texture)
QJSCLASSPREP_FUNCS(SDL_Renderer)
QJSCLASSPREP_FUNCS(SDL_Camera)
QJSCLASSPREP_FUNCS(SDL_Cursor)
QJSCLASSPREP_FUNCS(SDL_GPUDevice)
QJSCLASSPREP_FUNCS(SDL_GPUTexture)
QJSCLASSPREP_FUNCS(SDL_GPUCommandBuffer)
QJSCLASSPREP_FUNCS(SDL_GPURenderPass)
QJSCLASSPREP_FUNCS(SDL_GPUComputePass)
QJSCLASSPREP_FUNCS(SDL_GPUCopyPass)
QJSCLASSPREP_FUNCS(SDL_GPUFence)
QJSCLASSPREP_FUNCS(SDL_GPUTransferBuffer)
QJSCLASSPREP_FUNCS(SDL_GPUShader)
QJSCLASSPREP_FUNCS(SDL_GPUSampler)
QJSCLASSPREP_FUNCS(SDL_GPUGraphicsPipeline)
QJSCLASSPREP_FUNCS(SDL_GPUComputePipeline)
QJSCLASSPREP_FUNCS(sprite)
// QJSCLASSPREP_FUNCS(SDL_GPUGraphicsPipeline)
// QJSCLASSPREP_FUNCS(SDL_GPUSampler)
// QJSCLASSPREP_FUNCS(SDL_GPUShader)
QJSCLASSPREP_FUNCS(SDL_GPUBuffer)
// QJSCLASSPREP_FUNCS(SDL_GPUTransferBuffer)
QJSCLASSPREP_FUNCS(PHYSFS_File)
QJSCLASSPREP_FUNCS(transform);
QJSCLASSPREP_FUNCS(font);
QJSCLASSPREP_FUNCS(datastream);
QJSCLASSPREP_FUNCS(timer);
JS_SetPropertyStr(js, globalThis, "use_dyn", JS_NewCFunction(js,js_os_use_dyn,"use_dyn", 1));
JS_SetPropertyStr(js, globalThis, "use_embed", JS_NewCFunction(js,js_os_use_embed,"use_embed", 1));
QJSGLOBALCLASS(os);
QJSGLOBALCLASS(console);
JSValue jsobject = JS_GetPropertyStr(js,globalThis, "Object");
JS_SetPropertyStr(js, jsobject, "id", JS_NewCFunction(js, js_os_value_id, "id", 1));
JS_FreeValue(js,jsobject);
JSValue jsarray = JS_GetPropertyStr(js,globalThis, "Array");
JSValue array_proto = JS_GetPropertyStr(js,jsarray, "prototype");
JS_SetPropertyFunctionList(js, array_proto, js_array_funcs, countof(js_array_funcs));
JS_FreeValue(js,jsarray);
JS_FreeValue(js,array_proto);
JSValue jsnumber = JS_GetPropertyStr(js,globalThis, "Number");
JSValue number_proto = JS_GetPropertyStr(js,jsnumber, "prototype");
JS_SetPropertyFunctionList(js, number_proto, js_number_funcs, countof(js_number_funcs));
JS_FreeValue(js,jsnumber);
JS_FreeValue(js,number_proto);
x_atom = JS_NewAtom(js,"x");
y_atom = JS_NewAtom(js,"y");
width_atom = JS_NewAtom(js,"width");
height_atom = JS_NewAtom(js,"height");
anchor_x_atom = JS_NewAtom(js, "anchor_x");
anchor_y_atom = JS_NewAtom(js,"anchor_y");
src_atom = JS_NewAtom(js,"src");
pos_atom = JS_NewAtom(js, "pos");
uv_atom = JS_NewAtom(js, "uv");
color_atom = JS_NewAtom(js, "color");
indices_atom = JS_NewAtom(js, "indices");
vertices_atom = JS_NewAtom(js, "vertices");
dst_atom = JS_NewAtom(js, "dst");
count_atom = JS_NewAtom(js, "count");
transform_atom = JS_NewAtom(js,"transform");
image_atom = JS_NewAtom(js,"image");
layer_atom = JS_NewAtom(js,"layer");
texture_atom = JS_NewAtom(js, "texture");
cw_atom = JS_NewAtom(js,"cw");
ccw_atom = JS_NewAtom(js,"ccw");
zero_atom = JS_NewAtom(js, "zero");
one_atom = JS_NewAtom(js, "one");
add_atom = JS_NewAtom(js, "add");
sub_atom = JS_NewAtom(js, "sub");
rev_sub_atom = JS_NewAtom(js, "rev_sub");
min_atom = JS_NewAtom(js, "min");
max_atom = JS_NewAtom(js, "max");
none_atom = JS_NewAtom(js, "none");
front_atom = JS_NewAtom(js, "front");
back_atom = JS_NewAtom(js, "back");
never_atom = JS_NewAtom(js, "never");
less_atom = JS_NewAtom(js, "less");
equal_atom = JS_NewAtom(js, "equal");
less_equal_atom = JS_NewAtom(js, "less_equal");
greater_atom = JS_NewAtom(js, "greater");
not_equal_atom = JS_NewAtom(js, "not_equal");
greater_equal_atom = JS_NewAtom(js, "greater_equal");
always_atom = JS_NewAtom(js, "always");
keep_atom = JS_NewAtom(js, "keep");
zero_stencil_atom = JS_NewAtom(js, "zero");
replace_atom = JS_NewAtom(js, "replace");
incr_clamp_atom = JS_NewAtom(js, "incr_clamp");
decr_clamp_atom = JS_NewAtom(js, "decr_clamp");
invert_atom = JS_NewAtom(js, "invert");
incr_wrap_atom = JS_NewAtom(js, "incr_wrap");
decr_wrap_atom = JS_NewAtom(js, "decr_wrap");
point_atom = JS_NewAtom(js, "point");
line_atom = JS_NewAtom(js, "line");
linestrip_atom = JS_NewAtom(js, "linestrip");
triangle_atom = JS_NewAtom(js, "triangle");
trianglestrip_atom = JS_NewAtom(js, "trianglestrip");
src_color_atom = JS_NewAtom(js, "src_color");
one_minus_src_color_atom = JS_NewAtom(js, "one_minus_src_color");
dst_color_atom = JS_NewAtom(js, "dst_color");
one_minus_dst_color_atom = JS_NewAtom(js, "one_minus_dst_color");
src_alpha_atom = JS_NewAtom(js, "src_alpha");
one_minus_src_alpha_atom = JS_NewAtom(js, "one_minus_src_alpha");
dst_alpha_atom = JS_NewAtom(js, "dst_alpha");
one_minus_dst_alpha_atom = JS_NewAtom(js, "one_minus_dst_alpha");
constant_color_atom = JS_NewAtom(js, "constant_color");
one_minus_constant_color_atom = JS_NewAtom(js, "one_minus_constant_color");
src_alpha_saturate_atom = JS_NewAtom(js, "src_alpha_saturate");
none_cull_atom = JS_NewAtom(js, "none");
front_cull_atom = JS_NewAtom(js, "front");
back_cull_atom = JS_NewAtom(js, "back");
norm_atom = JS_NewAtom(js,"norm");
nearest_atom = JS_NewAtom(js, "nearest");
linear_atom = JS_NewAtom(js, "linear");
mipmap_nearest_atom = JS_NewAtom(js, "nearest"); // For mipmap mode "nearest"
mipmap_linear_atom = JS_NewAtom(js, "linear"); // For mipmap mode "linear"
repeat_atom = JS_NewAtom(js, "repeat");
mirror_atom = JS_NewAtom(js, "mirror");
clamp_edge_atom = JS_NewAtom(js, "clamp_edge");
clamp_border_atom = JS_NewAtom(js, "clamp_border");
vertex_atom = JS_NewAtom(js, "vertex");
index_atom = JS_NewAtom(js, "index");
indirect_atom = JS_NewAtom(js, "indirect");
num_indices_atom = JS_NewAtom(js,"num_indices");
parent_atom = JS_NewAtom(js,"parent");
rect_atom = JS_NewAtom(js,"rect");
l_atom = JS_NewAtom(js,"l");
r_atom = JS_NewAtom(js,"r");
b_atom = JS_NewAtom(js,"b");
t_atom = JS_NewAtom(js,"t");
fill_event_atoms(js);
global_js = js;
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGSEGV, signal_handler);
signal(SIGABRT, signal_handler);
atexit(exit_handler);
m_seedRand(&mrand, time(NULL));
JSValue args = JS_NewArray(js);
for (int i = 0; i < argc; i++)
JS_SetPropertyUint32(js,args, i, JS_NewString(js,argv[i]));
JS_SetPropertyStr(js,prosp,"argv", args);
JS_SetPropertyStr(js,prosp, "version", JS_NewString(js,PROSPERON_VERSION));
JS_SetPropertyStr(js,prosp,"revision",JS_NewString(js,PROSPERON_COMMIT));
JS_SetPropertyStr(js,prosp,"engine_start", JS_NewCFunction(js,js_os_engine_start, "engine_start", 1));
JS_SetPropertyStr(js,globalThis,"prosperon", prosp);
JS_FreeValue(js,globalThis);
}
/*
#if defined(TRACY_ENABLE) && !defined(_WIN32)
const char *ccname = get_func_name(ctx,func_obj);
const char *file = "<native C>";
TracyCZoneCtx tracy_ctx = ___tracy_emit_zone_begin_alloc(___tracy_alloc_srcloc(1, file, strlen(file), ccname, strlen(ccname), (int)ccname), 1);
JS_FreeCString(ctx,ccname);
#endif
#ifdef TRACY_ENABLE
___tracy_emit_zone_end(tracy_ctx);
#endif
#ifdef TRACY_ENABLE
const char *fn_src = JS_AtomToCString(caller_ctx, js_fn_filename(caller_ctx,func_obj));
const char *js_func_name = get_func_name(caller_ctx, func_obj);
const char *fn_name;
if (!js_func_name || js_func_name[0] == '\0')
fn_name = "<anonymous>";
else
fn_name = js_func_name;
uint64_t srcloc;
srcloc = ___tracy_alloc_srcloc(js_fn_linenum(caller_ctx,func_obj), fn_src, strlen(fn_src), fn_name, strlen(fn_name), (int)fn_src);
TracyCZoneCtx tracy_ctx = ___tracy_emit_zone_begin_alloc(srcloc,1);
JS_FreeCString(caller_ctx,js_func_name);
JS_FreeCString(caller_ctx,fn_src);
#endif
*/