diff --git a/changes/render3d-recycle.md b/changes/render3d-recycle.md new file mode 100644 index 00000000..503006fc --- /dev/null +++ b/changes/render3d-recycle.md @@ -0,0 +1,7 @@ +bump: minor +type: fix +**A handle, a model and an actor let go come round again.** A texture or buffer freed gives its id +back and the next one made takes it, so the per-handle lists stop growing; `model_release` frees a +glTF model whole (its primitives, meshes, names, skeleton) and takes it out of `gltf_cached`'s +cache; and `actor_release` takes an actor off the stage and keeps its record for the next +`actor_new`. `examples/rendering/steady.ludic` holds 2000 actors placed and released at 0 bytes. diff --git a/examples/rendering/release.ludic b/examples/rendering/release.ludic index 9a1b787c..4c0a7fea 100644 --- a/examples/rendering/release.ludic +++ b/examples/rendering/release.ludic @@ -44,7 +44,6 @@ program Release { let diff = a.prims[0].diff ok = check("the plates share their textures", diff != 0 and b.prims[0].diff == diff and render3d_st.gltf_tex_n == 2) and ok model_release(render3d_st, a) - ok = check("the first plate is emptied", len(a.prims) == 0) and ok ok = check("what the second still uses stays", tex_live(render3d_st, diff) and render3d_st.gltf_tex_n == 2) and ok model_release(render3d_st, b) ok = check("the last user frees the textures", not tex_live(render3d_st, diff) and render3d_st.gltf_tex_n == 0) and ok diff --git a/examples/rendering/steady.ludic b/examples/rendering/steady.ludic index 441ff5da..2d9b9be0 100644 --- a/examples/rendering/steady.ludic +++ b/examples/rendering/steady.ludic @@ -43,6 +43,18 @@ program Steady { return Vk.heap_bytes() - before } + # bytes gained over n actors placed and released (actor_release): the records come round again + function actor_rounds(render3d_st: mut Render3dState, m: Model, n: int) -> long { + let before = Vk.heap_bytes() + for i in 0 .. n { + let a = actor_new(render3d_st, m) + actor_place(a, float(i % 7), 0.0, 0.0, 0.0) + actor_hide_part(a, "none", true) + actor_release(render3d_st, a) + } + return Vk.heap_bytes() - before + } + # bytes gained over n parses of a glTF document, each freed whole (Json.free_all): strings too function parse_rounds(text: string, n: int) -> long { let before = Vk.heap_bytes() @@ -71,10 +83,13 @@ program Steady { let grew_f = frame_rounds(render3d_st, 600) model_rounds(render3d_st, 20) let grew_m = model_rounds(render3d_st, 200) + let am = gltf_load(render3d_st, "packages/ludic.lab/plate", "plate.gltf", "plate") + actor_rounds(render3d_st, am, 20) + let grew_a = actor_rounds(render3d_st, am, 2000) let text = Fs.read_text("packages/ludic.lab/plate/plate.gltf") parse_rounds(text, 20) let grew_p = parse_rounds(text, 200) - print(`steady: the buffer path gained {grew_b} bytes over 5000 rounds, the frame {grew_f} over 600, a glTF parsed and freed {grew_p} over 200, a model loaded and let go {grew_m} over 200`) + print(`steady: the buffer path gained {grew_b} bytes over 5000 rounds, the frame {grew_f} over 600, a glTF parsed and freed {grew_p} over 200, a model loaded and let go {grew_m} over 200, an actor placed and released {grew_a} over 2000`) # a few KB of slack for what the system's own libraries keep (Metal's caches, the allocator) var ok = grew_b < 16384 if not ok { print("steady: FAILED - releasing and making a buffer again leaves memory behind") } @@ -92,6 +107,10 @@ program Steady { ok = false print("steady: FAILED - a model loaded and let go leaves memory behind") } + if grew_a >= 4096 { + ok = false + print("steady: FAILED - an actor placed and released leaves memory behind") + } if ok { print("STEADY OK") } else { print("STEADY FAILED") } quit() } diff --git a/packages/ludic.anim/read.ludic b/packages/ludic.anim/read.ludic index 44e55649..c4278b8c 100644 --- a/packages/ludic.anim/read.ludic +++ b/packages/ludic.anim/read.ludic @@ -86,6 +86,7 @@ function read_one(anim_st: AnimState, render3d_st: mut Render3dState, a: Val) -> c.ckeys[k] = nk c.coff[k] = at for i in 0 .. nk { c.times[at + i] = tb[i] } + free(tb) if nk > 0 and c.times[at + nk - 1] > c.dur { c.dur = c.times[at + nk - 1] } at += nk let vb = gltf_accessor_floats(render3d_st, value_as_int(value_get(sp, "output"))) @@ -93,6 +94,7 @@ function read_one(anim_st: AnimState, render3d_st: mut Render3dState, a: Val) -> if c.cpath[k] == AC_ROT { comps = 4 } c.cvoff[k] = av for i in 0 .. nk * comps { c.vals[av + i] = vb[i] } + free(vb) av += nk * comps k += 1 } diff --git a/packages/ludic.render3d/actor.ludic b/packages/ludic.render3d/actor.ludic index 917193f6..40af73a1 100644 --- a/packages/ludic.render3d/actor.ludic +++ b/packages/ludic.render3d/actor.ludic @@ -122,12 +122,10 @@ function actor_hide_part(a: Actor, name: string, hidden: bool) -> void { } function actor_new(render3d_st: mut Render3dState, model: Model) -> Actor { - let a = new Actor + let a = actor_fresh(render3d_st) a.model = model a.scale = 1.0 - a.tint = v3_new(1.0, 1.0, 1.0) a.rough = 1.0 - a.mat = m4_new() render3d_st.ac_next_id += 1; a.id = render3d_st.ac_next_id if model != null { a.radius = Math.max(model.radius, model.height) + 1.0 } a.cull = 450.0 @@ -417,3 +415,36 @@ function actor_clear_all(render3d_st: mut Render3dState) -> void { List.clear(render3d_st.ac_actors) outline_clear(render3d_st) } + +# A record as `new Actor` makes it, with its matrix and tint: a released one when there is one. +function actor_fresh(render3d_st: mut Render3dState) -> Actor { + let sp = render3d_st.ac_spare + if sp == null or len(sp) == 0 { + let a = new Actor + a.tint = v3_new(1.0, 1.0, 1.0) + a.mat = m4_new() + return a + } + let a = List.pop(sp) + a.x = 0.0; a.y = 0.0; a.z = 0.0; a.yaw = 0.0; a.scale = 0.0; a.rough = 0.0 + a.tint[0] = 1.0; a.tint[1] = 1.0; a.tint[2] = 1.0 + m4_identity(a.mat) + a.visible = true; a.cast_hidden = false; a.casts = true; a.row = -1 + a.cutout = false; a.emissive = 0.0; a.radius = 0.0; a.cull = 0.0; a.outline = 0.0 + a.skin = null; a.ocol = null + return a +} + +# Off the stage for good, and its record kept for the next actor_new: whoever released it never +# touches it again. What it was lent stays its lender's (a skin clone, a rim colour); what it made +# for itself (a part's tint, a part hidden) goes. +function actor_release(render3d_st: mut Render3dState, a: Actor) -> void { + if a == null or a.id == 0 { return } # released already + actor_remove(render3d_st, a) + if a.ptint != null { free(a.ptint); a.ptint = null } + if a.hide != null { free(a.hide); a.hide = null } + a.model = null; a.skin = null; a.ocol = null + a.id = 0 + if render3d_st.ac_spare == null { render3d_st.ac_spare = new []Actor } + push(render3d_st.ac_spare, a) +} diff --git a/packages/ludic.render3d/env.ludic b/packages/ludic.render3d/env.ludic index 90c27ba6..7599cccb 100644 --- a/packages/ludic.render3d/env.ludic +++ b/packages/ludic.render3d/env.ludic @@ -13,6 +13,7 @@ export state Render3dState { ac_out_cut: AcProg = null ac_actors: []Actor = null ac_next_id: int = 0 + ac_spare: []Actor = null # released actors, taken again by actor_new (actor_release) ac_frame: int = 0 ac_oq: []OutlineReq = null ac_oq_n: int = 0 # live entries; the array is kept and reused @@ -211,6 +212,8 @@ export state Render3dState { gvk_lay_flat: []int = null # every layout known, end to end; gvk_lay_off / _len find each gvk_lay_off: []int = null gvk_lay_len: []int = null + gvk_tex_spare: []int = null # ids of freed textures, handed out again by gvk_tex_new + gvk_buf_spare: []int = null # the same for buffers (gvk_buf_delete) gvk_has_bc: bool = false # textureCompressionBC: .dds textures are uploaded compressed # R3D_VK_PROF: Vulkan objects made and destroyed, reported every 120 frames gvk_mk_img: int = 0 @@ -805,7 +808,6 @@ export state Render3dState { tex_depth: int = 0 # bits per sample (8 or 16) tex_file_len: int = 0 tex_anisotropy: fixed = 16.0 - tex_size_ids: []int = new []int tex_size_ws: []int = new []int tex_size_hs: []int = new []int hdr_max_lum: float = 0.0 # float bits of the brightest texel (sun finding) diff --git a/packages/ludic.render3d/gltf.ludic b/packages/ludic.render3d/gltf.ludic index cb72f67f..92c8e138 100644 --- a/packages/ludic.render3d/gltf.ludic +++ b/packages/ludic.render3d/gltf.ludic @@ -45,7 +45,9 @@ function gltf_mat_find(render3d_st: Render3dState, name: string) -> int { function gltf_mat_remember(render3d_st: mut Render3dState, name: string, diff: int, nrm: int, arm: int) -> void { if render3d_st.gltf_mat_names == null { render3d_st.gltf_mat_names = new []string; render3d_st.gltf_mat_diff = words(256); render3d_st.gltf_mat_nrm = words(256); render3d_st.gltf_mat_arm = words(256) } if gltf_mat_find(render3d_st, name) >= 0 or render3d_st.gltf_mat_n >= 256 { return } - push(render3d_st.gltf_mat_names, name); render3d_st.gltf_mat_diff[render3d_st.gltf_mat_n] = diff; render3d_st.gltf_mat_nrm[render3d_st.gltf_mat_n] = nrm; render3d_st.gltf_mat_arm[render3d_st.gltf_mat_n] = arm + # its own copy of the name: a primitive frees its own with it (model_release) + push(render3d_st.gltf_mat_names, name[0 .. len(name)]) + render3d_st.gltf_mat_diff[render3d_st.gltf_mat_n] = diff; render3d_st.gltf_mat_nrm[render3d_st.gltf_mat_n] = nrm; render3d_st.gltf_mat_arm[render3d_st.gltf_mat_n] = arm render3d_st.gltf_mat_n += 1 } @@ -53,14 +55,23 @@ function gltf_texture(render3d_st: mut Render3dState, uri: string, srgb: bool) - if render3d_st.gltf_tex_paths == null { render3d_st.gltf_tex_paths = new []pointer; render3d_st.gltf_tex_ids = words(256); render3d_st.gltf_tex_refs = words(256) } var png: string = uri let n = len(uri) - if n > 4 and uri[n - 4] == '.' and uri[n - 3] == 'j' { png = uri[0 .. n - 4] + ".png" } + var made = false + if n > 4 and uri[n - 4] == '.' and uri[n - 3] == 'j' { png = uri[0 .. n - 4] + ".png"; made = true } let path = render3d_st.gltf_dir + "/" + png + if made { free(png) } var i = 0 - while i < render3d_st.gltf_tex_n { if render3d_st.gltf_tex_paths[i] == path { render3d_st.gltf_tex_refs[i] += 1; return render3d_st.gltf_tex_ids[i] }; i += 1 } + while i < render3d_st.gltf_tex_n { + if render3d_st.gltf_tex_paths[i] == path { + free(path) # the cache holds its own + render3d_st.gltf_tex_refs[i] += 1 + return render3d_st.gltf_tex_ids[i] + } + i += 1 + } var dil = 0 if render3d_st.gltf_cutout { dil = 24 } let id = tex_load_ex(render3d_st, path, srgb, dil) - if render3d_st.gltf_tex_n < 256 { push(render3d_st.gltf_tex_paths, path); render3d_st.gltf_tex_ids[render3d_st.gltf_tex_n] = id; render3d_st.gltf_tex_refs[render3d_st.gltf_tex_n] = 1; render3d_st.gltf_tex_n += 1 } + if render3d_st.gltf_tex_n < 256 { push(render3d_st.gltf_tex_paths, path); render3d_st.gltf_tex_ids[render3d_st.gltf_tex_n] = id; render3d_st.gltf_tex_refs[render3d_st.gltf_tex_n] = 1; render3d_st.gltf_tex_n += 1 } else { free(path) } return id } @@ -165,7 +176,9 @@ function gltf_prim(render3d_st: mut Render3dState, p: Val) -> Prim { # Load the mesh of the node called `node_name` from dir/file. function gltf_load(render3d_st: mut Render3dState, dir: string, file: string, node_name: string) -> Model { render3d_st.gltf_dir = dir - let text = Fs.read_text(dir + "/" + file) + let at = dir + "/" + file + let text = Fs.read_text(at) + free(at) if text == null { print(`gltf: cannot read {dir}/{file}`); return null } # the last file's tree goes first (it is read only until the next load: ludic.anim's read.ludic), # and the text once parsed - nothing gives memory back on its own (plan 23 of maroon-lake) @@ -174,7 +187,9 @@ function gltf_load(render3d_st: mut Render3dState, dir: string, file: string, no free(text) let buffers = value_get(render3d_st.gltf_doc, "buffers") let bin_uri = value_as_str(value_get(value_at(buffers, 0), "uri")) - render3d_st.gltf_bin = file_open(dir + "/" + bin_uri, "rb") + let bin_at = dir + "/" + bin_uri + render3d_st.gltf_bin = file_open(bin_at, "rb") + free(bin_at) if render3d_st.gltf_bin == null { print(`gltf: cannot open {bin_uri}`); return null } let nodes = value_get(render3d_st.gltf_doc, "nodes") var mesh_idx = -1 diff --git a/packages/ludic.render3d/gltf_cached.ludic b/packages/ludic.render3d/gltf_cached.ludic index 7ae57cfe..eb5fcf7f 100644 --- a/packages/ludic.render3d/gltf_cached.ludic +++ b/packages/ludic.render3d/gltf_cached.ludic @@ -2,7 +2,7 @@ # model the first load made, found by comparing the names in place, so asking allocates nothing. For # props a game places while it plays (a fish, a sign an animal leaves); a model whose document is # read after the load (ludic.anim's clips) still goes through gltf_load. A model loaded here is shared -# by every actor drawing it, and is never released. +# by every actor drawing it; model_release takes it out of the cache as it lets it go. export function gltf_cached(render3d_st: mut Render3dState, dir: string, file: string, node_name: string) -> Model { if render3d_st.gltf_kept == null { render3d_st.gltf_kept = new []Model @@ -21,6 +21,23 @@ export function gltf_cached(render3d_st: mut Render3dState, dir: string, file: s return m } +# a model let go (model_release) leaves the cache, so the next ask loads it afresh +function gltf_cached_forget(render3d_st: mut Render3dState, m: Model) -> void { + if render3d_st.gltf_kept == null { return } + let kept = render3d_st.gltf_kept + let dirs = render3d_st.gltf_kept_dir + let files = render3d_st.gltf_kept_file + let nodes = render3d_st.gltf_kept_node + for i in 0 .. len(kept) { + if kept[i] == m { + let last = len(kept) - 1 + kept[i] = kept[last]; dirs[i] = dirs[last]; files[i] = files[last]; nodes[i] = nodes[last] + List.pop(kept); List.pop(dirs); List.pop(files); List.pop(nodes) + return + } + } +} + export function gltf_cached_count(render3d_st: Render3dState) -> int { if render3d_st.gltf_kept == null { return 0 } return len(render3d_st.gltf_kept) diff --git a/packages/ludic.render3d/gltf_release.ludic b/packages/ludic.render3d/gltf_release.ludic index 76654087..d4a0ff5d 100644 --- a/packages/ludic.render3d/gltf_release.ludic +++ b/packages/ludic.render3d/gltf_release.ludic @@ -27,6 +27,7 @@ function gltf_tex_unref(render3d_st: mut Render3dState, id: int) -> void { function gltf_tex_forget(render3d_st: mut Render3dState, i: int) -> void { let last = render3d_st.gltf_tex_n - 1 let paths = render3d_st.gltf_tex_paths + free(paths[i]) paths[i] = paths[last] render3d_st.gltf_tex_ids[i] = render3d_st.gltf_tex_ids[last] render3d_st.gltf_tex_refs[i] = render3d_st.gltf_tex_refs[last] @@ -42,6 +43,7 @@ function gltf_mats_forget(render3d_st: mut Render3dState, id: int) -> void { if render3d_st.gltf_mat_diff[k] == id or render3d_st.gltf_mat_nrm[k] == id or render3d_st.gltf_mat_arm[k] == id { let last = render3d_st.gltf_mat_n - 1 let names = render3d_st.gltf_mat_names + free(names[k]) names[k] = names[last] render3d_st.gltf_mat_diff[k] = render3d_st.gltf_mat_diff[last] render3d_st.gltf_mat_nrm[k] = render3d_st.gltf_mat_nrm[last] @@ -54,17 +56,31 @@ function gltf_mats_forget(render3d_st: mut Render3dState, id: int) -> void { } } -# Let a model go: every primitive's mesh, and its textures once no other model uses them. The -# caller says when - no actor may draw the model afterwards (a released model's prims are emptied, -# so a stale draw of it draws nothing). +# Let a model go: every primitive's mesh, and its textures once no other model uses it; the +# primitives, their meshes and the model itself are freed, and a model gltf_cached handed out +# leaves its cache. The caller says when: nothing may draw the model, or hold it, afterwards. For +# a model gltf_load or gltf_cached made (a procedural one - a card, a blade - owns its parts otherwise). function model_release(render3d_st: mut Render3dState, m: Model) -> void { - if m == null or m.prims == null { return } - for i in 0 .. len(m.prims) { - let pr = m.prims[i] - if pr.mesh != null { gpu_mesh_free(render3d_st, pr.mesh); pr.mesh = null } - gltf_tex_unref(render3d_st, pr.diff); gltf_tex_unref(render3d_st, pr.nrm); gltf_tex_unref(render3d_st, pr.arm) - pr.diff = 0; pr.nrm = 0; pr.arm = 0 + if m == null { return } + gltf_cached_forget(render3d_st, m) + if m.prims != null { + for i in 0 .. len(m.prims) { + let pr = m.prims[i] + gltf_tex_unref(render3d_st, pr.diff); gltf_tex_unref(render3d_st, pr.nrm); gltf_tex_unref(render3d_st, pr.arm) + if pr.mesh != null { mesh_release(render3d_st, pr.mesh) } + if pr.name != null { free(pr.name) } + free(pr) + } + free(m.prims) } - let prims = m.prims - while len(prims) > 0 { List.pop(prims) } + skin_free(m.skin) + free(m) +} + +# a mesh and what it holds: its buffers (their handles given back), its layout and buffer lists +function mesh_release(render3d_st: mut Render3dState, me: Mesh) -> void { + gpu_mesh_free(render3d_st, me) + if me.attrs != null { free(me.attrs) } + if me.vbufs != null { free(me.vbufs) } + free(me) } diff --git a/packages/ludic.render3d/gpu.ludic b/packages/ludic.render3d/gpu.ludic index 300657d9..55123123 100644 --- a/packages/ludic.render3d/gpu.ludic +++ b/packages/ludic.render3d/gpu.ludic @@ -599,7 +599,9 @@ function gpu_tex_free(render3d_st: mut Render3dState, tex: int) -> void { if tex == 0 { return } let ids = gpu_tmp(render3d_st) ids[0] = tex - gvk_flush(render3d_st); if tex < len(render3d_st.gvk_tex_image) { gvk_tex_release(render3d_st, tex) } + gvk_flush(render3d_st) + gvk_tex_give_back(render3d_st, tex) + tex_note_forget(render3d_st, tex) if render3d_st.gpu_unit_2d != null { for i in 0 .. 32 { if render3d_st.gpu_unit_2d[i] == tex { render3d_st.gpu_unit_2d[i] = 0 } } } if render3d_st.gpu_bound_2d == tex { render3d_st.gpu_bound_2d = 0 } let o = gpu_tx_at(render3d_st, tex) @@ -699,7 +701,8 @@ function gpu_fb_free(render3d_st: mut Render3dState, fb: int) -> void { gvk_fb_forget(render3d_st, fb); let fo = gpu_fb_at(render3d_st, fb); if fo >= 0 { for i in 0 .. GPU_FB_W { render3d_st.gpu_fb[fo + i] = 0 } } } function gpu_rb_free(render3d_st: mut Render3dState, rb: int) -> void { - gvk_flush(render3d_st); if rb > 0 and rb < len(render3d_st.gvk_tex_image) { gvk_tex_release(render3d_st, rb) } + gvk_flush(render3d_st) + gvk_tex_give_back(render3d_st, rb) } function gpu_viewport(render3d_st: mut Render3dState, x: int, y: int, w: int, h: int) -> void { gvk_viewport(render3d_st, x, y, w, h) } diff --git a/packages/ludic.render3d/gpu_vk_draw.ludic b/packages/ludic.render3d/gpu_vk_draw.ludic index 8879af01..97d8f78c 100644 --- a/packages/ludic.render3d/gpu_vk_draw.ludic +++ b/packages/ludic.render3d/gpu_vk_draw.ludic @@ -1460,10 +1460,10 @@ function gvk_program_new(render3d_st: mut Render3dState, vs: string, fs: string, function gvk_mesh_free(render3d_st: mut Render3dState, m: Mesh) -> void { if m == null { return } - if m.vbufs != null { for i in 0 .. m.n_vbufs { if m.vbufs[i] > 0 { gvk_buf_release(render3d_st, m.vbufs[i]) } } } + if m.vbufs != null { for i in 0 .. m.n_vbufs { if m.vbufs[i] > 0 { gvk_buf_delete(render3d_st, m.vbufs[i]) } } } m.n_vbufs = 0 m.vbo = 0 - if m.ebo > 0 { gvk_buf_release(render3d_st, m.ebo); m.ebo = 0 } + if m.ebo > 0 { gvk_buf_delete(render3d_st, m.ebo); m.ebo = 0 } } function gvk_scissor(render3d_st: mut Render3dState, x: int, y: int, w: int, h: int) -> void { diff --git a/packages/ludic.render3d/gpu_vk_res.ludic b/packages/ludic.render3d/gpu_vk_res.ludic index 38cff421..c1631202 100644 --- a/packages/ludic.render3d/gpu_vk_res.ludic +++ b/packages/ludic.render3d/gpu_vk_res.ludic @@ -57,6 +57,14 @@ function gvk_channel_bytes(ifmt: int) -> int { # renderer asks for mipmaps after the upload; an image made without pixels is a target and gets # one level. Every level sits in SHADER_READ_ONLY_OPTIMAL between uses. +# a texture freed for good: its image goes, and its id is handed out again by gvk_tex_new - once, and +# only if it held an image, so a texture freed twice cannot give two new ones the same id +function gvk_tex_give_back(render3d_st: mut Render3dState, tex: int) -> void { + if tex <= 0 or tex >= len(render3d_st.gvk_tex_image) or render3d_st.gvk_tex_image[tex] == 0 { return } + gvk_tex_release(render3d_st, tex) + if render3d_st.gvk_tex_spare == null { render3d_st.gvk_tex_spare = new []int } + push(render3d_st.gvk_tex_spare, tex) +} function gvk_tex_new(render3d_st: mut Render3dState) -> int { let zero: long = 0 if render3d_st.gvk_tex_image == null { @@ -73,6 +81,18 @@ function gvk_tex_new(render3d_st: mut Render3dState) -> int { push(render3d_st.gvk_tex_image, zero); push(render3d_st.gvk_tex_view, zero); push(render3d_st.gvk_tex_mem, zero) push(render3d_st.gvk_tex_levels, 0); push(render3d_st.gvk_tex_layers, 0); push(render3d_st.gvk_tex_vkfmt, 0) } + # an id a freed texture gave back first: its generation goes on counting, so every cache keyed by + # (id, generation) - views, sets, samplers - tells the new texture from the old + if render3d_st.gvk_tex_spare != null and len(render3d_st.gvk_tex_spare) > 0 { + let sp = render3d_st.gvk_tex_spare + let t = sp[len(sp) - 1] + List.pop(sp) + render3d_st.gvk_tex_image[t] = zero; render3d_st.gvk_tex_view[t] = zero; render3d_st.gvk_tex_mem[t] = zero + render3d_st.gvk_tex_levels[t] = 0; render3d_st.gvk_tex_layers[t] = 0; render3d_st.gvk_tex_vkfmt[t] = 0 + render3d_st.gvk_tex_dims_w[t] = 0; render3d_st.gvk_tex_dims_h[t] = 0; render3d_st.gvk_tex_glfmt[t] = 0; render3d_st.gvk_tex_array[t] = 0 + render3d_st.gvk_tex_smp_sig[t] = 0; render3d_st.gvk_tex_smp[t] = zero; render3d_st.gvk_tex_samples[t] = 0 + return t + } push(render3d_st.gvk_tex_image, zero); push(render3d_st.gvk_tex_view, zero); push(render3d_st.gvk_tex_mem, zero) push(render3d_st.gvk_tex_levels, 0); push(render3d_st.gvk_tex_layers, 0); push(render3d_st.gvk_tex_vkfmt, 0) push(render3d_st.gvk_tex_dims_w, 0); push(render3d_st.gvk_tex_dims_h, 0) @@ -605,10 +625,29 @@ function gvk_buf_new(render3d_st: mut Render3dState) -> int { # handle 0 is "no buffer", as it is on OpenGL push(render3d_st.gvk_buf, zero); push(render3d_st.gvk_buf_mem, zero); push(render3d_st.gvk_buf_size, 0); push(render3d_st.gvk_buf_map, null); push(render3d_st.gvk_buf_used, 0) } + # a handle a deleted buffer gave back first (gvk_buf_delete) + if render3d_st.gvk_buf_spare != null and len(render3d_st.gvk_buf_spare) > 0 { + let sp = render3d_st.gvk_buf_spare + let b = sp[len(sp) - 1] + List.pop(sp) + return b + } push(render3d_st.gvk_buf, zero); push(render3d_st.gvk_buf_mem, zero); push(render3d_st.gvk_buf_size, 0); push(render3d_st.gvk_buf_map, null); push(render3d_st.gvk_buf_used, 0) return len(render3d_st.gvk_buf) - 1 } +# a buffer done with for good (a mesh freed): its storage goes, and its handle is handed out again +# by gvk_buf_new. gvk_buf_release alone keeps the handle, for a caller that fills it again. +function gvk_buf_delete(render3d_st: mut Render3dState, b: int) -> void { + if b <= 0 or b >= len(render3d_st.gvk_buf) { return } + gvk_buf_release(render3d_st, b) + if render3d_st.gvk_buf_gpu != null and b < len(render3d_st.gvk_buf_gpu) { render3d_st.gvk_buf_gpu[b] = 0 } + if render3d_st.gvk_buf_spare == null { render3d_st.gvk_buf_spare = new []int } + let sp = render3d_st.gvk_buf_spare + for i in 0 .. len(sp) { if sp[i] == b { return } } + push(sp, b) +} + function gvk_buf_release(render3d_st: mut Render3dState, b: int) -> void { if render3d_st.gvk_buf[b] == 0 { return } let zero: long = 0 diff --git a/packages/ludic.render3d/skin.ludic b/packages/ludic.render3d/skin.ludic index 11ffc569..bbfb3aa1 100644 --- a/packages/ludic.render3d/skin.ludic +++ b/packages/ludic.render3d/skin.ludic @@ -227,3 +227,21 @@ function skin_clone(src: Skin) -> Skin { skin_pose(sk) return sk } + +# A skin loaded with a model, let go with it (model_release): every array it owns, its joints' names +# and itself. The views into gmat, inv_bind and bones are slices of those, and freeing a view would +# free its owner's data again, so only the lists that hold them go (a few bytes a joint stay). +function skin_free(sk: Skin) -> void { + if sk == null { return } + if sk.names != null { + for i in 0 .. len(sk.names) { if sk.names[i] != null and len(sk.names[i]) > 0 { free(sk.names[i]) } } + free(sk.names) + } + free(sk.par); free(sk.walk); free(sk.rest_t); free(sk.rest_r); free(sk.rest_s); free(sk.rest_g) + free(sk.pose_r); free(sk.pose_t); free(sk.gmat); free(sk.joints); free(sk.inv_bind); free(sk.bones) + free(sk.tmp_l); free(sk.tmp_q); free(sk.tmp_a); free(sk.tmp_b); free(sk.tmp_c); free(sk.tmp_v) + if sk.gmat_v != null { free(sk.gmat_v) } + if sk.inv_v != null { free(sk.inv_v) } + if sk.bones_v != null { free(sk.bones_v) } + free(sk) +} diff --git a/packages/ludic.render3d/texture.ludic b/packages/ludic.render3d/texture.ludic index 34275088..90bdaab6 100644 --- a/packages/ludic.render3d/texture.ludic +++ b/packages/ludic.render3d/texture.ludic @@ -284,22 +284,23 @@ function tex_load_ex(render3d_st: mut Render3dState, path: pointer, srgb: bool, return id } # the size each loaded texture was, by id: a nine-slice or a UI image asks +# a texture's size, by its id: a slot per id (ids are reused), not a list that grows by every load function tex_note_size(render3d_st: mut Render3dState, id: int) -> void { - push(render3d_st.tex_size_ids, id) - push(render3d_st.tex_size_ws, render3d_st.tex_w) - push(render3d_st.tex_size_hs, render3d_st.tex_h) + if id <= 0 { return } + while len(render3d_st.tex_size_ws) <= id { push(render3d_st.tex_size_ws, 0); push(render3d_st.tex_size_hs, 0) } + render3d_st.tex_size_ws[id] = render3d_st.tex_w + render3d_st.tex_size_hs[id] = render3d_st.tex_h +} +function tex_note_forget(render3d_st: mut Render3dState, id: int) -> void { + if id > 0 and id < len(render3d_st.tex_size_ws) { render3d_st.tex_size_ws[id] = 0; render3d_st.tex_size_hs[id] = 0 } } function tex_width(render3d_st: Render3dState, id: int) -> int { - for i in 0 .. len(render3d_st.tex_size_ids) { - if render3d_st.tex_size_ids[i] == id { return render3d_st.tex_size_ws[i] } - } - return 0 + if id <= 0 or id >= len(render3d_st.tex_size_ws) { return 0 } + return render3d_st.tex_size_ws[id] } function tex_height(render3d_st: Render3dState, id: int) -> int { - for i in 0 .. len(render3d_st.tex_size_ids) { - if render3d_st.tex_size_ids[i] == id { return render3d_st.tex_size_hs[i] } - } - return 0 + if id <= 0 or id >= len(render3d_st.tex_size_hs) { return 0 } + return render3d_st.tex_size_hs[id] } # A small solid-colour fallback texture (linear rgb 0..255), for missing maps.