From bb5e6b883a27b814a8847fa902f41008d6f2ba58 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Thu, 11 Jul 2024 14:25:45 -0500 Subject: [PATCH] rendering improvements --- docs/engine_tour/gui.md | 9 ++- scripts/components.js | 4 +- scripts/debug.js | 2 + scripts/engine.js | 2 + scripts/mum.js | 8 +- scripts/render.js | 159 ++++++++++++++++++++++++++-------------- source/engine/jsffi.c | 36 +++++++-- 7 files changed, 151 insertions(+), 69 deletions(-) diff --git a/docs/engine_tour/gui.md b/docs/engine_tour/gui.md index 3f61ba6..f58b31a 100644 --- a/docs/engine_tour/gui.md +++ b/docs/engine_tour/gui.md @@ -1,6 +1,9 @@ # GUI -Game GUIs are written by registering an entity's *gui* property to a function. +Game GUIs are written by registering an entity's `gui` property to a function, or its `hud` property. -The GUI system which ships with Prosperon is called *MUM*. MUM is a declarative, immediate mode interface system. Immediate to eliminate the issue of data synchronization in the game. +`gui` draws in window space, where the bottom left corner is `[0,0]`. `hud` draws in screen space. In either of these, you can call "render" functions directly. -All GUI objects derive from MUM. MUM has a list of properties, used for rendering. Mum also has functions which cause drawing to appear on the screen. +`draw` draws in world space, and mum functions can equally be used there. + +## MUM +The GUI system which ships with Prosperon is called *MUM*. MUM is a declarative, immediate mode HUD system. While Imgui is designed to make it easy to make editor-like controls, mum is designed to be easy to make video game huds. diff --git a/scripts/components.js b/scripts/components.js index e434342..bbdd84e 100644 --- a/scripts/components.js +++ b/scripts/components.js @@ -13,6 +13,8 @@ var make_point_obj = function(o, p) } } +var fullrect = [0,0,1,1]; + var sprite = { loop: true, anim:{}, @@ -63,7 +65,7 @@ var sprite = { this.texture = game.texture(p); this.diffuse = this.texture; - this.rect = [0,0,1,1]; + this.rect = fullrect; var anim = SpriteAnim.make(p); if (!anim) return; diff --git a/scripts/debug.js b/scripts/debug.js index a4dc63b..02e74d4 100644 --- a/scripts/debug.js +++ b/scripts/debug.js @@ -1,5 +1,7 @@ var debug = {}; +debug.build = function(fn) { fn(); } + debug.fn_break = function(fn,obj = globalThis) { if (typeof fn !== 'function') return; diff --git a/scripts/engine.js b/scripts/engine.js index cd21e97..3d8ede5 100644 --- a/scripts/engine.js +++ b/scripts/engine.js @@ -128,8 +128,10 @@ profile.report = function (start, msg = "[undefined report]") { }; profile.addreport = function (cache, line, start) { + cache ??= profcache; cache[line] ??= []; cache[line].push(profile.now() - start); + return profile.now(); }; profile.printreport = function (cache, name) { diff --git a/scripts/mum.js b/scripts/mum.js index fae1ba6..b69c5b5 100644 --- a/scripts/mum.js +++ b/scripts/mum.js @@ -8,6 +8,7 @@ mum.inputs = {}; mum.inputs.lm = function() { if (!selected) return; + if (!selected.action) return; selected.action(); } @@ -19,7 +20,6 @@ mum.base = { selectable: false, selected: false, font_size: 16, - text_align: "left", /* left, center, right */ scale: 1, angle: 0, anchor: [0,1], @@ -31,6 +31,8 @@ mum.base = { color: Color.white, }, text_outline: 1, /* outline in pixels */ + text_align: "left", /* left, center, right */ + text_shader: null, color: Color.white, margin: [0,0], /* Distance between elements for things like columns */ size: null, @@ -88,10 +90,6 @@ mum.list = function(fn, data = {}) if (mum.debug) render.boundingbox(context.bb); - if (context.background_image) { - mum.image(context.background_image, { - } - post = posts.pop(); end(); } diff --git a/scripts/render.js b/scripts/render.js index 4b176b1..b305643 100644 --- a/scripts/render.js +++ b/scripts/render.js @@ -4,6 +4,44 @@ render.doc = { wireframe: "Show only wireframes of models." }; +var cur = {}; + +render.use_shader = function(shader) +{ + if (cur.shader === shader) return; + cur.shader = shader; + cur.globals = {}; + cur.bind = undefined; + cur.mesh = undefined; + render.setpipeline(shader.pipe); +} + +render.use_mat = function(mat) +{ + if (!cur.shader) return; + if (cur.mat === mat) return; + + render.shader_apply_material(cur.shader, mat, cur.mat); + + cur.mat = mat; + + cur.images = []; + if (!cur.shader.fs.images) return; + for (var img of cur.shader.fs.images) + if (mat[img.name]) + cur.images.push(mat[img.name]); + else + cur.images.push(game.texture("icons/no_tex.gif")); +} + +var models_array = []; + +render.set_model = function(t) +{ + if (cur.shader.vs.unimap.model) + render.setunim4(0, cur.shader.vs.unimap.model.slot, t); +} + var shaderlang = { macos: "metal_macos", windows: "hlsl5", @@ -93,18 +131,25 @@ function shader_directive(shader, name, map) function global_uni(uni, stage) { + cur.globals[stage] ??= {}; + if (cur.globals[stage][uni.name]) return true; switch(uni.name) { case "time": + cur.globals[stage][uni.name] render.setuniv(stage, uni.slot, profile.secs(profile.now())); + cur.globals[stage][uni.name] = true; return true; case "projection": render.setuniproj(stage, uni.slot); + cur.globals[stage][uni.name] = true; return true; case "view": render.setuniview(stage, uni.slot); + cur.globals[stage][uni.name] = true; return true; case "vp": render.setunivp(stage, uni.slot); + cur.globals[stage][uni.name] = true; return true; } @@ -260,22 +305,26 @@ var shader_unisize = { 16: render.setuniv4 }; -render.shader_apply_material = function(shader, material = {}) +render.shader_apply_material = function(shader, material = {}, old = {}) { for (var p in shader.vs.unimap) { if (global_uni(shader.vs.unimap[p], 0)) continue; - if (!(p in material)) continue; + if (material[p] === old[p]) continue; + assert(p in material, `shader ${shader.name} has no uniform for ${p}`); var s = shader.vs.unimap[p]; shader_unisize[s.size](0, s.slot, material[p]); } for (var p in shader.fs.unimap) { if (global_uni(shader.fs.unimap[p], 1)) continue; - if (!(p in material)) continue; + if (material[p] === old[p]) continue; + assert(p in material, `shader ${shader.name} has no uniform for ${p}`); var s = shader.fs.unimap[p]; shader_unisize[s.size](1, s.slot, material[p]); } + if (!material.diffuse) return; + if (material.diffuse === old.diffuse) return; if ("diffuse_size" in shader.fs.unimap) render.setuniv2(1, shader.fs.unimap.diffuse_size.slot, [material.diffuse.width, material.diffuse.height]); @@ -284,40 +333,48 @@ render.shader_apply_material = function(shader, material = {}) render.setuniv2(0, shader.vs.unimap.diffuse_size.slot, [material.diffuse.width, material.diffuse.height]); } -render.sg_bind = function(shader, mesh = {}, material = {}, ssbo) +render.sg_bind = function(mesh, ssbo) { + if (cur.mesh === mesh && cur.bind) { + cur.bind.inst = 1; + cur.bind.images = cur.images; + render.setbind(cur.bind); + return cur.bind; + } + + cur.mesh = mesh; + var bind = {}; bind.attrib = []; - if (shader.vs.inputs) - for (var a of shader.vs.inputs) { + if (cur.shader.vs.inputs) + for (var a of cur.shader.vs.inputs) { if (!(a.name in mesh)) { if (!(a.name.slice(2) in mesh)) { - console.error(`cannot draw shader ${shader.name}; there is no attrib ${a.name} in the given mesh.`); + console.error(`cannot draw shader ${cur.shader.name}; there is no attrib ${a.name} in the given mesh.`); return undefined; } else bind.attrib.push(mesh[a.name.slice(2)]); } else bind.attrib.push(mesh[a.name]); } - bind.images = []; - if (shader.fs.images) - for (var img of shader.fs.images) { - if (material[img.name]) - bind.images.push(material[img.name]); - else - bind.images.push(game.texture("icons/no_tex.gif")); - } - if (shader.indexed) { + if (cur.shader.indexed) { bind.index = mesh.index; bind.count = mesh.count; } else bind.count = mesh.verts; bind.ssbo = []; - if (shader.vs.storage_buffers) - for (var b of shader.vs.storage_buffers) + if (cur.shader.vs.storage_buffers) + for (var b of cur.shader.vs.storage_buffers) bind.ssbo.push(ssbo); + + bind.inst = 1; + bind.images = cur.images; + + cur.bind = bind; + + render.setbind(cur.bind); return bind; } @@ -409,35 +466,29 @@ render.circle = function(pos, radius, color) { coord: pos, shade: color }; - render.setpipeline(circleshader.pipe); - render.shader_apply_material(circleshader, mat); - var bind = render.sg_bind(circleshader, shape.quad, mat); - bind.inst = 1; - render.spdraw(bind); + render.use_shader(circleshader); + render.use_mat(mat); + render.draw(shape.quad); } render.poly = function(points, color, transform) { var buffer = render.poly_prim(points); var mat = { shade: color}; - render.setpipeline(polyshader.pipe); - render.setunim4(0,polyshader.vs.unimap.model.slot, transform); - render.shader_apply_material(polyshader, mat); - var bind = render.sg_bind(polyshader, buffer, mat); - bind.inst = 1; - render.spdraw(bind); + render.use_shader(polyshader); + render.set_model(transform); + render.use_mat(mat); + render.draw(buffer); } render.line = function(points, color = Color.white, thickness = 1, transform) { var buffer = os.make_line_prim(points, thickness, 0, false); - render.setpipeline(polyshader.pipe); + render.use_shader(polyshader); var mat = { shade: color }; - render.shader_apply_material(polyshader, mat); - render.setunim4(0,polyshader.vs.unimap.model.slot, transform); - var bind = render.sg_bind(polyshader, buffer, mat); - bind.inst = 1; - render.spdraw(bind); + render.use_mat(mat); + render.set_model(transform); + render.draw(buffer); } /* All draw in screen space */ @@ -531,15 +582,14 @@ render.image = function(tex, pos, scale = [tex.width, tex.height], rotation = 0, var t = os.make_transform(); t.pos = pos; t.scale = [scale.x/tex.width,scale.y/tex.height,1]; - render.setpipeline(render.spriteshader.pipe); - render.setunim4(0, render.spriteshader.vs.unimap.model.slot, t); - render.shader_apply_material(render.spriteshader, { + render.use_shader(render.spriteshader); + render.set_model(t); + render.use_mat({ shade: color, diffuse: tex }); - var bind = render.sg_bind(render.spriteshader, shape.quad, {diffuse:tex}); - bind.inst = 1; - render.spdraw(bind); + + render.draw(shape.quad); var bb = {}; bb.b = pos.y; @@ -559,19 +609,18 @@ render.slice9 = function(tex, pos, bb, scale = [tex.width,tex.height], color = C border = [bb/tex.width,bb/tex.height,bb/tex.width,bb/tex.height]; else border = [bb.l/tex.width, bb.b/tex.height, bb.r/tex.width, bb.t/tex.height]; - - render.setpipeline(slice9shader.pipe); - render.setunim4(0, slice9shader.vs.unimap.model.slot, t); - render.shader_apply_material(slice9shader, { + + render.use_shader(slice9shader); + render.set_model(t); + render.use_mat({ shade: color, diffuse:tex, rect:[0,0,1,1], border: border, scale: [scale.x/tex.width,scale.y/tex.height] }); - var bind = render.sg_bind(slice9shader, shape.quad, {diffuse:tex}); - bind.inst = 1; - render.spdraw(bind); + + render.draw(shape.quad); } var textssbo = render.text_ssbo(); @@ -579,11 +628,10 @@ var textssbo = render.text_ssbo(); render.flush_text = function() { if (!render.textshader) return; - render.setpipeline(render.textshader.pipe); - render.shader_apply_material(render.textshader); - var textbind = render.sg_bind(render.textshader, shape.quad, {text:render.font.texture}, textssbo); - textbind.inst = render.flushtext(); - render.spdraw(textbind); + render.use_shader(render.textshader); + render.use_mat({text:render.font.texture}); + + render.draw(shape.quad, textssbo, render.flushtext()); } render.fontcache = {}; @@ -604,5 +652,10 @@ render.cross.doc = "Draw a cross centered at pos, with arm length size."; render.arrow.doc = "Draw an arrow from start to end, with wings of length wingspan at angle wingangle."; render.rectangle.doc = "Draw a rectangle, with its corners at lowerleft and upperright."; +render.draw = function(mesh, ssbo, inst = 1) +{ + render.sg_bind(mesh, ssbo); + render.spdraw(cur.bind.count, inst); +} return {render}; diff --git a/source/engine/jsffi.c b/source/engine/jsffi.c index b871d66..38a5961 100644 --- a/source/engine/jsffi.c +++ b/source/engine/jsffi.c @@ -655,9 +655,8 @@ sg_bindings js2bind(JSValue v) } JSValue ssbo = js_getpropstr(v, "ssbo"); - for (int i = 0; i < js_arrlen(ssbo); i++) { + for (int i = 0; i < js_arrlen(ssbo); i++) bind.vs.storage_buffers[i] = *js2sg_buffer(js_getpropidx(ssbo,i)); - } return bind; } @@ -948,12 +947,33 @@ JSC_CCALL(render_setunim4, sg_apply_uniforms(js2number(argv[0]), js2number(argv[1]), SG_RANGE_REF(m.e)); ); -JSC_CCALL(render_spdraw, +JSC_CCALL(render_setbind, sg_bindings bind = js2bind(argv[0]); sg_apply_bindings(&bind); - int p = js2number(js_getpropstr(argv[0], "count")); - int n = js2number(js_getpropstr(argv[0], "inst")); - sg_draw(0,p,n); +) + +JSC_CCALL(render_make_t_ssbo, + JSValue array = argv[0]; + HMM_Mat4 ms[js_arrlen(array)]; + for (int i = 0; i < js_arrlen(array); i++) + ms[i] = transform2mat(*js2transform(js_getpropidx(array, i))); + + sg_buffer *rr = malloc(sizeof(sg_buffer)); + *rr = sg_make_buffer(&(sg_buffer_desc){ + .data = { + .ptr = ms, + .size = sizeof(HMM_Mat4)*js_arrlen(array), + }, + .type = SG_BUFFERTYPE_STORAGEBUFFER, + .usage = SG_USAGE_IMMUTABLE, + .label = "transform buffer" + }); + + return sg_buffer2js(rr); +) + +JSC_CCALL(render_spdraw, + sg_draw(0,js2number(argv[0]),js2number(argv[1])); ) JSC_CCALL(render_setpipeline, @@ -988,7 +1008,8 @@ static const JSCFunctionListEntry js_render_funcs[] = { MIST_FUNC_DEF(render, pipeline, 1), MIST_FUNC_DEF(render, setuniv3, 2), MIST_FUNC_DEF(render, setuniv, 2), - MIST_FUNC_DEF(render, spdraw, 1), + MIST_FUNC_DEF(render, spdraw, 2), + MIST_FUNC_DEF(render, setbind, 1), MIST_FUNC_DEF(render, setuniproj, 2), MIST_FUNC_DEF(render, setuniview, 2), MIST_FUNC_DEF(render, setunivp, 2), @@ -1001,6 +1022,7 @@ static const JSCFunctionListEntry js_render_funcs[] = { MIST_FUNC_DEF(render, gfx_gui, 0), MIST_FUNC_DEF(render, imgui_end, 0), MIST_FUNC_DEF(render, imgui_init, 0), + MIST_FUNC_DEF(render, make_t_ssbo, 1) }; JSC_CCALL(gui_scissor, sg_apply_scissor_rect(js2number(argv[0]), js2number(argv[1]), js2number(argv[2]), js2number(argv[3]), 0))