Files
cell/source/jsffi.c
John Alanbrook a204fce4b5
Some checks failed
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / build-macos (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
initial refactor
2025-05-22 11:48:27 -05:00

3521 lines
105 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "jsffi.h"
#include "font.h"
#include "datastream.h"
#include "qjs_sdl.h"
#include "qjs_sdl_gpu.h"
#include "qjs_io.h"
#include "transform.h"
#include "stb_ds.h"
#include "stb_image.h"
#include "stb_rect_pack.h"
#define STB_DXT_IMPLEMENTATION
#include "stb_dxt.h"
#include "stb_image_write.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 "cute_aseprite.h"
#include "cgltf.h"
#include "physfs.h"
#include "prosperon.h"
#include "qjs_dmon.h"
#include "qjs_nota.h"
#include "qjs_wota.h"
#include "qjs_enet.h"
#include "qjs_soloud.h"
#include "qjs_qr.h"
#include "qjs_sdl.h"
#include "qjs_math.h"
#include "qjs_geometry.h"
#include "qjs_transform.h"
#include "qjs_sprite.h"
#include "qjs_io.h"
#include "qjs_sdl_gpu.h"
#include "qjs_os.h"
SDL_Window *global_window;
#include <signal.h>
void gui_input(SDL_Event *e);
#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>
int randombytes(void *buf, size_t n);
int trace = 0;
// External transform function declarations
extern JSClassID js_transform_id;
JSValue transform2js(JSContext *js, transform *t);
transform *js2transform(JSContext *js, JSValue v);
#ifdef __APPLE__
#include <Accelerate/Accelerate.h>
//#else
//#include <cblas.h>
#endif
// Random number generation constants
#define UPPER_MASK 0x80000000
#define LOWER_MASK 0x7fffffff
#define TEMPERING_MASK_B 0x9d2c5680
#define TEMPERING_MASK_C 0xefc60000
// Random number generation functions
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);
}
double rand_range(JSContext *js, double min, double max)
{
MTRand *mrand = &((prosperon_rt*)JS_GetContextOpaque(js))->mrand;
return genRand(mrand) * (max-min)+min;
}
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); } \
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;
}
#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_GetPropertyStr(JS,VALUE,#ATOM); \
TARGET = js2##TYPE(JS, __##PROP##__v); \
JS_FreeValue(JS,__##PROP##__v); }\
#define JS_SETATOM(JS, TARGET, ATOM, VALUE, TYPE) JS_SetProperty(JS, TARGET, #ATOM, TYPE##2js(JS, VALUE));
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, const char *prop)
{
JSValue ret = JS_GetPropertyStr(js, v, prop);
JS_FreeValue(js,ret);
return ret;
}
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;
}
JSValue make_quad_indices_buffer(JSContext *js, int quads)
{
prosperon_rt *rt = JS_GetContextOpaque(js);
int count = quads*6;
if (!JS_IsUndefined(rt->idx_buffer) && rt->idx_count >= count)
return JS_DupValue(js,rt->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(rt->idx_buffer))
JS_FreeValue(js,rt->idx_buffer);
rt->idx_buffer = make_gpu_buffer(js,indices, sizeof(*indices)*count, JS_TYPED_ARRAY_UINT16, 1,0,1);
rt->idx_count = count;
return JS_DupValue(js,rt->idx_buffer);
}
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_SetPropertyStr(js, ret, "pos", jspos);
JS_SetPropertyStr(js, ret, "uv", jsuv);
JS_SetPropertyStr(js, ret, "color", jscolor);
JS_SetPropertyStr(js, ret, "indices", jsidx);
JS_SetPropertyStr(js, ret, "vertices", number2js(js, verts));
JS_SetPropertyStr(js,ret,"num_indices", number2js(js,count));
return ret;
}
#ifndef _WIN32
#include <sys/resource.h>
#endif
typedef struct lrtb lrtb;
lrtb js2lrtb(JSContext *js, JSValue v)
{
lrtb ret = {0};
JS_GETATOM(js,ret.l,v,l,number)
JS_GETATOM(js,ret.r,v,r,number)
JS_GETATOM(js,ret.b,v,b,number)
JS_GETATOM(js,ret.t,v,t,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);
}
typedef struct renderer_ctx {
SDL_Renderer *sdl;
shader_globals cam;
} renderer_ctx;
void renderer_ctx_free(JSRuntime *rt, renderer_ctx *ctx)
{
SDL_DestroyRenderer(ctx->sdl);
free(ctx);
}
void SDL_Surface_free(JSRuntime *rt, SDL_Surface *s) {
if (s->flags & SDL_SURFACE_PREALLOCATED)
free(s->pixels);
SDL_DestroySurface(s);
}
void SDL_GPUCommandBuffer_free(JSRuntime *rt, SDL_GPUCommandBuffer *c)
{
}
QJSCLASS(renderer_ctx,)
QJSCLASS(font,)
QJSCLASS(datastream,)
QJSCLASS(SDL_Window,)
void SDL_Texture_free(JSRuntime *rt, SDL_Texture *t){
SDL_DestroyTexture(t);
}
QJSCLASS(SDL_Texture,
JS_SetPropertyStr(js, j, "width", number2js(js,n->w));
JS_SetPropertyStr(js,j,"height",number2js(js,n->h));
)
QJSCLASS(SDL_Surface,
JS_SetProperty(js, j, width_atom, number2js(js,n->w));
JS_SetProperty(js,j,height_atom,number2js(js,n->h));
)
void rtree_free(JSRuntime *rt, rtree *tree)
{
rtree_destroy(tree);
}
QJSCLASS(rtree,)
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;
}
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_ArrayLength(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_ArrayLength(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_ArrayLength(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_ArrayLength(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,number)
JS_GETATOM(js,rect.y,v,y,number)
JS_GETATOM(js,rect.w,v,width,number)
JS_GETATOM(js,rect.h,v,height,number)
float anchor_x, anchor_y;
JS_GETATOM(js, anchor_x, v, anchor_x, number)
JS_GETATOM(js, anchor_y, v, anchor_y, number)
rect.y -= anchor_y*rect.h;
rect.x -= anchor_x*rect.w;
return rect;
}
irect js2irect(JSContext *js, JSValue v)
{
if (JS_IsUndefined(v)) return (irect){0,0,1,1};
irect rect;
JS_GETATOM(js,rect.x,v,x,number)
JS_GETATOM(js,rect.y,v,y,number)
JS_GETATOM(js,rect.w,v,width,number)
JS_GETATOM(js,rect.h,v,height,number)
float anchor_x, anchor_y;
JS_GETATOM(js, anchor_x, v, anchor_x, number)
JS_GETATOM(js, anchor_y, v, anchor_y, number)
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_SETATOM(js, obj, x, rect.x, number);
JS_SETATOM(js, obj, y, rect.y, number);
JS_SETATOM(js, obj, width, rect.w, number);
JS_SETATOM(js, obj, height, rect.h, number);
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;
}
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, jspos);
JS_SetProperty(js, ret, uv, jsuv);
JS_SetProperty(js, ret, color, jscolor);
JS_SetProperty(js, ret, indices, jsidx);
JS_SetProperty(js, ret, vertices, number2js(js, buffers.verts));
JS_SetProperty(js,ret,num_indices, 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};
if (JS_IsUndefined(camera))
return data;
HMM_Vec2 size;
transform *transform;
double fov = 0;
int ortho;
double near_z = 0;
double far_z = 0;
HMM_Vec2 anchor;
JS_GETPROP(js, size, camera, size, vec2)
JS_GETPROP(js, transform, camera, transform, transform)
JS_GETPROP(js, fov, camera, fov, 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)
JS_GETPROP(js, anchor, camera, anchor, vec2)
HMM_Mat4 proj;
HMM_Mat4 view;
if (ortho) {
float left = -anchor.x * size.x;
float bottom = -anchor.y * size.y;
float right = left + size.x;
float top = bottom + size.y;
proj = HMM_Orthographic_RH_NO(
left, right,
bottom, top,
-1.0f, 1.0f
);
}
else {
proj = HMM_Perspective_RH_NO(fov, size.x/size.y,near_z,far_z);
proj.Columns[1] = HMM_MulV4F(proj.Columns[1], -1.0f);
}
view = HMM_MulM4(
HMM_InvTranslate(HMM_Translate(transform->pos)),
HMM_InvRotate(HMM_QToM4(transform->rotation))
);
// 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;
}
#define JS_HMM_FN(OP, HMM, SIGN) \
JSC_CCALL(array_##OP, \
int len = JS_ArrayLength(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_ArrayLength(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;
)
JSValue js_array_get_x(JSContext *js, JSValue self) { return JS_GetPropertyUint32(js,self,0); }
JSValue js_array_set_x(JSContext *js, JSValue self, JSValue val) { JS_SetPropertyUint32(js,self,0,val); return JS_UNDEFINED; }
JSValue js_array_get_y(JSContext *js, JSValue self) { return JS_GetPropertyUint32(js,self,1); }
JSValue js_array_set_y(JSContext *js, JSValue self, JSValue val) { JS_SetPropertyUint32(js,self,1,val); return JS_UNDEFINED; }
JSValue js_array_get_xy(JSContext *js, JSValue self)
{
JSValue arr = JS_NewArray(js);
JS_SetPropertyUint32(js,arr,0,JS_GetPropertyUint32(js,self,0));
JS_SetPropertyUint32(js,arr,1,JS_GetPropertyUint32(js,self,1));
return arr;
}
JSValue js_array_set_xy(JSContext *js, JSValue self, JSValue v)
{
JS_SetPropertyUint32(js,self,0,JS_GetPropertyUint32(js,v,0));
JS_SetPropertyUint32(js,self,1,JS_GetPropertyUint32(js,v,1));
return JS_UNDEFINED;
}
// Single-value accessors
JSValue js_array_get_r(JSContext *js, JSValue self)
{ return JS_GetPropertyUint32(js, self, 0); }
JSValue js_array_set_r(JSContext *js, JSValue self, JSValue val)
{ JS_SetPropertyUint32(js, self, 0, val); return JS_UNDEFINED; }
JSValue js_array_get_g(JSContext *js, JSValue self)
{ return JS_GetPropertyUint32(js, self, 1); }
JSValue js_array_set_g(JSContext *js, JSValue self, JSValue val)
{ JS_SetPropertyUint32(js, self, 1, val); return JS_UNDEFINED; }
JSValue js_array_get_b(JSContext *js, JSValue self)
{ return JS_GetPropertyUint32(js, self, 2); }
JSValue js_array_set_b(JSContext *js, JSValue self, JSValue val)
{ JS_SetPropertyUint32(js, self, 2, val); return JS_UNDEFINED; }
JSValue js_array_get_a(JSContext *js, JSValue self)
{ return JS_GetPropertyUint32(js, self, 3); }
JSValue js_array_set_a(JSContext *js, JSValue self, JSValue val)
{ JS_SetPropertyUint32(js, self, 3, val); return JS_UNDEFINED; }
// Multi-value accessors
JSValue js_array_get_rgb(JSContext *js, JSValue self)
{
JSValue arr = JS_NewArray(js);
JS_SetPropertyUint32(js, arr, 0, JS_GetPropertyUint32(js, self, 0));
JS_SetPropertyUint32(js, arr, 1, JS_GetPropertyUint32(js, self, 1));
JS_SetPropertyUint32(js, arr, 2, JS_GetPropertyUint32(js, self, 2));
return arr;
}
JSValue js_array_set_rgb(JSContext *js, JSValue self, JSValue val)
{
JS_SetPropertyUint32(js, self, 0, JS_GetPropertyUint32(js, val, 0));
JS_SetPropertyUint32(js, self, 1, JS_GetPropertyUint32(js, val, 1));
JS_SetPropertyUint32(js, self, 2, JS_GetPropertyUint32(js, val, 2));
return JS_UNDEFINED;
}
JSValue js_array_get_rgba(JSContext *js, JSValue self)
{
JSValue arr = JS_NewArray(js);
JS_SetPropertyUint32(js, arr, 0, JS_GetPropertyUint32(js, self, 0));
JS_SetPropertyUint32(js, arr, 1, JS_GetPropertyUint32(js, self, 1));
JS_SetPropertyUint32(js, arr, 2, JS_GetPropertyUint32(js, self, 2));
JS_SetPropertyUint32(js, arr, 3, JS_GetPropertyUint32(js, self, 3));
return arr;
}
JSValue js_array_set_rgba(JSContext *js, JSValue self, JSValue val)
{
JS_SetPropertyUint32(js, self, 0, JS_GetPropertyUint32(js, val, 0));
JS_SetPropertyUint32(js, self, 1, JS_GetPropertyUint32(js, val, 1));
JS_SetPropertyUint32(js, self, 2, JS_GetPropertyUint32(js, val, 2));
JS_SetPropertyUint32(js, self, 3, JS_GetPropertyUint32(js, val, 3));
return JS_UNDEFINED;
}
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),
JS_CGETSET_DEF("x", js_array_get_x,js_array_set_x),
JS_CGETSET_DEF("y", js_array_get_y, js_array_set_y),
JS_CGETSET_DEF("xy", js_array_get_xy, js_array_set_xy),
JS_CGETSET_DEF("r", js_array_get_r, js_array_set_r),
JS_CGETSET_DEF("g", js_array_get_g, js_array_set_g),
JS_CGETSET_DEF("b", js_array_get_b, js_array_set_b),
JS_CGETSET_DEF("a", js_array_get_a, js_array_set_a),
JS_CGETSET_DEF("rgb", js_array_get_rgb, js_array_set_rgb),
JS_CGETSET_DEF("rgba", js_array_get_rgba, js_array_set_rgba),
};
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,
if (SDL_GetCurrentThreadID() != main_thread)
return JS_ThrowInternalError(js, "This can only be called from the root actor.");
if (global_window)
return JS_ThrowReferenceError(js, "The engine was already started.");
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)
const char *title;
JS_GETPROP(js,title,argv[0],title,cstring)
SDL_Window *new = SDL_CreateWindow(title, js2number(js, js_getproperty(js,argv[0], "width")), js2number(js,js_getproperty(js,argv[0], "height")), 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);
)
JSC_SCALL(SDL_Window_make_renderer,
SDL_Window *win = js2SDL_Window(js,self);
renderer_ctx *ctx = malloc(sizeof(*ctx));
ctx->sdl = SDL_CreateRenderer(win, NULL);
ctx->cam = (shader_globals){0};
if (!ctx->sdl) {
free(ctx);
return JS_ThrowReferenceError(js, "Error creating renderer: %s",SDL_GetError());
}
SDL_SetRenderDrawBlendMode(ctx->sdl, SDL_BLENDMODE_BLEND);
return renderer_ctx2js(js,ctx);
)
/*
JSC_CCALL(SDL_Window_make_gpu,
if (global_gpu)
return JS_ThrowReferenceError(js, "A gpu has already been created somewhere.");
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),
};
static inline SDL_FPoint renderer_world_to_screen(renderer_ctx *ctx, HMM_Vec2 p)
{
HMM_Vec4 clip = HMM_MulM4V4(
ctx->cam.world_to_projection,
(HMM_Vec4){ p.x, p.y, 0.0f, 1.0f });
float inv_w = 1.0f / clip.w;
float ndc_x = clip.x * inv_w; /* 1 … +1 */
float ndc_y = clip.y * inv_w;
float scr_x = (ndc_x * 0.5f + 0.5f) * ctx->cam.render_size.x;
float scr_y = (1.0f - (ndc_y * 0.5f + 0.5f)) * ctx->cam.render_size.y;
return (SDL_FPoint){ scr_x, scr_y };
}
static inline rect renderer_worldrect_to_screen(renderer_ctx *ctx, rect r_world)
{
SDL_FPoint bl = renderer_world_to_screen(
ctx, (HMM_Vec2){ r_world.x,
r_world.y });
SDL_FPoint tr = renderer_world_to_screen(
ctx, (HMM_Vec2){ r_world.x + r_world.w,
r_world.y + r_world.h });
/* SDL wants the *top-left* corner, and y grows down. */
rect out;
out.x = SDL_min(bl.x, tr.x); /* left edge */
out.y = SDL_min(bl.y, tr.y); /* top edge */
/* always positive width / height */
out.w = fabsf(tr.x - bl.x);
out.h = fabsf(tr.y - bl.y);
return out;
}
JSC_CCALL(SDL_Renderer_clear,
SDL_Renderer *renderer = js2renderer_ctx(js,self)->sdl;
SDL_RenderClear(renderer);
)
JSC_CCALL(SDL_Renderer_present,
SDL_Renderer *ren = js2renderer_ctx(js,self)->sdl;
SDL_RenderPresent(ren);
)
JSC_CCALL(SDL_Renderer_draw_color,
SDL_Renderer *renderer = js2renderer_ctx(js,self)->sdl;
colorf color = js2color(js,argv[0]);
SDL_SetRenderDrawColorFloat(renderer, color.r,color.g,color.b,color.a);
)
JSC_CCALL(renderer_load_texture,
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
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, number2js(js,tex->w));
JS_SetProperty(js,ret,height, number2js(js,tex->h));
)
JSC_CCALL(renderer_get_image,
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
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_CCALL(renderer_line,
if (!JS_IsArray(js, argv[0])) return JS_ThrowReferenceError(js, "First arugment must be an array of points.");
renderer_ctx *ctx = js2renderer_ctx(js,self);
SDL_Renderer *r = ctx->sdl;
int len = JS_ArrayLength(js,argv[0]);
if (len < 2) return JS_ThrowReferenceError(js, "Must provide at least 2 points to render.");
SDL_FPoint points[len];
assert(sizeof(HMM_Vec2) == sizeof(SDL_FPoint));
for (int i = 0; i < len; i++) {
JSValue val = JS_GetPropertyUint32(js,argv[0],i);
HMM_Vec2 wpt = js2vec2(js,val);
JS_FreeValue(js,val);
points[i] = renderer_world_to_screen(ctx, wpt);
}
SDL_RenderLines(r,points,len);
)
JSC_CCALL(renderer_point,
renderer_ctx *ctx = js2renderer_ctx(js, self);
SDL_Renderer *r = ctx->sdl;
if (JS_IsArray(js, argv[0])) {
int len = JS_ArrayLength(js, argv[0]);
SDL_FPoint pts[len];
for (int i = 0; i < len; ++i) {
JSValue val = JS_GetPropertyUint32(js, argv[0], i);
HMM_Vec2 w = js2vec2(js, val);
JS_FreeValue(js, val);
pts[i] = renderer_world_to_screen(ctx, w);
}
SDL_RenderPoints(r, pts, len);
return JS_UNDEFINED;
}
HMM_Vec2 w = js2vec2(js, argv[0]);
SDL_FPoint p = renderer_world_to_screen(ctx, w);
SDL_RenderPoint(r, p.x, p.y);
)
JSC_CCALL(renderer_rects,
renderer_ctx *ctx = js2renderer_ctx(js, self);
SDL_Renderer *r = ctx->sdl;
/* array-of-rectangles case */
if (JS_IsArray(js, argv[0])) {
int len = JS_ArrayLength(js, argv[0]);
if (len <= 0) return JS_UNDEFINED;
SDL_FRect rects[len];
for (int i = 0; i < len; ++i) {
JSValue val = JS_GetPropertyUint32(js, argv[0], i);
rect w = js2rect(js, val);
JS_FreeValue(js, val);
w = renderer_worldrect_to_screen(ctx, w);
rects[i] = w;
}
if (!SDL_RenderFillRects(r, rects, len))
return JS_ThrowReferenceError(js, "SDL_RenderFillRects: %s", SDL_GetError());
return JS_UNDEFINED;
}
/* single-rect path */
rect w = js2rect(js, argv[0]);
w = renderer_worldrect_to_screen(ctx, w);
if (!SDL_RenderFillRects(r, &w, 1))
return JS_ThrowReferenceError(js, "SDL_RenderFillRects: %s", SDL_GetError());
)
// Should take a single struct with pos, color, uv, and indices arrays
JSC_CCALL(renderer_geometry,
renderer_ctx *ctx = js2renderer_ctx(js,self);
SDL_Renderer *r = ctx->sdl;
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], "num_indices");
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++) {
SDL_FPoint p = renderer_world_to_screen(ctx, trans_pos[i]);
trans_pos[i].x = p.x;
trans_pos[i].y = p.y;
}
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_geometry2,
renderer_ctx *ctx = js2renderer_ctx(js, self);
SDL_Renderer *r = ctx->sdl;
SDL_Texture *tex = js2SDL_Texture(js, argv[0]);
JSValue geo = argv[1];
int vertices = js_getnum_str(js, geo, "vertices");
int count = js_getnum_str(js, geo, "num_indices");
JSValue pos_v = JS_GetPropertyStr(js, geo, "pos");
JSValue color_v = JS_GetPropertyStr(js, geo, "color");
JSValue uv_v = JS_GetPropertyStr(js, geo, "uv");
JSValue idx_v = JS_GetPropertyStr(js, geo, "indices");
size_t pos_stride, color_stride, uv_stride, idx_stride;
void *pos_data = get_gpu_buffer(js, pos_v, &pos_stride, NULL);
void *color_data = get_gpu_buffer(js, color_v, &color_stride, NULL);
void *uv_data = get_gpu_buffer(js, uv_v, &uv_stride, NULL);
void *idx_data = get_gpu_buffer(js, idx_v, &idx_stride, NULL);
SDL_Vertex *verts = malloc(vertices * sizeof *verts);
for(int i = 0; i < vertices; ++i) {
const float *p = (const float *)((uint8_t *)pos_data + i * pos_stride);
const float *u = (const float *)((uint8_t *)uv_data + i * uv_stride);
const float *c = (const float *)((uint8_t *)color_data + i * color_stride);
SDL_FPoint screen = renderer_world_to_screen(ctx,
(HMM_Vec2){ .x = p[0], .y = p[1] });
verts[i].position = screen;
verts[i].tex_coord = (SDL_FPoint){ u[0], u[1] };
verts[i].color = (SDL_FColor){ c[0], c[1], c[2], c[3] };
}
const int *indices32 = NULL;
int *tmp_idx = NULL;
if(idx_data && count) {
if(idx_stride == 4) indices32 = (const int *)idx_data;
else {
tmp_idx = malloc(count * sizeof *tmp_idx);
if(idx_stride == 2) {
const uint16_t *src = idx_data;
for(int i = 0; i < count; ++i) tmp_idx[i] = src[i];
} else { /* 8-bit */
const uint8_t *src = idx_data;
for(int i = 0; i < count; ++i) tmp_idx[i] = src[i];
}
indices32 = tmp_idx;
}
}
printf("num verts, num indices: %d, %d\n", vertices, count);
if(!SDL_RenderGeometry(r, tex, verts, vertices, indices32, count))
printf("Error rendering geometry: %s\n", SDL_GetError());
free(tmp_idx);
free(verts);
JS_FreeValue(js, pos_v);
JS_FreeValue(js, color_v);
JS_FreeValue(js, uv_v);
JS_FreeValue(js, idx_v);
)
JSC_CCALL(renderer_logical_size,
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
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 = js2renderer_ctx(js,self)->sdl;
if (JS_IsUndefined(argv[0]))
SDL_SetRenderViewport(r,NULL);
else {
rect view = js2rect(js,argv[0]);
SDL_SetRenderViewport(r,&view);
}
)
JSC_CCALL(renderer_clip,
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
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 = js2renderer_ctx(js,self)->sdl;
HMM_Vec2 v = js2vec2(js,argv[0]);
SDL_SetRenderScale(r, v.x, v.y);
)
JSC_CCALL(renderer_vsync,
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
return JS_NewBool(js, SDL_SetRenderVSync(r,js2number(js,argv[0])));
)
// This returns the coordinates inside the
JSC_CCALL(renderer_coords,
SDL_Renderer *r = js2renderer_ctx(js,self)->sdl;
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,
renderer_ctx *ctx = js2renderer_ctx(js,self);
ctx->cam = camera_globals(js, argv[0]);
)
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 = js2renderer_ctx(js,self)->sdl;
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:
// 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_ArrayLength(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 jssrc = JS_GetProperty(js,sub,src);
JSValue jscolor = JS_GetProperty(js,sub,color);
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;
// 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,sub);
JS_FreeValue(js,jscolor);
JS_FreeValue(js,jssrc);
}
ret = JS_NewObject(js);
JS_SetProperty(js, ret, pos, make_gpu_buffer(js, posdata, sizeof(*posdata) * verts, JS_TYPED_ARRAY_FLOAT32, 2, 0,0));
JS_SetProperty(js, ret, uv, make_gpu_buffer(js, uvdata, sizeof(*uvdata) * verts, JS_TYPED_ARRAY_FLOAT32, 2, 0,0));
JS_SetProperty(js, ret, color, make_gpu_buffer(js, colordata, sizeof(*colordata) * verts, JS_TYPED_ARRAY_FLOAT32, 4, 0,0));
JS_SetProperty(js, ret, indices, make_quad_indices_buffer(js, quads));
JS_SetProperty(js, ret, vertices, number2js(js, verts));
JS_SetProperty(js, ret, count, number2js(js, count));
)
static sprite js_getsprite(JSContext *js, JSValue sp)
{
sprite *s = js2sprite(js, sp);
if (s)
return *s;
sprite pp = {0};
return pp;
}
JSC_CCALL(renderer_texture,
renderer_ctx *ctx = js2renderer_ctx(js, self);
SDL_Texture *tex = js2SDL_Texture(js,argv[0]);
rect src = js2rect(js,argv[1]);
rect dst = js2rect(js,argv[2]);
dst = renderer_worldrect_to_screen(ctx, dst);
double angle;
JS_ToFloat64(js, &angle, argv[3]);
angle *= -360;
HMM_Vec2 anchor = js2vec2(js, argv[4]);
anchor.y = dst.h - anchor.y*dst.h;
anchor.x *= dst.w;
// anchor.x *= dst.w;
// anchor.y *= dst.h;
// anchor.x += dst.x;
// anchor.y += dst.y;
SDL_RenderTextureRotated(ctx->sdl, tex, &src, &dst, angle, &anchor, SDL_FLIP_NONE);
)
JSC_CCALL(renderer_sprite,
renderer_ctx *ctx = js2renderer_ctx(js, self);
sprite sp = js_getsprite(js, argv[0]);
SDL_Texture *tex;
JS_GETATOM(js, tex, sp.image, texture, SDL_Texture);
rect uv;
JS_GETATOM(js, uv, sp.image, rect, rect);
float w = uv.w, h = uv.h;
HMM_Vec2 tl_local = { -sp.center.X, -sp.center.Y };
HMM_Vec2 tr_local = { -sp.center.X + w, -sp.center.Y };
HMM_Vec2 bl_local = { -sp.center.X, -sp.center.Y + h };
HMM_Vec2 world_tl = HMM_AddV2(sp.pos, HMM_MulM2V2(sp.affine, tl_local));
HMM_Vec2 world_tr = HMM_AddV2(sp.pos, HMM_MulM2V2(sp.affine, tr_local));
HMM_Vec2 world_bl = HMM_AddV2(sp.pos, HMM_MulM2V2(sp.affine, bl_local));
SDL_FPoint origin = renderer_world_to_screen(ctx, world_tl);
SDL_FPoint right = renderer_world_to_screen(ctx, world_tr);
SDL_FPoint down = renderer_world_to_screen(ctx, world_bl);
if (!SDL_RenderTextureAffine(ctx->sdl, tex, &uv, &origin, &right, &down))
return JS_ThrowInternalError(js, "Render sprite error: %s", SDL_GetError());
)
static const JSCFunctionListEntry js_renderer_ctx_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(renderer, line, 1),
MIST_FUNC_DEF(renderer, point, 1),
MIST_FUNC_DEF(renderer, texture, 5),
MIST_FUNC_DEF(renderer, rects, 1),
MIST_FUNC_DEF(renderer, geometry, 2),
MIST_FUNC_DEF(renderer, geometry2, 2),
MIST_FUNC_DEF(renderer, sprite, 1),
MIST_FUNC_DEF(renderer, load_texture, 1),
MIST_FUNC_DEF(renderer, get_image, 1),
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, screen2world, 1),
MIST_FUNC_DEF(renderer, target, 1),
MIST_FUNC_DEF(renderer, make_sprite_mesh, 2),
};
typedef struct { const char *name; SDL_PixelFormat fmt; } fmt_entry;
static const fmt_entry k_fmt_table[] = {
{ "rgba32", SDL_PIXELFORMAT_RGBA32 },
{ "argb32", SDL_PIXELFORMAT_ARGB32 },
{ "bgra32", SDL_PIXELFORMAT_BGRA32 },
{ "abgr32", SDL_PIXELFORMAT_ABGR32 },
{ "rgb565", SDL_PIXELFORMAT_RGB565 },
{ "bgr565", SDL_PIXELFORMAT_BGR565 },
{ "rgb24", SDL_PIXELFORMAT_RGB24 },
{ "bgr24", SDL_PIXELFORMAT_BGR24 },
{ "rgb332", SDL_PIXELFORMAT_RGB332 },
{ "rgba64", SDL_PIXELFORMAT_RGBA64 },
{ "rgb48", SDL_PIXELFORMAT_RGB48 },
{ NULL, SDL_PIXELFORMAT_UNKNOWN }
};
static JSValue pixelformat2js(JSContext *js, SDL_PixelFormat fmt)
{
fmt_entry *it;
for (it = k_fmt_table; it->name; it++)
if (it->fmt == fmt)
break;
if (it->name)
return JS_NewString(js, it->name);
return JS_UNDEFINED;
}
static SDL_PixelFormat js2pixelformat(JSContext *js, JSValue v)
{
if (JS_IsUndefined(v)) return SDL_PIXELFORMAT_UNKNOWN;
const char *s = JS_ToCString(js, v);
if (!s) return SDL_PIXELFORMAT_UNKNOWN;
fmt_entry *it;
for (it = k_fmt_table; it->name; it++)
if (!strcmp(it->name, s))
break;
JS_FreeCString(js,s);
return it->fmt;
}
typedef struct { const char *name; SDL_ScaleMode mode; } scale_entry;
static const scale_entry k_scale_table[] = {
{ "nearest", SDL_SCALEMODE_NEAREST },
{ "linear", SDL_SCALEMODE_LINEAR },
{ NULL, SDL_SCALEMODE_LINEAR } /* fallback */
};
static JSValue scalemode2js(JSContext *js, SDL_ScaleMode mode){
const scale_entry *it;
for(it = k_scale_table; it->name; ++it)
if(it->mode == mode) break;
return JS_NewString(js, it->name ? it->name : "nearest");
}
static SDL_ScaleMode js2SDL_ScaleMode(JSContext *js, JSValue v){
if(JS_IsUndefined(v)) return SDL_SCALEMODE_NEAREST;
const char *s = JS_ToCString(js, v);
if(!s) return SDL_SCALEMODE_NEAREST;
const scale_entry *it;
for(it = k_scale_table; it->name; ++it)
if(!strcmp(it->name, s)) break;
JS_FreeCString(js, s);
return it->mode;
}
/* -------------------------------------------------------------------------
* surface.blit(dstrect, src, srcrect, scalemode)
* -------------------------------------------------------------------------*/
JSC_CCALL(surface_blit,
SDL_Surface *dst = js2SDL_Surface(js, self);
irect dr = {0}, *pdr = NULL;
if(!JS_IsUndefined(argv[0])){ dr = js2irect(js, argv[0]); pdr = &dr; }
SDL_Surface *src = js2SDL_Surface(js, argv[1]);
if (!src)
return JS_ThrowReferenceError(js, "Argument must be a surface.");
irect sr = {0}, *psr = NULL;
if(!JS_IsUndefined(argv[2])){ sr = js2irect(js, argv[2]); psr = &sr; }
SDL_ScaleMode mode = js2SDL_ScaleMode(js, argv[3]);
SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE);
printf("src %p, dst %p, src rect %g,%g,%g,%g [%p], dst rect %g,%g,%g,%g [%p]\n", src, dst, sr.x, sr.y, sr.w, sr.h, psr, dr.x, dr.y, dr.w, dr.h, pdr);
SDL_BlitSurfaceScaled(src, psr, dst, pdr, mode);
)
JSC_CCALL(surface_scale,
SDL_Surface *src = js2SDL_Surface(js,self);
HMM_Vec2 wh = js2vec2(js,argv[0]);
SDL_ScaleMode mode = js2SDL_ScaleMode(js, argv[1]);
SDL_Surface *new = SDL_ScaleSurface(src, wh.x, wh.y, mode);
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_convert,
SDL_Surface *surf = js2SDL_Surface(js,self);
SDL_PixelFormat fmt = js2pixelformat(js, argv[0]);
SDL_Surface *dst = SDL_ConvertSurface(surf, fmt);
if (!dst) return JS_ThrowInternalError(js, "Convert failed: %s", SDL_GetError());
return SDL_Surface2js(js, dst);
)
JSC_CCALL(surface_dup,
SDL_Surface *surf = js2SDL_Surface(js,self);
SDL_Surface *conv = SDL_DuplicateSurface(surf);
if (!conv)
return JS_ThrowReferenceError(js, "could not blit to dup'd surface: %s", SDL_GetError());
return SDL_Surface2js(js,conv);
)
JSC_CCALL(surface_pixels,
SDL_Surface *surf = js2SDL_Surface(js,self);
/* if (SDL_ISPIXELFORMAT_FOURCC(surf->format->format))
return JS_ThrowTypeError(js, "planar or FOURCC formats are not supported - convert first");
*/
int locked = 0;
if (SDL_MUSTLOCK(surf)) {
if (SDL_LockSurface(surf) < 0)
return JS_ThrowInternalError(js, "Lock surface failed: %s", SDL_GetError());
locked = 1;
}
size_t byte_size = surf->pitch*surf->h;
ret = JS_NewArrayBufferCopy(js, surf->pixels, byte_size);
if (locked)
SDL_UnlockSurface(surf);
)
JSC_CCALL(surface_get_width,
SDL_Surface *s = js2SDL_Surface(js,self);
return JS_NewFloat64(js, s->w);
)
JSC_CCALL(surface_get_height,
SDL_Surface *s = js2SDL_Surface(js,self);
return JS_NewFloat64(js, s->h);
)
JSC_CCALL(surface_get_format,
SDL_Surface *s = js2SDL_Surface(js,self);
return pixelformat2js(js, s->format);
)
JSC_CCALL(surface_get_pitch,
SDL_Surface *s = js2SDL_Surface(js,self);
return JS_NewFloat64(js, s->pitch);
)
static const JSCFunctionListEntry js_SDL_Surface_funcs[] = {
MIST_FUNC_DEF(surface, blit, 4),
MIST_FUNC_DEF(surface, scale, 1),
MIST_FUNC_DEF(surface, fill,1),
MIST_FUNC_DEF(surface, rect,2),
MIST_FUNC_DEF(surface, dup, 0),
MIST_FUNC_DEF(surface, pixels, 0),
MIST_FUNC_DEF(surface, convert, 1),
JS_CGETSET_DEF("width", js_surface_get_width, NULL),
JS_CGETSET_DEF("height", js_surface_get_height, NULL),
JS_CGETSET_DEF("format", js_surface_get_format, NULL),
JS_CGETSET_DEF("pitch", js_surface_get_pitch, NULL),
};
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_CCALL(os_guid,
SDL_GUID guid;
randombytes(guid.data, 16);
char guid_str[33];
SDL_GUIDToString(guid, guid_str, 33);
return JS_NewString(js,guid_str);
)
JSC_SCALL(os_openurl,
if (!SDL_OpenURL(str))
ret = JS_ThrowReferenceError(js, "unable to open url %s: %s\n", str, SDL_GetError());
)
JSC_SCALL(console_print,
printf("%s", str);
)
static const JSCFunctionListEntry js_console_funcs[] = {
MIST_FUNC_DEF(console,print,1),
};
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_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(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),
};
JSValue js_os_sys(JSContext *js, JSValue self, int argc, JSValue *argv)
{
return JS_NewString(js, SDL_GetPlatform());
}
JSC_CCALL(os_dump_atoms,
return js_dump_atoms(js);
)
JSC_CCALL(os_dump_shapes,
return js_dump_shapes(js);
)
// 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, 4);
if (!data)
return JS_ThrowReferenceError(js, "no known image type from pixel data: %s", stbi_failure_reason());
if (width <= 0 || height <= 0) {
free(data);
return JS_ThrowReferenceError(js, "decoded image has invalid size: %dx%d", width, height);
}
int pitch = width*4;
int fmt = SDL_PIXELFORMAT_RGBA32;
SDL_Surface *surf = SDL_CreateSurfaceFrom(width,height,fmt, data, pitch);
if (!surf) {
free(data);
return JS_ThrowReferenceError(js, "Error creating surface from data: %s",SDL_GetError());
}
ret = SDL_Surface2js(js,surf);
)
JSC_CCALL(graphics_surface_from_pixels,
int w, h, pitch;
SDL_PixelFormat fmt;
JS_GETATOM(js, w, argv[0], width, number)
JS_GETATOM(js, h, argv[0], height, number)
JS_GETATOM(js, fmt, argv[0], format, pixelformat)
JS_GETATOM(js, pitch, argv[0], pitch, number)
JSValue js_px = JS_GetPropertyStr(js, argv[0], "buffer");
size_t len;
void *raw = JS_GetArrayBuffer(js, &len, js_px);
JS_FreeValue(js, js_px);
if (!raw || !w || !h || !fmt)
return JS_ThrowReferenceError(js, "Invalid source.");
void *newraw = malloc(len);
memcpy(newraw, raw, len);
SDL_Surface *s = SDL_CreateSurfaceFrom(w, h, fmt, newraw, pitch);
if (!s)
return JS_ThrowInternalError(js, "Unable to create surface: %s", SDL_GetError());
return SDL_Surface2js(js, s);
)
// 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, "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);
)
// TODO: Implement this correctly
JSC_CCALL(os_make_cursor,
/*if (SDL_GetCurrentThreadID() != main_thread)
return JS_ThrowInternalError(js, "This can only be called from the root actor.");
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,
/*sprite *sp = make_sprite();
JS_GETATOM(js, sp->pos, argv[0], pos, vec2)
JS_GETATOM(js, sp->center, argv[0], center, vec2)
JS_GETATOM(js, sp->skew, argv[0], skew, vec2)
JS_GETATOM(js, sp->scale, argv[0], scale, vec2)
JS_GETATOM(js, sp->rotation, argv[0], rotation, angle)
JS_GETATOM(js, sp->layer, argv[0], layer, number)
JS_GETATOM(js, sp->color, argv[0], color, color)
sprite_apply(sp);
return sprite2js(js,sp);*/
)
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_ArrayLength(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_ArrayLength(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_ArrayLength(js,sprites);
for (int i = 0; i < len; i++) {
JSValue sub = JS_GetPropertyUint32(js,sprites,i);
transform *t;
JS_GETATOM(js,t,sub,transform,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_on,
prosperon_rt *rt = JS_GetContextOpaque(js);
JS_FreeValue(js, rt->on_exception);
rt->on_exception = JS_DupValue(js,argv[1]);
)
JSC_CCALL(os_clean_transforms,
clean_all(js);
)
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,"");
)
JSValue js_os_freemem(JSContext *js, JSValue self, int argc, JSValue *argv) {
#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
}
JSValue js_os_arch(JSContext *js, JSValue self, int argc, JSValue *argv) {
#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
}
JSValue js_os_version(JSContext *js, JSValue self, int argc, JSValue *argv) {
#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
return JS_UNDEFINED;
}
JSC_CCALL(os_createprocess,
int ac = JS_ArrayLength(js,argv[0]);
const char *args[ac+1];
for (int i = 0; i < ac; i++) {
JSValue astr = JS_GetPropertyUint32(js,argv[0],i);
args[i] = JS_ToCString(js,astr);
JS_FreeValue(js,astr);
}
args[ac] = NULL;
SDL_Process *actor = SDL_CreateProcess(args, 0);
for (int i = 0; i < ac; i++)
JS_FreeCString(js,args[i]);
if (!actor)
return JS_ThrowReferenceError(js, "Unable to create process: %s\n", SDL_GetError());
)
JSC_CCALL(os_createactor,
int margc = JS_ArrayLength(js, argv[0]);
char **margv = malloc(margc*sizeof(char*));
for (int i = 0; i < margc; i++) {
JSValue val = JS_GetPropertyUint32(js, argv[0], i);
const char *cstr = JS_ToCString(js,val);
margv[i] = strdup(cstr);
JS_FreeCString(js,cstr);
JS_FreeValue(js,val);
}
create_actor(margc, margv);
)
#include "qjs_wota.h"
JSC_CCALL(os_mailbox_push,
if (argc < 2) return JS_ThrowInternalError(js, "Need an actor and a message");
if (!JS_IsObject(argv[1])) return JS_ThrowInternalError(js, "Object to push must be an object.");
const char *id = JS_ToCString(js, argv[0]);
int exist = actor_exists(id);
JS_FreeCString(js,id);
if (!exist)
return JS_ThrowInternalError(js, "No mailbox found for given ID");
void *data = value2wota(js, argv[1], JS_UNDEFINED);
const char *err = send_message(id, data);
if (err) {
free(data);
return JS_ThrowInternalError(js, "Could not send message: %s", err);
}
)
JSC_CCALL(os_register_actor,
prosperon_rt *rt = JS_GetContextOpaque(js);
const char *id = JS_ToCString(js, argv[0]);
const char *err = register_actor(id, rt, JS_ToBool(js, argv[2]));
if (err) return JS_ThrowInternalError(js, "Could not register actor: %s", err);
rt->message_handle = JS_DupValue(js, argv[1]);
rt->context = js;
JS_FreeCString(js, id);
)
JSC_CCALL(os_mailbox_exist,
const char *id = JS_ToCString(js, argv[0]);
int exist = actor_exists(id);
JS_FreeCString(js, id);
return JS_NewBool(js, exist);
)
JSC_CCALL(os_unneeded,
prosperon_rt *actor = JS_GetContextOpaque(js);
SDL_LockMutex(actor->msg_mutex);
JS_FreeValue(js, actor->unneeded);
if (!JS_IsFunction(js, argv[0])) {
actor->unneeded = JS_UNDEFINED;
goto END;
}
actor->unneeded = JS_DupValue(js, argv[0]);
JS_ToFloat64(js, &actor->unneeded_secs, argv[1]);
END:
if (actor->ar) {
SDL_RemoveTimer(actor->ar);
actor->ar = 0;
}
set_actor_state(actor);
SDL_UnlockMutex(actor->msg_mutex);
)
JSValue js_os_get_trace(JSContext *js, JSValue self)
{
return JS_NewBool(js, trace);
}
JSValue js_os_set_trace(JSContext *js, JSValue self, JSValue value)
{
trace = JS_ToBool(js, value);
return JS_UNDEFINED;
}
JSC_CCALL(os_destroy,
prosperon_rt *rt = JS_GetContextOpaque(js);
rt->need_stop = 1;
)
JSC_CCALL(js_dump_class, return js_get_object_class_distribution(js))
JSC_CCALL(js_dump_type_overheads, return js_get_object_type_overheads(js))
JSC_CCALL(js_dump_objects, return js_dump_objects(js))
void cycle_hook_call(JSContext *js, JSValue v)
{
prosperon_rt *rt = JS_GetContextOpaque(js);
JS_FreeValue(js,JS_Call(js, rt->cycle_fn,JS_UNDEFINED,1,&v));
}
JSC_CCALL(js_cycle_hook,
if (JS_IsUndefined(argv[0]))
js_debug_sethook(js,NULL,JS_HOOK_CYCLE);
else {
prosperon_rt *rt = JS_GetContextOpaque(js);
JS_FreeValue(js,rt->cycle_fn);
rt->cycle_fn = JS_DupValue(js,argv[0]);
js_debug_sethook(js,cycle_hook_call, JS_HOOK_CYCLE);
}
)
JSC_CCALL(js_stack_info, return js_dump_stack_info(js))
JSC_CCALL(os_mem, return js_get_memory_usage(js))
#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)
JSJMEMRET(memory_used_size)
JSJMEMRET(gc_threshold)
JSJMEMRET(malloc_count)
JSJMEMRET(memory_used_count)
JSJMEMRET(atom_count)
JSJMEMRET(atom_size)
JSJMEMRET(str_count)
JSJMEMRET(str_size)
JSJMEMRET(obj_count)
JSJMEMRET(obj_size)
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_gc, JS_RunGC(JS_GetRuntime(js)) )
JSC_SSCALL(os_eval,
ret = JS_Eval(js,str,strlen(str),str2,JS_EVAL_TYPE_GLOBAL);
)
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))
JSC_CCALL(os_calc_mem,
JSMemoryUsage mu;
JS_ComputeMemoryUsage(JS_GetRuntime(js),&mu);
ret = JS_NewObject(js);
JS_SetPropertyStr(js,ret,"malloc_size",number2js(js,mu.malloc_size));
JS_SetPropertyStr(js,ret,"malloc_limit",number2js(js,mu.malloc_limit));
JS_SetPropertyStr(js,ret,"memory_used_size",number2js(js,mu.memory_used_size));
JS_SetPropertyStr(js,ret,"malloc_count",number2js(js,mu.malloc_count));
JS_SetPropertyStr(js,ret,"memory_used_count",number2js(js,mu.memory_used_count));
JS_SetPropertyStr(js,ret,"atom_count",number2js(js,mu.atom_count));
JS_SetPropertyStr(js,ret,"atom_size",number2js(js,mu.atom_size));
JS_SetPropertyStr(js,ret,"str_count",number2js(js,mu.str_count));
JS_SetPropertyStr(js,ret,"str_size",number2js(js,mu.str_size));
JS_SetPropertyStr(js,ret,"obj_count",number2js(js,mu.obj_count));
JS_SetPropertyStr(js,ret,"obj_size",number2js(js,mu.obj_size));
JS_SetPropertyStr(js,ret,"prop_count",number2js(js,mu.prop_count));
JS_SetPropertyStr(js,ret,"prop_size",number2js(js,mu.prop_size));
JS_SetPropertyStr(js,ret,"shape_count",number2js(js,mu.shape_count));
JS_SetPropertyStr(js,ret,"shape_size",number2js(js,mu.shape_size));
JS_SetPropertyStr(js,ret,"js_func_count",number2js(js,mu.js_func_count));
JS_SetPropertyStr(js,ret,"js_func_size",number2js(js,mu.js_func_size));
JS_SetPropertyStr(js,ret,"js_func_code_size",number2js(js,mu.js_func_code_size));
JS_SetPropertyStr(js,ret,"js_func_pc2line_count",number2js(js,mu.js_func_pc2line_count));
JS_SetPropertyStr(js,ret,"js_func_pc2line_size",number2js(js,mu.js_func_pc2line_size));
JS_SetPropertyStr(js,ret,"c_func_count",number2js(js,mu.c_func_count));
JS_SetPropertyStr(js,ret,"array_count",number2js(js,mu.array_count));
JS_SetPropertyStr(js,ret,"fast_array_count",number2js(js,mu.fast_array_count));
JS_SetPropertyStr(js,ret,"fast_array_elements",number2js(js,mu.fast_array_elements));
JS_SetPropertyStr(js,ret,"binary_object_count",number2js(js,mu.binary_object_count));
JS_SetPropertyStr(js,ret,"binary_object_size",number2js(js,mu.binary_object_size));
)
/*
JSC_CCALL(os_memstate,
#ifdef _WIN32
MEMORYSTATUSEX statex;
statex.dwLength = sizeof (statex);
GlobalMemoryStatusEx (&statex);
ret = JS_NewObject(js);
JS_SetPropertyStr(js,ret,"load",number2js(js,statex.dwMemoryLoad));
JS_SetPropertyStr(js,ret,"physical",number2js(js,statex.ullTotalPhys));
JS_SetPropertyStr(js,ret,"availphysical",number2js(js,statex.ullAvailPhys));
JS_SetPropertyStr(js,ret,"pagefile",number2js(js,statex.ullTotalPageFile));
JS_SetPropertyStr(js,ret,"availpagefile",number2js(js,statex.ullAvailPageFile));
JS_SetPropertyStr(js,ret,"virtual",number2js(js,statex.ullTotalVirtual));
JS_SetPropertyStr(js,ret,"availvirtual",number2js(js,statex.ullAvailVirtual));
JS_SetPropertyStr(js,ret,"availextended",number2js(js,statex.ullAvailExtendedVirtual));
#else
ret = JS_NewObject(js);
#endif
)*/
static const JSCFunctionListEntry js_js_funcs[] = {
MIST_FUNC_DEF(js, cycle_hook,1),
MIST_FUNC_DEF(os, dump_shapes, 0),
MIST_FUNC_DEF(os, dump_atoms,0),
MIST_FUNC_DEF(js, dump_class, 0),
MIST_FUNC_DEF(js, dump_objects, 0),
MIST_FUNC_DEF(js, dump_type_overheads, 0),
MIST_FUNC_DEF(js, stack_info, 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),
MIST_FUNC_DEF(os, rt_info, 0),
};
static const JSCFunctionListEntry js_util_funcs[] = {
MIST_FUNC_DEF(os, guid, 0),
MIST_FUNC_DEF(os, insertion_sort, 2),
};
JSC_CCALL(graphics_hsl_to_rgb,
float h, s, l;
JS_ToFloat64(js, &h, argv[0]);
JS_ToFloat64(js, &s, argv[1]);
JS_ToFloat64(js, &l, argv[2]);
float c = (1 - abs(2 * l - 1)) * s;
float x = c * (1 - abs(fmod((h/60),2) - 1));
float m = l - c / 2;
float r = 0, g = 0, b = 0;
if (h < 60) { r = c; g = x; }
else if (h < 120) { r = x; g = c; }
else if (h < 180) { g = c; b = x; }
else if (h < 240) { g = x; b = c; }
else if (h < 300) { r = x; b = c; }
else { r = c; b = x; }
return color2js(js, (colorf){r+m, g+m, b+m, 1});
)
JSC_CCALL(graphics_save_png,
const char *file = JS_ToCString(js, argv[0]);
int w, h, comp, pitch;
JS_ToInt32(js, &w, argv[1]);
JS_ToInt32(js, &h, argv[2]);
JS_ToInt32(js, &pitch, argv[4]);
size_t size;
void *data = JS_GetArrayBuffer(js, &size, argv[3]);
if (!stbi_write_png(file, w, h, 4, data, pitch))
return JS_ThrowInternalError(js, "Could not write png");
)
JSC_CCALL(graphics_save_jpg,
const char *file = JS_ToCString(js, argv[0]);
int w, h, comp, pitch, quality;
JS_ToInt32(js, &w, argv[1]);
JS_ToInt32(js, &h, argv[2]);
JS_ToInt32(js, &pitch, argv[4]);
JS_ToInt32(js, &quality, argv[5]);
if (!quality) quality = 80;
size_t size;
void *data = JS_GetArrayBuffer(js, &size, argv[3]);
if (!stbi_write_jpg(file, w, h, 4, data, quality))
return JS_ThrowInternalError(js, "Could not write png");
)
JSC_CCALL(gpu_make_sprite_queue,
sprite *sprites = NULL;
size_t quads = 0;
int needfree = 1;
if (JS_IsArrayBuffer(js, argv[0])) {
// test for fastest
size_t size;
sprite *sprites = JS_GetArrayBuffer(js, &size, argv[0]);
quads = size/sizeof(*sprites);
needfree = 0;
for (int i = 0; i < quads; i++)
JS_DupValue(js,sprites[i].image);
} else {
quads = JS_ArrayLength(js, argv[0]);
if (quads == 0)
return JS_ThrowReferenceError(js, "Expected an array of sprites with length > 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.pos, sub, pos, vec2)
JS_GETATOM(js, sp.center, sub, center, vec2)
JS_GETATOM(js, sp.skew, sub, skew, vec2)
JS_GETATOM(js, sp.scale, sub, scale, vec2)
JS_GETATOM(js, sp.color,sub,color,color)
JS_GETATOM(js, sp.layer,sub,layer,number)
sp.image = JS_GetProperty(js,sub,image);
sprite_apply(&sp);
arrput(sprites,sp);
}
JS_FreeValue(js, sub);
}
}
qsort(sprites, quads, sizeof(sprite), sort_sprite);
struct quad_buffers buffers = quad_buffers_new(quads*4);
const HMM_Vec2 local[4] = { {0,0}, {1,0}, {0,1}, {1,1} };
rect uv;
rect uv_px;
JSValue cur_img = JS_UNDEFINED;
for (size_t i = 0; i < quads; i++) {
sprite *s = &sprites[i];
if (JS_IsUndefined(cur_img) || !JS_StrictEq(js, s->image, cur_img)) {
cur_img = s->image;
JS_GETATOM(js, uv, cur_img, rect, rect)
JS_GETATOM(js, uv_px, cur_img, rect_px, rect)
}
HMM_Vec2 px_size = {
uv_px.w * s->scale.X,
uv_px.h * s->scale.Y
};
HMM_Vec2 anchor = {
px_size.X * s->center.X,
px_size.Y * s->center.Y
};
size_t base = i * 4;
for (int v = 0; v < 4; v++) {
HMM_Vec2 lp = {
local[v].X * px_size.X - anchor.X,
local[v].Y * px_size.Y - anchor.Y
};
HMM_Vec2 world = HMM_AddV2(s->pos, HMM_MulM2V2(s->affine, lp));
buffers.pos[base + v] = world;
buffers.color[base + v] = s->color;
}
/* UVs are still top-left-origin pixel coords, so keep previous packing */
buffers.uv[base + 0] = (HMM_Vec2){ uv.x, uv.y + uv.h };
buffers.uv[base + 1] = (HMM_Vec2){ uv.x + uv.w, uv.y + uv.h };
buffers.uv[base + 2] = (HMM_Vec2){ uv.x, uv.y };
buffers.uv[base + 3] = (HMM_Vec2){ uv.x + uv.w, uv.y };
}
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_ArrayLength(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,rect)
JS_GETATOM(js,color,sub,color,color)
JS_GETATOM(js,tr,sub,transform,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,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", pos_size, JS_TYPED_ARRAY_FLOAT32, 2, 1, 0);
BufferCheckResult uv_chk = get_or_extend_buffer(js, old_mesh, "uv", uv_size, JS_TYPED_ARRAY_FLOAT32, 2, 1, 0);
BufferCheckResult color_chk = get_or_extend_buffer(js, old_mesh, "color", 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_SetPropertyStr(js, ret, "pos", new_pos);
JS_SetPropertyStr(js, ret, "uv", new_uv);
JS_SetPropertyStr(js, ret, "color", new_color);
// Indices
JSValue indices = make_quad_indices_buffer(js, quads);
JS_SetPropertyStr(js, ret, "indices", 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_SetPropertyStr(js, ret, "pos", JS_DupValue(js, pos_chk.val));
JS_SetPropertyStr(js, ret, "uv", JS_DupValue(js, uv_chk.val));
JS_SetPropertyStr(js, ret, "color", 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_SetPropertyStr(js, ret, "indices", indices);
}
JS_SetPropertyStr(js, ret, "vertices", number2js(js, verts));
JS_SetPropertyStr(js, ret, "count", 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;
)
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, make_surface, 1),
MIST_FUNC_DEF(os, make_cursor, 1),
MIST_FUNC_DEF(os, make_font, 2),
MIST_FUNC_DEF(os, make_sprite, 1),
MIST_FUNC_DEF(os, make_line_prim, 5),
MIST_FUNC_DEF(graphics, hsl_to_rgb, 3),
MIST_FUNC_DEF(graphics, save_png, 4),
MIST_FUNC_DEF(graphics, save_jpg, 4),
MIST_FUNC_DEF(graphics, surface_from_pixels, 1),
};
static const JSCFunctionListEntry js_video_funcs[] = {
MIST_FUNC_DEF(os, make_video, 1),
};
JSC_SCALL(os_use_embed,
prosperon_rt *rt = JS_GetContextOpaque(js);
ModuleEntry *module_registry = rt->module_registry;
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,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,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;
}
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;
)
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),
};
#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_miniz_use(JSContext *js);
#ifdef TRACY_ENABLE
JSValue js_tracy_use(JSContext *js);
#endif
JSValue js_spline_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_spline_funcs,countof(js_spline_funcs));
return mod;
}
JSValue js_js_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_js_funcs,countof(js_js_funcs));
return mod;
}
JSValue js_graphics_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_graphics_funcs,countof(js_graphics_funcs));
return mod;
}
JSValue js_util_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_util_funcs,countof(js_util_funcs));
return mod;
}
JSValue js_video_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_video_funcs,countof(js_video_funcs));
return mod;
}
JSValue js_debug_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_debug_funcs,countof(js_debug_funcs));
return mod;
}
JSC_CCALL(os_value_id,
JS_SetPropertyStr(js, argv[0], "id", number2js(js, (uintptr_t)JS_VALUE_GET_PTR(argv[0])));
return argv[0];
)
#include "qjs_crypto.h"
#include "qjs_time.h"
#include "qjs_blob.h"
#include "qjs_http.h"
JSValue js_imgui_use(JSContext *js);
#define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use}
void ffi_load(JSContext *js)
{
prosperon_rt *rt = JS_GetContextOpaque(js);
m_seedRand(&rt->mrand, time(NULL));
arrput(rt->module_registry, ((ModuleEntry){"io", js_io_use}));
arrput(rt->module_registry, ((ModuleEntry){"os", js_os_use}));
arrput(rt->module_registry, ((ModuleEntry){"input", js_input_use}));
arrput(rt->module_registry, MISTLINE(time));
arrput(rt->module_registry, ((ModuleEntry){"math", js_math_use}));
arrput(rt->module_registry, MISTLINE(spline));
arrput(rt->module_registry, ((ModuleEntry){"geometry", js_geometry_use}));
arrput(rt->module_registry, MISTLINE(graphics));
arrput(rt->module_registry, MISTLINE(js));
arrput(rt->module_registry, MISTLINE(util));
arrput(rt->module_registry, MISTLINE(video));
arrput(rt->module_registry, MISTLINE(soloud));
arrput(rt->module_registry, MISTLINE(layout));
arrput(rt->module_registry, MISTLINE(miniz));
arrput(rt->module_registry, MISTLINE(imgui));
arrput(rt->module_registry, ((ModuleEntry){"camera", js_camera_use}));
arrput(rt->module_registry, MISTLINE(debug));
arrput(rt->module_registry, MISTLINE(dmon));
arrput(rt->module_registry, MISTLINE(nota));
arrput(rt->module_registry, MISTLINE(enet));
arrput(rt->module_registry, MISTLINE(qr));
arrput(rt->module_registry, MISTLINE(wota));
arrput(rt->module_registry, MISTLINE(crypto));
arrput(rt->module_registry, MISTLINE(blob));
arrput(rt->module_registry, MISTLINE(http));
arrput(rt->module_registry, ((ModuleEntry){"sdl_audio", js_sdl_audio_use}));
#ifdef TRACY_ENABLE
arrput(rt->module_registry, MISTLINE(tracy));
#endif
JSValue globalThis = JS_GetGlobalObject(js);
JSValue prosp = JS_NewObject(js);
JS_SetPropertyStr(js,globalThis,"prosperon", prosp);
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_Texture)
// Initialize extracted modules which handle their own class registrations
js_input_use(js); // Handles input
js_camera_use(js); // Handles camera
js_sdl_audio_use(js); // Handles SDL_AudioStream and other SDL classes
js_sdl_gpu_use(js); // Handles all GPU classes
js_io_use(js); // Handles PHYSFS_File
QJSCLASSPREP_FUNCS(renderer_ctx)
QJSCLASSPREP_FUNCS(font);
QJSCLASSPREP_FUNCS(datastream);
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));
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);
JSValue args = JS_NewArray(js);
for (int i = 0; i < rt->cmd.argc; i++)
JS_SetPropertyUint32(js,args, i, JS_NewString(js,rt->cmd.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_FreeValue(js,globalThis);
}