From dab13a1f5403cab0bf35073e5a5eae61d64fee2f Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Mon, 13 Jan 2025 15:35:04 -0600 Subject: [PATCH] improve sprite render speed; particle rendering --- scripts/components.js | 35 ++-- scripts/particle.js | 34 +--- scripts/render.js | 85 +++++++-- scripts/std.js | 2 +- source/jsffi.c | 391 ++++++++++++++++++++++++++++++++++-------- source/sprite.h | 5 +- source/transform.c | 29 +++- source/transform.h | 11 +- 8 files changed, 449 insertions(+), 143 deletions(-) diff --git a/scripts/components.js b/scripts/components.js index 5fae1ecb..78608e7f 100644 --- a/scripts/components.js +++ b/scripts/components.js @@ -12,21 +12,6 @@ function make_point_obj(o, p) { }; }; -function sprite_addbucket(sprite) { - if (!sprite.image) return; - var layer = sprite.z_value(); - sprite_buckets[layer] ??= {}; - sprite_buckets[layer][sprite.image.texture] ??= []; - sprite_buckets[layer][sprite.image.texture].push(sprite); - sprite._oldlayer = layer; - sprite._oldtex = sprite.image.texture; -}; - -function sprite_rmbucket(sprite) { - if (sprite._oldlayer && sprite._oldtex) sprite_buckets[sprite._oldlayer][sprite._oldtex].remove(sprite); -// else for (var layer of Object.values(sprite_buckets)) for (var path of Object.values(layer)) path.remove(sprite); -}; - /* an anim is simply an array of images */ /* an anim set is like this frog = { @@ -40,16 +25,22 @@ var sprite = { image: undefined, get diffuse() { return this.image; }, set diffuse(x) {}, - z_value() {return 100000 + this.gameobject.drawlayer * 1000 - this.gameobject.pos.y;}, anim_speed: 1, play(str, loop = true, reverse = false, fn) { if (!this.animset) { // console.warn(`Sprite has no animset when trying to play ${str}`); - return parseq.imm(); + fn?.(); + return; +// return parseq.imm(); } - if (typeof str === 'string') + if (typeof str === 'string') { + if (!this.animset[str]) { + fn?.(); + return; + } this.anim = this.animset[str]; + } var playing = this.anim; @@ -140,7 +131,6 @@ var sprite = { return this._p; }, kill: function kill() { - sprite_rmbucket(this); this.del_anim?.(); this.anim = undefined; this.gameobject = undefined; @@ -148,11 +138,7 @@ var sprite = { }, anchor: [0, 0], sync: function sync() { - var layer = this.z_value(); - if (layer === this._oldlayer && this.image.texture === this._oldtex) return; - - sprite_rmbucket(this); - sprite_addbucket(this); + this.layer = this.gameobject.drawlayer; }, pick() { return this; @@ -249,7 +235,6 @@ component.sprite = function (obj) { sp.transform.parent = obj.transform; sp.guid = prosperon.guid(); allsprites.push(sp); - sprite_addbucket(sp); return sp; }; diff --git a/scripts/particle.js b/scripts/particle.js index a52183fc..3542e840 100644 --- a/scripts/particle.js +++ b/scripts/particle.js @@ -74,6 +74,9 @@ emitter.step = function step(dt) { this.particles.remove(p); } } + + for (var p of this.particles) + p.transform.clean(); }; emitter.burst = function (count, t) { @@ -84,8 +87,6 @@ var emitters = []; var make_emitter = function () { var e = Object.create(emitter); -// e.shape = shape.centered_quad; -// e.shader = "shaders/baseparticle.cg"; e.particles = []; e.dead = []; emitters.push(e); @@ -96,31 +97,6 @@ function update_emitters(dt) { for (var e of emitters) e.step(dt); } -var arr = []; -function draw_emitters() { - var buckets = {}; - var base = 0; - for (var e of emitters) { - var bucket = buckets[e.diffuse.path]; - if (!bucket) - buckets[e.diffuse.path] = [e]; - else - bucket.push(e); - } - - for (var path in buckets) { - arr.length = 0; - var bucket = buckets[path]; - for (var e of bucket) { - if (e.particles.length === 0) continue; - for (var p of e.particles) arr.push(p); - } - var sprite_mesh = os.make_sprite_mesh(arr); - render.geometry(bucket[0], sprite_mesh); - base += arr.length; - } -} - function stat_emitters() { var stat = {}; @@ -131,4 +107,6 @@ function stat_emitters() return stat; } -return { make_emitter, update_emitters, draw_emitters, stat_emitters }; +function all_emitters() { return emitters; } + +return { make_emitter, update_emitters, stat_emitters, all_emitters }; diff --git a/scripts/render.js b/scripts/render.js index ca1626b5..39670f91 100644 --- a/scripts/render.js +++ b/scripts/render.js @@ -496,8 +496,9 @@ try{ var buffers = []; buffers = buffers.concat(queue_sprite_mesh(render_queue)); - for (var q of render_queue) - if (q.type === 'geometry') buffers = buffers.concat([q.mesh.pos, q.mesh.color,q.mesh.uv,q.mesh.indices]); + var unique_meshes = [...new Set(render_queue.map(x => x.mesh))]; + for (var q of unique_meshes) + buffers = buffers.concat([q.pos, q.color,q.uv,q.indices]); buffers = buffers.concat(queue_sprite_mesh(hud_queue)); for (var q of hud_queue) @@ -743,25 +744,63 @@ render.draw_hud = true; render.draw_gui = true; render.draw_gizmos = true; +function insertion_sort(arr, cmp) +{ + for (let i = 1; i < arr.length; i++) { + let key = arr[i] + let j = i - 1 + while (j >= 0 && cmp(arr[j], key) > 0) + arr[j + 1] = arr[j--] + arr[j + 1] = key + } + return arr +} + function sprites_to_queue(sprites, ysort = false) { + var sprites = allsprites; + for (var i = 0; i < sprites.length; i++) + sprites[i].transform.clean(); +// var sprites = os.cull_sprites(allsprites, prosperon.camera); + + os.insertion_sort(sprites,render._main.sort_sprite) +// sprites.sort(render._main.sort_sprite) + + var mesh = render._main.make_sprite_mesh(sprites); + var queue = []; - for (var l in sprites) { - var layer = sprites[l] - for (var image in layer) { - var sparr = layer[image] - if (sparr.length === 0) continue; - var mesh = render._main.make_sprite_mesh(sparr); - queue.push({ - type: 'geometry', + var idx = 0; + var image; + var first_index = 0; + var count = 0; + + for (var i = 0; i < sprites.length; i++) { + var spr = sprites[i]; + if (spr.image !== image) { + if (count > 0) queue.push({ + type:'geometry', mesh, pipeline:sprite_pipeline, - image:sparr[0].image, - first_index:0, - num_indices:mesh.num_indices + image, + first_index, + num_indices:count*6 }); - } + + image = spr.image; + first_index = i*6; + count = 1; + } else count++ } + + if (count > 0) queue.push({ + type:'geometry', + mesh, + pipeline:sprite_pipeline, + image, + first_index, + num_indices: count*6 + }); + return queue; } @@ -853,6 +892,20 @@ render.rectangle = function render_rectangle(rect, color = Color.white, pipeline }); }; +render.particles = function render_particles(emitter, pipeline = sprite_pipeline) +{ + var mesh = render._main.make_sprite_mesh(emitter.particles); + if (mesh.num_indices === 0) return; + current_queue.push({ + type:'geometry', + mesh, + image:emitter.diffuse, + pipeline, + first_index:0, + num_indices:mesh.num_indices + }); +} + render.text = function text(text, rect, font = prosperon.font, size = 0, color = Color.white, wrap = 0, pipeline = sprite_pipeline) { if (typeof font === 'string') font = render.get_font(font) @@ -1275,7 +1328,7 @@ try { } catch(e) { console.error(e); } } -var waittime = 1/60; +var waittime = 1/240; var last_frame_time = 0; // Ran once per frame prosperon.process = function process() { @@ -1320,6 +1373,8 @@ try { current_queue = render_queue; try { prosperon.draw(); } catch(e) { console.error(e) } + for (var e of all_emitters()) + render.particles(e); current_queue = hud_queue; try { prosperon.hud(); } catch(e) { console.error(e) } try { imgui_fn(); } catch(e) { console.error(e) } diff --git a/scripts/std.js b/scripts/std.js index 9220cd33..169907e6 100644 --- a/scripts/std.js +++ b/scripts/std.js @@ -271,7 +271,7 @@ Cmdline.register_order( render._main = prosperon.window.make_gpu(false, driver); render._main.window = prosperon.window; render._main.claim_window(prosperon.window); - render._main.set_swapchain("sdr", "immediate"); + render._main.set_swapchain("sdr", "mailbox"); var tt = game.texture('moon'); tt.texture.__proto__.toString = function() { return os.value_id(this); } diff --git a/source/jsffi.c b/source/jsffi.c index 3187e061..a9f865ba 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -58,6 +58,9 @@ static JSAtom src_atom; static JSAtom count_atom; static JSAtom num_indices_atom; static JSAtom transform_atom; +static JSAtom image_atom; +static JSAtom layer_atom; +static JSAtom parent_atom; // GPU ATOMS static JSAtom cw_atom; @@ -240,6 +243,11 @@ JSValue VALUE##__##PROP##__v = JS_GetPropertyStr(JS,VALUE,#PROP); \ TARGET = js2##TYPE(JS, VALUE##__##PROP##__v); \ JS_FreeValue(JS,VALUE##__##PROP##__v); }\ +#define JS_GETATOM(JS, TARGET, VALUE, ATOM, TYPE) {\ +JSValue VALUE##__##PROP##__v = JS_GetProperty(JS,VALUE,ATOM); \ +TARGET = js2##TYPE(JS, VALUE##__##PROP##__v); \ +JS_FreeValue(JS,VALUE##__##PROP##__v); }\ + int JS_GETBOOL(JSContext *js, JSValue v, const char *prop) { JSValue __v = JS_GetPropertyStr(js,v,prop); @@ -1142,16 +1150,40 @@ int js_arrlen(JSContext *js,JSValue v) { return len; } +static inline int js_transform_dirty_chain(JSContext *js, JSValue v) +{ + transform *t = js2transform(js, v); + if (!t) return 0; // no transform => assume not dirty + + if (t->dirty) return 1; + + JSValue parentVal = JS_GetProperty(js, v, parent_atom); + if (JS_IsObject(parentVal)) { + int r = js_transform_dirty_chain(js, parentVal); + JS_FreeValue(js, parentVal); + return r; + } + + JS_FreeValue(js, parentVal); + return 0; +} + static inline HMM_Mat3 js2transform_mat3(JSContext *js, JSValue v) { transform *T = js2transform(js,v); - transform *P = js2transform(js,js_getpropertystr(js,v,"parent")); + if (!js_transform_dirty_chain(js,v)) return T->gcache3; + if (!T) return HMM_M3D(1); + transform *P; + JS_GETATOM(js,P,v,parent_atom,transform); if (P) { HMM_Mat3 pm = transform2mat3(P); HMM_Mat3 tm = transform2mat3(T); - return HMM_MulM3(pm,tm); - } - return transform2mat3(T); + T->gcache3 = HMM_MulM3(pm,tm); + + } else + T->gcache3 = transform2mat3(T); + + return T->gcache3; } // Unpacks a typed array javascript object. If it has a gpu property, returns it, too. Otherwise, if requested, makes one. @@ -1218,6 +1250,7 @@ double js2angle(JSContext *js,JSValue v) { 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]); @@ -1407,6 +1440,7 @@ int js_print_exception(JSContext *js, JSValue v) } rect js2rect(JSContext *js,JSValue v) { + if (JS_IsUndefined(v)) return (rect){0,0,1,1}; rect rect; rect.w = js_getnum(js,v,width_atom); rect.h = js_getnum(js,v,height_atom); @@ -3294,7 +3328,8 @@ JSC_CCALL(renderer_make_sprite_mesh, // Calculate the base index for the current quad size_t base = i * 4; - HMM_Mat3 trmat = js2transform_mat3(js,jstransform); + + HMM_Mat3 trmat = tr->gcache3; HMM_Vec3 base_quad[4] = { {0.0,0.0,1.0}, @@ -3902,6 +3937,147 @@ static HMM_Vec3 base_quad[4] = { {1.0,1.0,1.0} }; +static inline void add_quad(text_vert **verts, rect *restrict src, rect *restrict dst) +{ + text_vert v = (text_vert){ + .pos = (HMM_Vec2){dst->x, dst->y}, + .uv = (HMM_Vec2){src->x,src->y}, + .color = (HMM_Vec4){1,1,1,1} + }; + arrput(*verts, v); + + v = (text_vert){ + .pos = (HMM_Vec2){dst->x+dst->w, dst->y}, + .uv = (HMM_Vec2){src->x+src->w,src->y}, + .color = (HMM_Vec4){1,1,1,1} + }; + arrput(*verts, v); + + v = (text_vert){ + .pos = (HMM_Vec2){dst->x, dst->y+dst->h}, + .uv = (HMM_Vec2){src->x,src->y+src->h}, + .color = (HMM_Vec4){1,1,1,1} + }; + arrput(*verts, v); + + v = (text_vert){ + .pos = (HMM_Vec2){dst->x+dst->w, dst->y+dst->h}, + .uv = (HMM_Vec2){src->x+src->w,src->y+src->h}, + .color = (HMM_Vec4){1,1,1,1} + }; + arrput(*verts, v); +} + +typedef struct { + text_vert vert[4]; +} quad; + +typedef struct { + quad quad; + JSValue image; + int layer; + JSContext *js; + JSValue sprite; +} sprite; + +int sort_sprite(const sprite *a, const sprite *b) +{ + if (a->layer != b->layer) return a->layer - b->layer; + + if (a->quad.vert[0].pos.y != b->quad.vert[0].pos.y) return b->quad.vert[0].pos.y - a->quad.vert[0].pos.y; + +// if (!JS_SameValue(a->js, a->image, b->image)) return JS_VALUE_GET_PTR(a->image) < JS_VALUE_GET_PTR(b->image) ? -1 : 1; + + return 0; +} + +JSC_CCALL(gpu_sort_sprite, + JSValue a = argv[0]; + JSValue b = argv[1]; + + int alayer, blayer; + JS_GETATOM(js,alayer,a,layer_atom,number) + JS_GETATOM(js,blayer,b,layer_atom,number) + if (alayer != blayer) return number2js(js,alayer - blayer); + + transform *atr, *btr; + JS_GETATOM(js,atr,a,transform_atom,transform) + JS_GETATOM(js,btr,b,transform_atom,transform); + if (atr->gcache3.Columns[2].y != btr->gcache3.Columns[2].y) return number2js(js,btr->gcache3.Columns[2].y - atr->gcache3.Columns[2].y); + + JSValue aimg,bimg; + aimg = JS_GetProperty(js,a,image_atom); + bimg = JS_GetProperty(js,b,image_atom); + JS_FreeValue(js,aimg); + JS_FreeValue(js,bimg); + if (!JS_SameValue(js,aimg,bimg)) return number2js(js,JS_VALUE_GET_PTR(aimg) < JS_VALUE_GET_PTR(bimg) ? -1 : 1); + return number2js(js,0); +) + +JSC_CCALL(gpu_make_sprite_queue, + size_t quads = js_arrlen(js, argv[0]); + + // Reserve an array of 'sprites' if needed, but watch for out-of-bounds + sprite *sprites = NULL; + arrsetlen(sprites, quads); + + for (int i = 0; i < quads; i++) { + JSValue sub = JS_GetPropertyUint32(js, argv[0], i); + rect src; + HMM_Vec4 color; + + JS_GETATOM(js, src, sub, src_atom, rect) + JS_GETATOM(js, color, sub, color_atom, color) + + JSValue jstransform = JS_GetPropertyStr(js, sub, "transform"); + + quad sprite_quad; + transform *t; + JS_GETATOM(js,t,sub,transform_atom,transform) + HMM_Mat3 trmat = t->gcache3; + + // Transform the base_quad's 4 points into sprite_quad + for (int j = 0; j < 4; j++) + sprite_quad.vert[j].pos = HMM_MulM3V3(trmat, base_quad[j]).xy; + + // Use [0..3] for uv and color + sprite_quad.vert[0].uv = (HMM_Vec2){ src.x, src.y + src.h }; + sprite_quad.vert[1].uv = (HMM_Vec2){ src.x+src.w, src.y + src.h }; + sprite_quad.vert[2].uv = (HMM_Vec2){ src.x, src.y }; + sprite_quad.vert[3].uv = (HMM_Vec2){ src.x+src.w, src.y }; + + sprite_quad.vert[0].color = color; + sprite_quad.vert[1].color = color; + sprite_quad.vert[2].color = color; + sprite_quad.vert[3].color = color; + + // If you need to store into a bigger array: + // sprites[i].quad = sprite_quad; + // etc. + sprite sp; + sp.quad = sprite_quad; + sp.image = JS_GetPropertyStr(js,sub,"image"); + sp.js = js; + JS_GETPROP(js,sp.layer,sub,layer,number) + sprites[i] = sp; + sprites[i].sprite = JS_DupValue(js,sub); + + JS_FreeValue(js, sub); + JS_FreeValue(js, jstransform); + JS_FreeValue(js, sp.image); + } + + qsort(sprites, arrlen(sprites),sizeof(sprite),sort_sprite); + + + ret = JS_NewArray(js); + + for (int i = 0; i < quads; i++) + JS_SetPropertyUint32(js,ret,i,sprites[i].sprite); + + arrfree(sprites); +) + JSC_CCALL(gpu_make_sprite_mesh, size_t quads = js_arrlen(js, argv[0]); size_t verts = quads*4; @@ -3914,25 +4090,17 @@ JSC_CCALL(gpu_make_sprite_mesh, for (int i = 0; i < quads; i++) { JSValue sub = JS_GetPropertyUint32(js,argv[0],i); - JSValue jstransform = JS_GetProperty(js,sub,transform_atom); - transform *tr = js2transform(js,jstransform); - JSValue jssrc = JS_GetProperty(js,sub,src_atom); - JSValue jscolor = JS_GetProperty(js,sub,color_atom); - + transform *tr; rect src; - if (JS_IsUndefined(jssrc)) - src = (rect){0,0,1,1}; - else - src = js2rect(js,jssrc); - HMM_Vec4 color; - if (JS_IsUndefined(jscolor)) - color = (HMM_Vec4){1,1,1,1}; - else - color = js2vec4(js,jscolor); + JS_GETATOM(js,src,sub,src_atom,rect) + JS_GETATOM(js,color,sub,color_atom,color) + JS_GETATOM(js,tr,sub,transform_atom,transform) + JS_FreeValue(js,sub); size_t base = i*4; - HMM_Mat3 trmat = js2transform_mat3(js,jstransform); + HMM_Mat3 trmat = tr->gcache3; +// HMM_Mat3 trmat = transform2mat3(tr); for (int j = 0; j < 4; j++) posdata[base+j] = HMM_MulM3V3(trmat, base_quad[j]).xy; @@ -3945,11 +4113,6 @@ JSC_CCALL(gpu_make_sprite_mesh, colordata[base+1] = color; colordata[base+2] = color; colordata[base+3] = color; - - JS_FreeValue(js,jstransform); - JS_FreeValue(js,sub); - JS_FreeValue(js,jscolor); - JS_FreeValue(js,jssrc); } // Check old mesh @@ -4314,37 +4477,6 @@ JSC_CCALL(gpu_compute_pipeline, return SDL_GPUComputePipeline2js(js,pipeline); ) -static inline void add_quad(text_vert **verts, rect *restrict src, rect *restrict dst) -{ - text_vert v = (text_vert){ - .pos = (HMM_Vec2){dst->x, dst->y}, - .uv = (HMM_Vec2){src->x,src->y}, - .color = (HMM_Vec4){1,1,1,1} - }; - arrput(*verts, v); - - v = (text_vert){ - .pos = (HMM_Vec2){dst->x+dst->w, dst->y}, - .uv = (HMM_Vec2){src->x+src->w,src->y}, - .color = (HMM_Vec4){1,1,1,1} - }; - arrput(*verts, v); - - v = (text_vert){ - .pos = (HMM_Vec2){dst->x, dst->y+dst->h}, - .uv = (HMM_Vec2){src->x,src->y+src->h}, - .color = (HMM_Vec4){1,1,1,1} - }; - arrput(*verts, v); - - v = (text_vert){ - .pos = (HMM_Vec2){dst->x+dst->w, dst->y+dst->h}, - .uv = (HMM_Vec2){src->x+src->w,src->y+src->h}, - .color = (HMM_Vec4){1,1,1,1} - }; - arrput(*verts, v); -} - static inline void tile_region(text_vert **verts, rect src_uv, rect dst, float tex_w, float tex_h, bool tile_x, bool tile_y) { // Convert the incoming UV rect into pixel coords @@ -4652,10 +4784,12 @@ static const JSCFunctionListEntry js_SDL_GPUDevice_funcs[] = { MIST_FUNC_DEF(gpu, make_pipeline, 1), // loads pipeline state into an object MIST_FUNC_DEF(gpu,compute_pipeline,1), MIST_FUNC_DEF(gpu, set_swapchain, 2), + MIST_FUNC_DEF(gpu,sort_sprite,2), MIST_FUNC_DEF(gpu, make_sampler,1), MIST_FUNC_DEF(gpu, load_texture, 2), MIST_FUNC_DEF(gpu, texture, 1), MIST_FUNC_DEF(gpu, make_sprite_mesh, 2), + MIST_FUNC_DEF(gpu, make_sprite_queue, 1), MIST_FUNC_DEF(gpu, make_quad, 0), MIST_FUNC_DEF(gpu, driver, 0), MIST_FUNC_DEF(gpu, make_shader, 1), @@ -4900,6 +5034,7 @@ JSC_CCALL(cmd_push_compute_uniform_data, ) JSC_CCALL(cmd_submit, + Uint64 start = SDL_GetTicksNS(); SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js,self); SDL_GPUFence *fence = SDL_SubmitGPUCommandBufferAndAcquireFence(cmds); return SDL_GPUFence2js(js,fence); @@ -4922,10 +5057,9 @@ JSC_CCALL(cmd_hud, SDL_PushGPUVertexUniformData(cmds, js2number(js,argv[1]), &data, sizeof(data)); ) -JSC_CCALL(cmd_camera, - SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self); - JSValue camera = argv[0]; - +shader_globals camera_globals(JSContext *js, JSValue camera) +{ + shader_globals data = {0}; HMM_Vec2 size; transform *transform; double fov; @@ -4944,8 +5078,6 @@ JSC_CCALL(cmd_camera, HMM_Mat4 proj; HMM_Mat4 view; - - shader_globals data = {0}; if (ortho) { proj = HMM_Orthographic_RH_NO( @@ -4977,6 +5109,12 @@ JSC_CCALL(cmd_camera, data.viewport_size = (HMM_Vec2){0.5,0.5}; data.viewport_offset = (HMM_Vec2){0,0}; data.time = SDL_GetTicksNS() / 1000000000.0f; + return data; +} + +JSC_CCALL(cmd_camera, + SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self); + shader_globals data = camera_globals(js, argv[0]); SDL_PushGPUVertexUniformData(cmds, js2number(js,argv[1]), &data, sizeof(data)); ) @@ -5626,9 +5764,9 @@ static const JSCFunctionListEntry js_io_funcs[] = { MIST_FUNC_DEF(io, gamemode, 2), }; -JSC_GETSET(transform, pos, vec3) -JSC_GETSET(transform, scale, vec3f) -JSC_GETSET(transform, rotation, quat) +JSC_GETSET_APPLY(transform, pos, vec3) +JSC_GETSET_APPLY(transform, scale, vec3f) +JSC_GETSET_APPLY(transform, rotation, quat) JSC_CCALL(transform_move, transform *t = js2transform(js,self); transform_move(t, js2vec3(js,argv[0])); @@ -5647,7 +5785,7 @@ JSC_CCALL(transform_rotate, transform *t = js2transform(js,self); HMM_Quat rot = HMM_QFromAxisAngle_RH(axis, js2angle(js,argv[1])); t->rotation = HMM_MulQ(t->rotation,rot); - t->dirty = true; + transform_apply(t); ) JSC_CCALL(transform_angle, @@ -5672,6 +5810,7 @@ JSC_CCALL(transform_phys2d, transform_move(t, (HMM_Vec3){v.x*dt,v.y*dt,0}); HMM_Quat rot = HMM_QFromAxisAngle_RH((HMM_Vec3){0,0,1}, av*dt); t->rotation = HMM_MulQ(t->rotation, rot); + transform_apply(t); ) JSC_CCALL(transform_unit, @@ -5679,6 +5818,7 @@ JSC_CCALL(transform_unit, t->pos = v3zero; t->rotation = QUAT1; t->scale = v3one; + transform_apply(t); ) JSC_CCALL(transform_trs, @@ -5686,6 +5826,7 @@ JSC_CCALL(transform_trs, t->pos = JS_IsUndefined(argv[0]) ? v3zero : js2vec3(js,argv[0]); t->rotation = JS_IsUndefined(argv[1]) ? QUAT1 : js2quat(js,argv[1]); t->scale = JS_IsUndefined(argv[2]) ? v3one : js2vec3(js,argv[2]); + transform_apply(t); ) JSC_CCALL(transform_rect, @@ -5694,6 +5835,7 @@ JSC_CCALL(transform_rect, t->pos = (HMM_Vec3){r.x,r.y,0}; t->scale = (HMM_Vec3){r.w,r.h,1}; t->rotation = QUAT1; + transform_apply(t); ) JSC_CCALL(transform_array, @@ -5704,10 +5846,35 @@ JSC_CCALL(transform_array, JS_SetPropertyUint32(js,ret,i, number2js(js,m.em[i])); ) +/*static JSValue js_transform_get_parent(JSContext *js, JSValueConst self) +{ + return JS_GetProperty(js,self,parent_atom); +} + +static JSValue js_transform_set_parent(JSContext *js, JSValueConst self, JSValue v) +{ + transform *t = js2transform(js,self); + if (t->parent) return JS_UNDEFINED; + transform *p = js2transform(js,v); + if (!p) { + JS_SetProperty(js,self,parent_atom,JS_UNDEFINED); + t->parent = NULL; + } + + JS_SetProperty(js,self,parent_atom,JS_DupValue(js,v)); + t->parent = p; +} +*/ + +JSC_CCALL(transform_clean, + js2transform_mat3(js,self); +) + static const JSCFunctionListEntry js_transform_funcs[] = { CGETSET_ADD(transform, pos), CGETSET_ADD(transform, scale), CGETSET_ADD(transform, rotation), +// CGETSET_ADD(transform, parent), MIST_FUNC_DEF(transform, trs, 3), MIST_FUNC_DEF(transform, phys2d, 3), MIST_FUNC_DEF(transform, move, 1), @@ -5718,6 +5885,7 @@ static const JSCFunctionListEntry js_transform_funcs[] = { MIST_FUNC_DEF(transform, unit, 0), MIST_FUNC_DEF(transform, rect, 1), MIST_FUNC_DEF(transform, array, 0), + MIST_FUNC_DEF(transform, clean, 0), }; JSC_CCALL(datastream_time, return number2js(js,plm_get_time(js2datastream(js,self)->plm)); ) @@ -6737,6 +6905,37 @@ JSC_CCALL(os_battery_seconds, return number2js(js,seconds); ) +JSC_CCALL(os_insertion_sort, + JSValue arr = argv[0]; + JSValue cmp = argv[1]; + int len = js_arrlen(js, arr); + + for (int i = 1; i < len; i++) { + JSValue key = JS_GetPropertyUint32(js, arr, i); + int j = i - 1; + + while (j >= 0) { + JSValue arr_j = JS_GetPropertyUint32(js, arr, j); + + JSValue ret = JS_Call(js, cmp, JS_UNDEFINED, 2, (JSValue[]){ arr_j, key }); + 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) { @@ -6750,6 +6949,60 @@ JSC_CCALL(os_power_state, return JS_UNDEFINED; ) +JSC_CCALL(os_cull_sprite, + JSValue sprite = argv[0]; + JSValue camera = argv[1]; +) + +JSC_CCALL(os_cull_sprites, + ret = JS_NewArray(js); + int n = 0; + + JSValue sprites = argv[0]; + shader_globals info = camera_globals(js,argv[1]); + + int len = js_arrlen(js,sprites); + + HMM_Vec2 corners[4] = { + (HMM_Vec2){0,0}, + (HMM_Vec2){1,0}, + (HMM_Vec2){0,1}, + (HMM_Vec2){1,1}, + }; + + for (int i = 0; i < len; i++) { + JSValue sub = JS_GetPropertyUint32(js,sprites, i); + transform *t; + JS_GETATOM(js,t,sub,transform_atom,transform) + HMM_Mat3 trmat = t->gcache3; + int outside = 0; + + for (int j = 0; j < 4; j++) { + Uint64 start = SDL_GetTicksNS(); + HMM_Vec3 corner = HMM_MulM3V3(trmat, (HMM_Vec3){corners[j].x, corners[j].y, 1.0}); + HMM_Vec4 clip = HMM_MulM4V4(info.world_to_projection, (HMM_Vec4){corner.x,corner.y,corner.z,1.0}); + if (clip.w <= 0.0) { + outside++; + continue; + } + + float nx = clip.x/clip.w; + float ny = clip.y/clip.w; + float nz = clip.z/clip.w; + + if (nx < -1 || nx > 1) outside++; + else if (ny < -1 || ny > 1) outside++; + else if (nz < -1 || nz > 1) outside++; + } + + if (outside != 4) { + JS_SetPropertyUint32(js,ret,n,JS_DupValue(js,sub)); + n++; + } + JS_FreeValue(js,sub); + } +) + static const JSCFunctionListEntry js_os_funcs[] = { MIST_FUNC_DEF(os, turbulence, 4), MIST_FUNC_DEF(os, model_buffer, 1), @@ -6810,6 +7063,8 @@ static const JSCFunctionListEntry js_os_funcs[] = { MIST_FUNC_DEF(os, battery_voltage, 0), MIST_FUNC_DEF(os, battery_seconds, 0), MIST_FUNC_DEF(os, power_state, 0), + MIST_FUNC_DEF(os, insertion_sort, 2), + MIST_FUNC_DEF(os, cull_sprites, 2), }; #define JSSTATIC(NAME, PARENT) \ @@ -6885,7 +7140,6 @@ void ffi_load(JSContext *js) { // QJSCLASSPREP_FUNCS(SDL_GPUShader) QJSCLASSPREP_FUNCS(SDL_GPUBuffer) // QJSCLASSPREP_FUNCS(SDL_GPUTransferBuffer) - QJSGLOBALCLASS(os); @@ -6953,6 +7207,8 @@ void ffi_load(JSContext *js) { dst_atom = JS_NewAtom(js, "dst"); count_atom = JS_NewAtom(js, "count"); transform_atom = JS_NewAtom(js,"transform"); + image_atom = JS_NewAtom(js,"image"); + layer_atom = JS_NewAtom(js,"layer"); cw_atom = JS_NewAtom(js,"cw"); ccw_atom = JS_NewAtom(js,"ccw"); @@ -7017,6 +7273,7 @@ void ffi_load(JSContext *js) { index_atom = JS_NewAtom(js, "index"); indirect_atom = JS_NewAtom(js, "indirect"); num_indices_atom = JS_NewAtom(js,"num_indices"); + parent_atom = JS_NewAtom(js,"parent"); fill_event_atoms(js); diff --git a/source/sprite.h b/source/sprite.h index 927f64b1..5f1fabe3 100644 --- a/source/sprite.h +++ b/source/sprite.h @@ -2,9 +2,12 @@ #define SPRITE_H #include "HandmadeMath.h" +#include "script.h" typedef struct { - HMM_Mat2 affine; + HMM_Mat3 affine; + JSValue image; + int layer; } sprite; #endif diff --git a/source/transform.c b/source/transform.c index 48468aee..bad8a366 100644 --- a/source/transform.c +++ b/source/transform.c @@ -8,16 +8,15 @@ transform *make_transform() t->scale = (HMM_Vec3){1,1,1}; t->rotation = (HMM_Quat){0,0,0,1}; - t->dirty = 1; + transform_apply(t); return t; } void transform_free(JSRuntime *rt, transform *t) { free(t); } -void transform_apply(transform *t) { t->dirty = 1; } void transform_move(transform *t, HMM_Vec3 v) { t->pos = HMM_AddV3(t->pos, v); - t->dirty = 1; + transform_apply(t); } HMM_Vec3 transform_direction(transform *t, HMM_Vec3 dir) @@ -53,14 +52,34 @@ HMM_Vec3 mat3_t_dir(HMM_Mat4 m, HMM_Vec3 dir) return mat3_t_pos(m, dir); } +static inline void transform_clean(transform *t) +{ + if (!t->dirty) return; + t->dirty = 0; + t->cache = HMM_M4TRS(t->pos,t->rotation,t->scale); + t->cache3 = HMM_M3TRS(t->pos.xy,t->rotation.x,t->scale.xy); +} + HMM_Mat4 transform2mat(transform *t) { - return HMM_M4TRS(t->pos, t->rotation, t->scale); + transform_clean(t); + return t->cache; } HMM_Mat3 transform2mat3(transform *t) { - return HMM_M3TRS(t->pos.xy, t->rotation.x, t->scale.xy); + transform_clean(t); + return t->cache3; +} + +HMM_Mat3 transform2mat3_global(transform *t) +{ + +} + +void transform_apply(transform *t) +{ + t->dirty = 1; } HMM_Quat angle2rotation(float angle) diff --git a/source/transform.h b/source/transform.h index 5fab352e..10bc592d 100644 --- a/source/transform.h +++ b/source/transform.h @@ -9,13 +9,19 @@ typedef struct transform { HMM_Vec3 scale; HMM_Quat rotation; HMM_Mat4 cache; + HMM_Mat3 cache3; + HMM_Mat3 gcache3; + HMM_Mat4 gcache; int dirty; + struct transform *parent; + struct transform *children; } transform; transform *make_transform(); -void transform_apply(transform *t); void transform_free(JSRuntime *rt,transform *t); +void transform_apply(transform *t); + #define VEC2_FMT "[%g,%g]" #define VEC2_MEMS(s) (s).x, (s).y @@ -40,6 +46,9 @@ HMM_Vec3 mat3_t_dir(HMM_Mat4 m, HMM_Vec3 dir); HMM_Mat4 transform2mat(transform *t); HMM_Mat3 transform2mat3(transform *t); + +HMM_Mat3 transform2mat3_global(transform *t); + transform mat2transform(HMM_Mat4 m); HMM_Quat angle2rotation(float angle);