#include "jsffi.h" #include "font.h" #include "datastream.h" #include "qjs_sdl.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 #include #include #include #include #include #include #include "render.h" #include "model.h" #include "HandmadeMath.h" #include "par/par_streamlines.h" #include "par/par_shapes.h" #include #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" #include "qjs_actor.h" #include "qjs_rtree.h" #include "qjs_spline.h" #include "qjs_js.h" #include "qjs_debug.h" #ifndef NSTEAM #include "qjs_steam.h" #endif SDL_Window *global_window; #include void gui_input(SDL_Event *e); #ifdef _WIN32 #include #else #include #include #ifdef __linux__ #include #endif #endif #include "wildmatch.h" #include "freelist.h" #include "sprite.h" #include #include #include #include #include #include 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 //#else //#include #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->indexindex++) { 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; kkmt[kk] & UPPER_MASK) | (rand->mt[kk+1] & LOWER_MASK); rand->mt[kk] = rand->mt[kk+STATE_VECTOR_M] ^ (y >> 1) ^ mag[y & 0x1]; } for(; kkmt[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 #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)); ) 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); ) 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_Init(SDL_INIT_VIDEO)) return JS_ThrowInternalError(js, "Unable to initialize video subsystem: %s", SDL_GetError()); 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(console_print, printf("%s", str); ) static const JSCFunctionListEntry js_console_funcs[] = { MIST_FUNC_DEF(console,print,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), }; // 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_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); } ) 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"); ) int sort_sprite(const sprite *a, const sprite *b) { if (a->layer != b->layer) return a->layer - b->layer; if (a->pos.Y != b->pos.Y) return (b->pos.Y - a->pos.Y); if (JS_VALUE_GET_PTR(a->image) != JS_VALUE_GET_PTR(b->image)) return JS_VALUE_GET_PTR(a->image) < JS_VALUE_GET_PTR(b->image) ? -1 : 1; return 0; } 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); ) typedef struct { JSValue val; void *ptr; size_t size; int need_new; } BufferCheckResult; static BufferCheckResult get_or_extend_buffer( JSContext *js, JSValue old_mesh, const char *prop, size_t needed_size, int type, int elements_per_item, int copy, int index ) { BufferCheckResult res = { JS_UNDEFINED, NULL, 0, 0 }; if (!JS_IsUndefined(old_mesh)) { JSValue old_buf = JS_GetProperty(js, old_mesh, prop); if (!JS_IsUndefined(old_buf)) { size_t old_size; void *data = get_gpu_buffer(js, old_buf, NULL, &old_size); if (data && old_size >= needed_size) { // Old buffer is large enough res.val = old_buf; // keep it res.ptr = data; res.size = old_size; return res; } JS_FreeValue(js, old_buf); } } // If we reach here, we need a new buffer res.need_new = 1; return res; } static HMM_Vec3 base_quad[4] = { {0.0,0.0,1.0}, {1.0,0.0,1.0}, {0.0,1.0,1.0}, {1.0,1.0,1.0} }; 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_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_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; } } ) 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); ) #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_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_console_use(JSContext *js) { JSValue mod = JS_NewObject(js); JS_SetPropertyFunctionList(js,mod,js_console_funcs,countof(js_console_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){"actor", js_actor_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})); arrput(rt->module_registry, MISTLINE(console)); arrput(rt->module_registry, MISTLINE(rtree)); arrput(rt->module_registry, MISTLINE(sprite)); arrput(rt->module_registry, MISTLINE(transform)); #ifdef TRACY_ENABLE arrput(rt->module_registry, MISTLINE(tracy)); #endif #ifndef NSTEAM arrput(rt->module_registry, MISTLINE(steam)); #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(SDL_Window) QJSCLASSPREP_FUNCS(SDL_Surface) QJSCLASSPREP_FUNCS(SDL_Texture) 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); }