From 9a2e8974645be855a88b99cc16881680f80ecd11 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Mon, 13 Jan 2025 20:12:59 -0600 Subject: [PATCH] fast sprite render --- scripts/render.js | 2 +- source/jsffi.c | 232 +++++++++++++++++++++++++--------------------- 2 files changed, 129 insertions(+), 105 deletions(-) diff --git a/scripts/render.js b/scripts/render.js index 39670f91..188cc6dd 100644 --- a/scripts/render.js +++ b/scripts/render.js @@ -758,6 +758,7 @@ function insertion_sort(arr, cmp) function sprites_to_queue(sprites, ysort = false) { + return render._main.make_sprite_queue(allsprites, prosperon.camera, sprite_pipeline) var sprites = allsprites; for (var i = 0; i < sprites.length; i++) sprites[i].transform.clean(); @@ -769,7 +770,6 @@ function sprites_to_queue(sprites, ysort = false) var mesh = render._main.make_sprite_mesh(sprites); var queue = []; - var idx = 0; var image; var first_index = 0; var count = 0; diff --git a/source/jsffi.c b/source/jsffi.c index a9f865ba..6e5cce4a 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -1149,7 +1149,6 @@ int js_arrlen(JSContext *js,JSValue v) { len = js_getnum_str(js,v,"length"); return len; } - static inline int js_transform_dirty_chain(JSContext *js, JSValue v) { transform *t = js2transform(js, v); @@ -1753,6 +1752,64 @@ static const JSCFunctionListEntry js_spline_funcs[] = { MIST_FUNC_DEF(spline, bezier, 2) }; + +shader_globals camera_globals(JSContext *js, JSValue camera) +{ + shader_globals data = {0}; + HMM_Vec2 size; + transform *transform; + double fov; + double aspect; + int ortho; + double near; + double far; + + JS_GETPROP(js, size, camera, size, vec2) + JS_GETPROP(js, transform, camera, transform, transform) + JS_GETPROP(js, fov, camera, fov, number) + JS_GETPROP(js, aspect, camera, aspect, number) + JS_GETPROP(js, ortho, camera,ortho,bool) + JS_GETPROP(js,near,camera,near,number) + JS_GETPROP(js,far,camera,far,number) + + HMM_Mat4 proj; + HMM_Mat4 view; + + if (ortho) { + proj = HMM_Orthographic_RH_NO( + -size.x*0.5, 0.5*size.x, + -size.y*0.5, 0.5*size.y, + -1.0f, 1.0f + ); + view = HMM_Translate((HMM_Vec3){ -transform->pos.x, -transform->pos.y, 0.0f }); + } + else { + proj = HMM_Perspective_RH_NO(fov, aspect,near,far); + + HMM_Mat4 camera_transform = HMM_Translate(transform->pos); + camera_transform = HMM_MulM4(camera_transform, HMM_QToM4(transform->rotation)); +// camera_transform = HMM_MulM4(camera_transform, HMM_Scale(transform->scale)); // don't bother w/ scale + view = HMM_InvGeneralM4(camera_transform); + } + + // Update your shader globals + data.world_to_projection = HMM_MulM4(proj, view); + data.projection_to_world = HMM_InvGeneralM4(data.world_to_projection); + data.camera_pos_world = transform->pos; + data.viewport_min_z = near; + data.viewport_max_z = far; + 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; +} + + + JSValue js_vector_dot(JSContext *js, JSValue self, int argc, JSValue *argv) { size_t alen, blen; float *a = js2floats(js,argv[0], &alen); @@ -3986,7 +4043,7 @@ int sort_sprite(const sprite *a, const sprite *b) 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; + 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; } @@ -4014,33 +4071,58 @@ JSC_CCALL(gpu_sort_sprite, return number2js(js,0); ) +inline int sprite_in_view(HMM_Mat3 sprite, HMM_Mat4 camera) +{ + int outside = 0; + for (int j = 0; j < 4; j++) { + HMM_Vec3 corner = HMM_MulM3V3(sprite, (HMM_Vec3){base_quad[j].x, base_quad[j].y, 1.0}); + HMM_Vec4 clip = HMM_MulM4V4(camera, (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++; + } + + return outside != 4; +} + 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 + shader_globals info = camera_globals(js,argv[1]); + sprite *sprites = NULL; - arrsetlen(sprites, quads); + arrsetcap(sprites, quads); for (int i = 0; i < quads; i++) { JSValue sub = JS_GetPropertyUint32(js, argv[0], i); + JSValue jstransform = JS_GetPropertyStr(js, sub, "transform"); + HMM_Mat3 trmat = js2transform_mat3(js,jstransform); + if (!sprite_in_view(trmat,info.world_to_projection)) { + JS_FreeValue(js,jstransform); + JS_FreeValue(js,sub); + continue; + } + 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 }; @@ -4051,31 +4133,55 @@ JSC_CCALL(gpu_make_sprite_queue, 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); + arrput(sprites,sp); JS_FreeValue(js, sub); JS_FreeValue(js, jstransform); - JS_FreeValue(js, sp.image); } qsort(sprites, arrlen(sprites),sizeof(sprite),sort_sprite); + text_vert *buffer = NULL; + for (int i = 0; i < quads; i++) + for (int j = 0; j < 4; j++) + arrpush(buffer, sprites[i].quad.vert[j]); + + JSValue mesh = quads_to_mesh(js, buffer); + + arrfree(buffer); ret = JS_NewArray(js); - - for (int i = 0; i < quads; i++) - JS_SetPropertyUint32(js,ret,i,sprites[i].sprite); + 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", JS_DupValue(js,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; + JS_FreeValue(js,img); + img = JS_DupValue(js,sprites[i].image); + } else count++; + JS_FreeValue(js,sprites[i].image); + } arrfree(sprites); + JS_FreeValue(js,mesh); ) JSC_CCALL(gpu_make_sprite_mesh, @@ -4789,7 +4895,7 @@ static const JSCFunctionListEntry js_SDL_GPUDevice_funcs[] = { 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_sprite_queue, 3), MIST_FUNC_DEF(gpu, make_quad, 0), MIST_FUNC_DEF(gpu, driver, 0), MIST_FUNC_DEF(gpu, make_shader, 1), @@ -5057,61 +5163,6 @@ JSC_CCALL(cmd_hud, SDL_PushGPUVertexUniformData(cmds, js2number(js,argv[1]), &data, sizeof(data)); ) -shader_globals camera_globals(JSContext *js, JSValue camera) -{ - shader_globals data = {0}; - HMM_Vec2 size; - transform *transform; - double fov; - double aspect; - int ortho; - double near; - double far; - - JS_GETPROP(js, size, camera, size, vec2) - JS_GETPROP(js, transform, camera, transform, transform) - JS_GETPROP(js, fov, camera, fov, number) - JS_GETPROP(js, aspect, camera, aspect, number) - JS_GETPROP(js, ortho, camera,ortho,bool) - JS_GETPROP(js,near,camera,near,number) - JS_GETPROP(js,far,camera,far,number) - - HMM_Mat4 proj; - HMM_Mat4 view; - - if (ortho) { - proj = HMM_Orthographic_RH_NO( - -size.x*0.5, 0.5*size.x, - -size.y*0.5, 0.5*size.y, - -1.0f, 1.0f - ); - view = HMM_Translate((HMM_Vec3){ -transform->pos.x, -transform->pos.y, 0.0f }); - } - else { - proj = HMM_Perspective_RH_NO(fov, aspect,near,far); - - HMM_Mat4 camera_transform = HMM_Translate(transform->pos); - camera_transform = HMM_MulM4(camera_transform, HMM_QToM4(transform->rotation)); -// camera_transform = HMM_MulM4(camera_transform, HMM_Scale(transform->scale)); // don't bother w/ scale - view = HMM_InvGeneralM4(camera_transform); - } - - // Update your shader globals - data.world_to_projection = HMM_MulM4(proj, view); - data.projection_to_world = HMM_InvGeneralM4(data.world_to_projection); - data.camera_pos_world = transform->pos; - data.viewport_min_z = near; - data.viewport_max_z = far; - 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; -} - JSC_CCALL(cmd_camera, SDL_GPUCommandBuffer *cmds = js2SDL_GPUCommandBuffer(js, self); shader_globals data = camera_globals(js, argv[0]); @@ -6963,39 +7014,12 @@ JSC_CCALL(os_cull_sprites, 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) { + if (sprite_in_view(trmat, info.world_to_projection)) { JS_SetPropertyUint32(js,ret,n,JS_DupValue(js,sub)); n++; }