render3d: recycle texture and buffer ids, free a released model whole, actor_release
A freed texture or buffer id goes on a spare list the next one takes; tex_note_size keeps sizes by id. model_release frees prims, meshes, material names, the skin and leaves gltf_cached. actor_release takes an actor off the stage and actor_new reuses its record (ECS's Things come and go all day). steady.ludic: an actor round at 0 bytes over 2000. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2fe4018d28
commit
9b482d609a
14 changed files with 208 additions and 39 deletions
7
changes/render3d-recycle.md
Normal file
7
changes/render3d-recycle.md
Normal file
|
|
@ -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.
|
||||||
|
|
@ -44,7 +44,6 @@ program Release {
|
||||||
let diff = a.prims[0].diff
|
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
|
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)
|
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
|
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)
|
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
|
ok = check("the last user frees the textures", not tex_live(render3d_st, diff) and render3d_st.gltf_tex_n == 0) and ok
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,18 @@ program Steady {
|
||||||
return Vk.heap_bytes() - before
|
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
|
# 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 {
|
function parse_rounds(text: string, n: int) -> long {
|
||||||
let before = Vk.heap_bytes()
|
let before = Vk.heap_bytes()
|
||||||
|
|
@ -71,10 +83,13 @@ program Steady {
|
||||||
let grew_f = frame_rounds(render3d_st, 600)
|
let grew_f = frame_rounds(render3d_st, 600)
|
||||||
model_rounds(render3d_st, 20)
|
model_rounds(render3d_st, 20)
|
||||||
let grew_m = model_rounds(render3d_st, 200)
|
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")
|
let text = Fs.read_text("packages/ludic.lab/plate/plate.gltf")
|
||||||
parse_rounds(text, 20)
|
parse_rounds(text, 20)
|
||||||
let grew_p = parse_rounds(text, 200)
|
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)
|
# a few KB of slack for what the system's own libraries keep (Metal's caches, the allocator)
|
||||||
var ok = grew_b < 16384
|
var ok = grew_b < 16384
|
||||||
if not ok { print("steady: FAILED - releasing and making a buffer again leaves memory behind") }
|
if not ok { print("steady: FAILED - releasing and making a buffer again leaves memory behind") }
|
||||||
|
|
@ -92,6 +107,10 @@ program Steady {
|
||||||
ok = false
|
ok = false
|
||||||
print("steady: FAILED - a model loaded and let go leaves memory behind")
|
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") }
|
if ok { print("STEADY OK") } else { print("STEADY FAILED") }
|
||||||
quit()
|
quit()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ function read_one(anim_st: AnimState, render3d_st: mut Render3dState, a: Val) ->
|
||||||
c.ckeys[k] = nk
|
c.ckeys[k] = nk
|
||||||
c.coff[k] = at
|
c.coff[k] = at
|
||||||
for i in 0 .. nk { c.times[at + i] = tb[i] }
|
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] }
|
if nk > 0 and c.times[at + nk - 1] > c.dur { c.dur = c.times[at + nk - 1] }
|
||||||
at += nk
|
at += nk
|
||||||
let vb = gltf_accessor_floats(render3d_st, value_as_int(value_get(sp, "output")))
|
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 }
|
if c.cpath[k] == AC_ROT { comps = 4 }
|
||||||
c.cvoff[k] = av
|
c.cvoff[k] = av
|
||||||
for i in 0 .. nk * comps { c.vals[av + i] = vb[i] }
|
for i in 0 .. nk * comps { c.vals[av + i] = vb[i] }
|
||||||
|
free(vb)
|
||||||
av += nk * comps
|
av += nk * comps
|
||||||
k += 1
|
k += 1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
function actor_new(render3d_st: mut Render3dState, model: Model) -> Actor {
|
||||||
let a = new Actor
|
let a = actor_fresh(render3d_st)
|
||||||
a.model = model
|
a.model = model
|
||||||
a.scale = 1.0
|
a.scale = 1.0
|
||||||
a.tint = v3_new(1.0, 1.0, 1.0)
|
|
||||||
a.rough = 1.0
|
a.rough = 1.0
|
||||||
a.mat = m4_new()
|
|
||||||
render3d_st.ac_next_id += 1; a.id = render3d_st.ac_next_id
|
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 }
|
if model != null { a.radius = Math.max(model.radius, model.height) + 1.0 }
|
||||||
a.cull = 450.0
|
a.cull = 450.0
|
||||||
|
|
@ -417,3 +415,36 @@ function actor_clear_all(render3d_st: mut Render3dState) -> void {
|
||||||
List.clear(render3d_st.ac_actors)
|
List.clear(render3d_st.ac_actors)
|
||||||
outline_clear(render3d_st)
|
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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ export state Render3dState {
|
||||||
ac_out_cut: AcProg = null
|
ac_out_cut: AcProg = null
|
||||||
ac_actors: []Actor = null
|
ac_actors: []Actor = null
|
||||||
ac_next_id: int = 0
|
ac_next_id: int = 0
|
||||||
|
ac_spare: []Actor = null # released actors, taken again by actor_new (actor_release)
|
||||||
ac_frame: int = 0
|
ac_frame: int = 0
|
||||||
ac_oq: []OutlineReq = null
|
ac_oq: []OutlineReq = null
|
||||||
ac_oq_n: int = 0 # live entries; the array is kept and reused
|
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_flat: []int = null # every layout known, end to end; gvk_lay_off / _len find each
|
||||||
gvk_lay_off: []int = null
|
gvk_lay_off: []int = null
|
||||||
gvk_lay_len: []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
|
gvk_has_bc: bool = false # textureCompressionBC: .dds textures are uploaded compressed
|
||||||
# R3D_VK_PROF: Vulkan objects made and destroyed, reported every 120 frames
|
# R3D_VK_PROF: Vulkan objects made and destroyed, reported every 120 frames
|
||||||
gvk_mk_img: int = 0
|
gvk_mk_img: int = 0
|
||||||
|
|
@ -805,7 +808,6 @@ export state Render3dState {
|
||||||
tex_depth: int = 0 # bits per sample (8 or 16)
|
tex_depth: int = 0 # bits per sample (8 or 16)
|
||||||
tex_file_len: int = 0
|
tex_file_len: int = 0
|
||||||
tex_anisotropy: fixed = 16.0
|
tex_anisotropy: fixed = 16.0
|
||||||
tex_size_ids: []int = new []int
|
|
||||||
tex_size_ws: []int = new []int
|
tex_size_ws: []int = new []int
|
||||||
tex_size_hs: []int = new []int
|
tex_size_hs: []int = new []int
|
||||||
hdr_max_lum: float = 0.0 # float bits of the brightest texel (sun finding)
|
hdr_max_lum: float = 0.0 # float bits of the brightest texel (sun finding)
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
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 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 }
|
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
|
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) }
|
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
|
var png: string = uri
|
||||||
let n = len(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
|
let path = render3d_st.gltf_dir + "/" + png
|
||||||
|
if made { free(png) }
|
||||||
var i = 0
|
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
|
var dil = 0
|
||||||
if render3d_st.gltf_cutout { dil = 24 }
|
if render3d_st.gltf_cutout { dil = 24 }
|
||||||
let id = tex_load_ex(render3d_st, path, srgb, dil)
|
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
|
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.
|
# 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 {
|
function gltf_load(render3d_st: mut Render3dState, dir: string, file: string, node_name: string) -> Model {
|
||||||
render3d_st.gltf_dir = dir
|
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 }
|
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),
|
# 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)
|
# 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)
|
free(text)
|
||||||
let buffers = value_get(render3d_st.gltf_doc, "buffers")
|
let buffers = value_get(render3d_st.gltf_doc, "buffers")
|
||||||
let bin_uri = value_as_str(value_get(value_at(buffers, 0), "uri"))
|
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 }
|
if render3d_st.gltf_bin == null { print(`gltf: cannot open {bin_uri}`); return null }
|
||||||
let nodes = value_get(render3d_st.gltf_doc, "nodes")
|
let nodes = value_get(render3d_st.gltf_doc, "nodes")
|
||||||
var mesh_idx = -1
|
var mesh_idx = -1
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# model the first load made, found by comparing the names in place, so asking allocates nothing. For
|
# 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
|
# 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
|
# 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 {
|
export function gltf_cached(render3d_st: mut Render3dState, dir: string, file: string, node_name: string) -> Model {
|
||||||
if render3d_st.gltf_kept == null {
|
if render3d_st.gltf_kept == null {
|
||||||
render3d_st.gltf_kept = new []Model
|
render3d_st.gltf_kept = new []Model
|
||||||
|
|
@ -21,6 +21,23 @@ export function gltf_cached(render3d_st: mut Render3dState, dir: string, file: s
|
||||||
return m
|
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 {
|
export function gltf_cached_count(render3d_st: Render3dState) -> int {
|
||||||
if render3d_st.gltf_kept == null { return 0 }
|
if render3d_st.gltf_kept == null { return 0 }
|
||||||
return len(render3d_st.gltf_kept)
|
return len(render3d_st.gltf_kept)
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
function gltf_tex_forget(render3d_st: mut Render3dState, i: int) -> void {
|
||||||
let last = render3d_st.gltf_tex_n - 1
|
let last = render3d_st.gltf_tex_n - 1
|
||||||
let paths = render3d_st.gltf_tex_paths
|
let paths = render3d_st.gltf_tex_paths
|
||||||
|
free(paths[i])
|
||||||
paths[i] = paths[last]
|
paths[i] = paths[last]
|
||||||
render3d_st.gltf_tex_ids[i] = render3d_st.gltf_tex_ids[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]
|
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 {
|
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 last = render3d_st.gltf_mat_n - 1
|
||||||
let names = render3d_st.gltf_mat_names
|
let names = render3d_st.gltf_mat_names
|
||||||
|
free(names[k])
|
||||||
names[k] = names[last]
|
names[k] = names[last]
|
||||||
render3d_st.gltf_mat_diff[k] = render3d_st.gltf_mat_diff[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]
|
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
|
# Let a model go: every primitive's mesh, and its textures once no other model uses it; the
|
||||||
# caller says when - no actor may draw the model afterwards (a released model's prims are emptied,
|
# primitives, their meshes and the model itself are freed, and a model gltf_cached handed out
|
||||||
# so a stale draw of it draws nothing).
|
# 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 {
|
function model_release(render3d_st: mut Render3dState, m: Model) -> void {
|
||||||
if m == null or m.prims == null { return }
|
if m == null { return }
|
||||||
for i in 0 .. len(m.prims) {
|
gltf_cached_forget(render3d_st, m)
|
||||||
let pr = m.prims[i]
|
if m.prims != null {
|
||||||
if pr.mesh != null { gpu_mesh_free(render3d_st, pr.mesh); pr.mesh = null }
|
for i in 0 .. len(m.prims) {
|
||||||
gltf_tex_unref(render3d_st, pr.diff); gltf_tex_unref(render3d_st, pr.nrm); gltf_tex_unref(render3d_st, pr.arm)
|
let pr = m.prims[i]
|
||||||
pr.diff = 0; pr.nrm = 0; pr.arm = 0
|
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
|
skin_free(m.skin)
|
||||||
while len(prims) > 0 { List.pop(prims) }
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -599,7 +599,9 @@ function gpu_tex_free(render3d_st: mut Render3dState, tex: int) -> void {
|
||||||
if tex == 0 { return }
|
if tex == 0 { return }
|
||||||
let ids = gpu_tmp(render3d_st)
|
let ids = gpu_tmp(render3d_st)
|
||||||
ids[0] = tex
|
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_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 }
|
if render3d_st.gpu_bound_2d == tex { render3d_st.gpu_bound_2d = 0 }
|
||||||
let o = gpu_tx_at(render3d_st, tex)
|
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 } }
|
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 {
|
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) }
|
function gpu_viewport(render3d_st: mut Render3dState, x: int, y: int, w: int, h: int) -> void { gvk_viewport(render3d_st, x, y, w, h) }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
function gvk_mesh_free(render3d_st: mut Render3dState, m: Mesh) -> void {
|
||||||
if m == null { return }
|
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.n_vbufs = 0
|
||||||
m.vbo = 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 {
|
function gvk_scissor(render3d_st: mut Render3dState, x: int, y: int, w: int, h: int) -> void {
|
||||||
|
|
|
||||||
|
|
@ -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
|
# 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.
|
# 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 {
|
function gvk_tex_new(render3d_st: mut Render3dState) -> int {
|
||||||
let zero: long = 0
|
let zero: long = 0
|
||||||
if render3d_st.gvk_tex_image == null {
|
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_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_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_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_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)
|
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
|
# 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)
|
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)
|
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
|
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 {
|
function gvk_buf_release(render3d_st: mut Render3dState, b: int) -> void {
|
||||||
if render3d_st.gvk_buf[b] == 0 { return }
|
if render3d_st.gvk_buf[b] == 0 { return }
|
||||||
let zero: long = 0
|
let zero: long = 0
|
||||||
|
|
|
||||||
|
|
@ -227,3 +227,21 @@ function skin_clone(src: Skin) -> Skin {
|
||||||
skin_pose(sk)
|
skin_pose(sk)
|
||||||
return 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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -284,22 +284,23 @@ function tex_load_ex(render3d_st: mut Render3dState, path: pointer, srgb: bool,
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
# the size each loaded texture was, by id: a nine-slice or a UI image asks
|
# 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 {
|
function tex_note_size(render3d_st: mut Render3dState, id: int) -> void {
|
||||||
push(render3d_st.tex_size_ids, id)
|
if id <= 0 { return }
|
||||||
push(render3d_st.tex_size_ws, render3d_st.tex_w)
|
while len(render3d_st.tex_size_ws) <= id { push(render3d_st.tex_size_ws, 0); push(render3d_st.tex_size_hs, 0) }
|
||||||
push(render3d_st.tex_size_hs, render3d_st.tex_h)
|
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 {
|
function tex_width(render3d_st: Render3dState, id: int) -> int {
|
||||||
for i in 0 .. len(render3d_st.tex_size_ids) {
|
if id <= 0 or id >= len(render3d_st.tex_size_ws) { return 0 }
|
||||||
if render3d_st.tex_size_ids[i] == id { return render3d_st.tex_size_ws[i] }
|
return render3d_st.tex_size_ws[id]
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
function tex_height(render3d_st: Render3dState, id: int) -> int {
|
function tex_height(render3d_st: Render3dState, id: int) -> int {
|
||||||
for i in 0 .. len(render3d_st.tex_size_ids) {
|
if id <= 0 or id >= len(render3d_st.tex_size_hs) { return 0 }
|
||||||
if render3d_st.tex_size_ids[i] == id { return render3d_st.tex_size_hs[i] }
|
return render3d_st.tex_size_hs[id]
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# A small solid-colour fallback texture (linear rgb 0..255), for missing maps.
|
# A small solid-colour fallback texture (linear rgb 0..255), for missing maps.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue