diff --git a/changes/render3d-texture-scratch.md b/changes/render3d-texture-scratch.md new file mode 100644 index 00000000..331efbf7 --- /dev/null +++ b/changes/render3d-texture-scratch.md @@ -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. diff --git a/examples/rendering/steady.ludic b/examples/rendering/steady.ludic index c095e4d5..e9c03857 100644 --- a/examples/rendering/steady.ludic +++ b/examples/rendering/steady.ludic @@ -1,7 +1,7 @@ # 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, # 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 program Steady { @@ -95,7 +95,13 @@ program Steady { buffer_rounds(render3d_st, b, 500) let grew_b = buffer_rounds(render3d_st, b, 5000) 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) let grew_m = model_rounds(render3d_st, 200) let am = gltf_load(render3d_st, "packages/ludic.lab/plate", "plate.gltf", "plate") @@ -116,9 +122,9 @@ program Steady { ok = false 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 - # left behind were ~640 KB a model, so a bound of 8 KB a round is what this holds - if grew_m >= 200 * 8192 { + # a load and a release leave nothing: 0 over 200 (a document's strings were ~640 KB a model, a + # texture's create infos and its .dds path ~700 bytes a round) + if grew_m >= 4096 { ok = false print("steady: FAILED - a model loaded and let go leaves memory behind") } diff --git a/packages/ludic.render3d/gpu_vk_res.ludic b/packages/ludic.render3d/gpu_vk_res.ludic index c1631202..5b6cc088 100644 --- a/packages/ludic.render3d/gpu_vk_res.ludic +++ b/packages/ludic.render3d/gpu_vk_res.ludic @@ -212,11 +212,18 @@ function gvk_tex_storage(render3d_st: mut Render3dState, tex: int, array: bool, let out = bytes(8) render3d_st.gvk_mk_img += 1 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 req = bytes(VkMemoryRequirements_sizeof) 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) + free(req) let mem: long = ma # the allocation id (gvk_mem_new), kept where the memory was let zero: long = 0 # 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 { render3d_st.gvk_mk_x_img += 1 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) } 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) Vk.zero(vci, VkImageViewCreateInfo_sizeof) 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) render3d_st.gvk_mk_view += 1 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) } - 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_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 diff --git a/packages/ludic.render3d/texture.ludic b/packages/ludic.render3d/texture.ludic index 90bdaab6..f0659c5e 100644 --- a/packages/ludic.render3d/texture.ludic +++ b/packages/ludic.render3d/texture.ludic @@ -267,12 +267,14 @@ 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) if render3d_st.gvk_has_bc and dilate == 0 { let dds = dds_path_of(path) - if len(dds) > 0 and Fs.exists(dds) { - let t = tex_load_dds(render3d_st, dds, srgb) - if t != 0 { - tex_note_size(render3d_st, t) - return t - } + var t = 0 + 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 { + tex_note_size(render3d_st, t) + return t } } let px = png_decode(render3d_st, path)