render3d: free a texture's create infos and the .dds path it looked for; steady's model at 0
gvk_tex_storage freed none of ici, out, req and vci (8 blocks a two-texture model); tex_load_ex kept dds_path_of's string. Found with malloc_history over 400 load/release rounds (712 bytes a round, all of it these). steady: the model's bound is 4 KB over 200 (was 1.6 MB), and the frame is the least of three settled windows - a valley self-test beside it read 87 KB once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
328dee77c8
commit
1adce5b58b
4 changed files with 43 additions and 14 deletions
7
changes/render3d-texture-scratch.md
Normal file
7
changes/render3d-texture-scratch.md
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
bump: patch
|
||||||
|
type: fix
|
||||||
|
**A texture made and freed leaves nothing behind.** Making a texture's image once kept four small
|
||||||
|
Vulkan create-info buffers, and a texture loaded from a .png kept the .dds path it looked for
|
||||||
|
beside it; both are given back now. `examples/rendering/steady.ludic` holds a glTF model loaded and
|
||||||
|
released at 0 bytes, and reads a frame's growth as the least of three windows after the device is
|
||||||
|
idle, so a busy machine beside it does not fail it.
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# steady.ludic - render3d allocates nothing in its steady state: a buffer released and made again
|
# steady.ludic - render3d allocates nothing in its steady state: a buffer released and made again
|
||||||
# (what a streamed or grown buffer does every few frames) and a whole frame, drawn over and over,
|
# (what a streamed or grown buffer does every few frames) and a whole frame, drawn over and over,
|
||||||
# leave malloc's live bytes where they were. Prints STEADY OK. The heap is read on the Mac
|
# leave malloc's live bytes where they were. Prints STEADY OK. The heap is read on the Mac
|
||||||
# (Vk.heap_bytes); elsewhere it reads 0 and the check passes on the run alone.
|
# (Vk.heap_bytes) once the device is idle; elsewhere it reads 0 and the check passes on the run alone.
|
||||||
#
|
#
|
||||||
# bin/ludic build examples/rendering/steady.ludic --headless && ./build/steady_headless
|
# bin/ludic build examples/rendering/steady.ludic --headless && ./build/steady_headless
|
||||||
program Steady {
|
program Steady {
|
||||||
|
|
@ -95,7 +95,13 @@ program Steady {
|
||||||
buffer_rounds(render3d_st, b, 500)
|
buffer_rounds(render3d_st, b, 500)
|
||||||
let grew_b = buffer_rounds(render3d_st, b, 5000)
|
let grew_b = buffer_rounds(render3d_st, b, 5000)
|
||||||
frame_rounds(render3d_st, 120)
|
frame_rounds(render3d_st, 120)
|
||||||
let grew_f = frame_rounds(render3d_st, 600)
|
# the least of three windows: a leak grows in every one, while a busy GPU's lag (a valley
|
||||||
|
# self-test beside it once read 87 KB) does not repeat three times
|
||||||
|
var grew_f = frame_rounds(render3d_st, 600)
|
||||||
|
for w in 0 .. 2 {
|
||||||
|
let g = frame_rounds(render3d_st, 600)
|
||||||
|
if g < grew_f { grew_f = g }
|
||||||
|
}
|
||||||
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")
|
let am = gltf_load(render3d_st, "packages/ludic.lab/plate", "plate.gltf", "plate")
|
||||||
|
|
@ -116,9 +122,9 @@ program Steady {
|
||||||
ok = false
|
ok = false
|
||||||
print("steady: FAILED - a parsed document freed whole leaves memory behind")
|
print("steady: FAILED - a parsed document freed whole leaves memory behind")
|
||||||
}
|
}
|
||||||
# a load keeps a few records of its own for now (handles are not reused); a document's strings
|
# a load and a release leave nothing: 0 over 200 (a document's strings were ~640 KB a model, a
|
||||||
# left behind were ~640 KB a model, so a bound of 8 KB a round is what this holds
|
# texture's create infos and its .dds path ~700 bytes a round)
|
||||||
if grew_m >= 200 * 8192 {
|
if grew_m >= 4096 {
|
||||||
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")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -212,11 +212,18 @@ function gvk_tex_storage(render3d_st: mut Render3dState, tex: int, array: bool,
|
||||||
let out = bytes(8)
|
let out = bytes(8)
|
||||||
render3d_st.gvk_mk_img += 1
|
render3d_st.gvk_mk_img += 1
|
||||||
var r = Vk.create_image(render3d_st.gvk_dev, ici, null, out)
|
var r = Vk.create_image(render3d_st.gvk_dev, ici, null, out)
|
||||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, `vkCreateImage {w}x{h}x{layers} format {vkfmt}`, r) }
|
# the create infos are read by the call they are handed to and go straight after (a texture
|
||||||
|
# made in play once left four of them behind)
|
||||||
|
free(ici)
|
||||||
|
if r != VK_SUCCESS {
|
||||||
|
free(out)
|
||||||
|
return gvk_fail(render3d_st, `vkCreateImage {w}x{h}x{layers} format {vkfmt}`, r)
|
||||||
|
}
|
||||||
let image = gvk_handle(out)
|
let image = gvk_handle(out)
|
||||||
let req = bytes(VkMemoryRequirements_sizeof)
|
let req = bytes(VkMemoryRequirements_sizeof)
|
||||||
Vk.get_image_memory_requirements(render3d_st.gvk_dev, image, req)
|
Vk.get_image_memory_requirements(render3d_st.gvk_dev, image, req)
|
||||||
let ma = gvk_mem_new(render3d_st, req, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, true)
|
let ma = gvk_mem_new(render3d_st, req, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, true)
|
||||||
|
free(req)
|
||||||
let mem: long = ma # the allocation id (gvk_mem_new), kept where the memory was
|
let mem: long = ma # the allocation id (gvk_mem_new), kept where the memory was
|
||||||
let zero: long = 0
|
let zero: long = 0
|
||||||
# no memory for it (one allocation per resource meets the driver's allocation limit long before
|
# no memory for it (one allocation per resource meets the driver's allocation limit long before
|
||||||
|
|
@ -224,10 +231,14 @@ function gvk_tex_storage(render3d_st: mut Render3dState, tex: int, array: bool,
|
||||||
if mem == 0 {
|
if mem == 0 {
|
||||||
render3d_st.gvk_mk_x_img += 1
|
render3d_st.gvk_mk_x_img += 1
|
||||||
Vk.destroy_image(render3d_st.gvk_dev, image, null)
|
Vk.destroy_image(render3d_st.gvk_dev, image, null)
|
||||||
|
free(out)
|
||||||
return gvk_fail(render3d_st, `no device memory for a {w}x{h}x{layers} image ({render3d_st.gvk_n_allocs} allocations live)`, VK_ERROR_OUT_OF_DEVICE_MEMORY)
|
return gvk_fail(render3d_st, `no device memory for a {w}x{h}x{layers} image ({render3d_st.gvk_n_allocs} allocations live)`, VK_ERROR_OUT_OF_DEVICE_MEMORY)
|
||||||
}
|
}
|
||||||
r = Vk.bind_image_memory(render3d_st.gvk_dev, image, gvk_mem_handle(render3d_st, ma), gvk_mem_offset(render3d_st, ma))
|
r = Vk.bind_image_memory(render3d_st.gvk_dev, image, gvk_mem_handle(render3d_st, ma), gvk_mem_offset(render3d_st, ma))
|
||||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkBindImageMemory", r) }
|
if r != VK_SUCCESS {
|
||||||
|
free(out)
|
||||||
|
return gvk_fail(render3d_st, "vkBindImageMemory", r)
|
||||||
|
}
|
||||||
let vci = bytes(VkImageViewCreateInfo_sizeof)
|
let vci = bytes(VkImageViewCreateInfo_sizeof)
|
||||||
Vk.zero(vci, VkImageViewCreateInfo_sizeof)
|
Vk.zero(vci, VkImageViewCreateInfo_sizeof)
|
||||||
Vk.put_i32(vci, VkImageViewCreateInfo_sType, VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO)
|
Vk.put_i32(vci, VkImageViewCreateInfo_sType, VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO)
|
||||||
|
|
@ -240,8 +251,11 @@ function gvk_tex_storage(render3d_st: mut Render3dState, tex: int, array: bool,
|
||||||
Vk.put_i32(vci, sr + VkImageSubresourceRange_layerCount, layers)
|
Vk.put_i32(vci, sr + VkImageSubresourceRange_layerCount, layers)
|
||||||
render3d_st.gvk_mk_view += 1
|
render3d_st.gvk_mk_view += 1
|
||||||
r = Vk.create_image_view(render3d_st.gvk_dev, vci, null, out)
|
r = Vk.create_image_view(render3d_st.gvk_dev, vci, null, out)
|
||||||
|
free(vci)
|
||||||
|
let view = gvk_handle(out)
|
||||||
|
free(out)
|
||||||
if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkCreateImageView", r) }
|
if r != VK_SUCCESS { return gvk_fail(render3d_st, "vkCreateImageView", r) }
|
||||||
render3d_st.gvk_tex_image[tex] = image; render3d_st.gvk_tex_view[tex] = gvk_handle(out); render3d_st.gvk_tex_mem[tex] = mem
|
render3d_st.gvk_tex_image[tex] = image; render3d_st.gvk_tex_view[tex] = view; render3d_st.gvk_tex_mem[tex] = mem
|
||||||
render3d_st.gvk_tex_levels[tex] = levels; render3d_st.gvk_tex_layers[tex] = layers; render3d_st.gvk_tex_vkfmt[tex] = vkfmt
|
render3d_st.gvk_tex_levels[tex] = levels; render3d_st.gvk_tex_layers[tex] = layers; render3d_st.gvk_tex_vkfmt[tex] = vkfmt
|
||||||
render3d_st.gvk_tex_dims_w[tex] = w; render3d_st.gvk_tex_dims_h[tex] = h
|
render3d_st.gvk_tex_dims_w[tex] = w; render3d_st.gvk_tex_dims_h[tex] = h
|
||||||
render3d_st.gvk_tex_glfmt[tex] = ifmt; render3d_st.gvk_tex_gen[tex] = render3d_st.gvk_tex_gen[tex] + 1
|
render3d_st.gvk_tex_glfmt[tex] = ifmt; render3d_st.gvk_tex_gen[tex] = render3d_st.gvk_tex_gen[tex] + 1
|
||||||
|
|
|
||||||
|
|
@ -267,14 +267,16 @@ function tex_load_ex(render3d_st: mut Render3dState, path: pointer, srgb: bool,
|
||||||
# (not for a cut-out atlas: its edge padding is made here, from the .png)
|
# (not for a cut-out atlas: its edge padding is made here, from the .png)
|
||||||
if render3d_st.gvk_has_bc and dilate == 0 {
|
if render3d_st.gvk_has_bc and dilate == 0 {
|
||||||
let dds = dds_path_of(path)
|
let dds = dds_path_of(path)
|
||||||
if len(dds) > 0 and Fs.exists(dds) {
|
var t = 0
|
||||||
let t = tex_load_dds(render3d_st, dds, srgb)
|
if len(dds) > 0 {
|
||||||
|
if Fs.exists(dds) { t = tex_load_dds(render3d_st, dds, srgb) }
|
||||||
|
free(dds) # made for this question ("" is a literal)
|
||||||
|
}
|
||||||
if t != 0 {
|
if t != 0 {
|
||||||
tex_note_size(render3d_st, t)
|
tex_note_size(render3d_st, t)
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
let px = png_decode(render3d_st, path)
|
let px = png_decode(render3d_st, path)
|
||||||
if px == null { return 0 }
|
if px == null { return 0 }
|
||||||
if dilate > 0 { tex_dilate(render3d_st, px, 60, dilate) }
|
if dilate > 0 { tex_dilate(render3d_st, px, 60, dilate) }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue