From 0b89ed85b7bad031eef19a6ee28007d1ef2e9047 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Mon, 28 Sep 2026 16:58:31 +0300 Subject: [PATCH] render3d: @creates / @releases on its handles GpuBuffer (gpu_buffer_new/free), GpuTexture, GpuFramebuffer, GpuRenderbuffer, Target (target_new/free), Model (gltf_load / model_release), Mesh (gpu_mesh_new / gpu_mesh_free, mesh_release), Actor (actor_new / actor_release) and SkinClone (skin_clone / skin_clone_free), for ludic deps --resources. Compiled (steady). Co-Authored-By: Claude Opus 5.5 --- packages/ludic.render3d/actor.ludic | 2 ++ packages/ludic.render3d/gltf.ludic | 1 + packages/ludic.render3d/gltf_release.ludic | 2 ++ packages/ludic.render3d/gpu.ludic | 10 ++++++++++ packages/ludic.render3d/programs.ludic | 2 ++ packages/ludic.render3d/skin.ludic | 2 ++ 6 files changed, 19 insertions(+) diff --git a/packages/ludic.render3d/actor.ludic b/packages/ludic.render3d/actor.ludic index 3b0b84ae..665a5b48 100644 --- a/packages/ludic.render3d/actor.ludic +++ b/packages/ludic.render3d/actor.ludic @@ -139,6 +139,7 @@ function actor_hide_part(a: Actor, name: string, hidden: bool) -> void { for i in 0 .. n { if a.model.prims[i].name == name { a.hide[i] = v } } } +@creates(Actor) function actor_new(render3d_st: mut Render3dState, model: Model) -> Actor { let a = actor_fresh(render3d_st) a.model = model @@ -458,6 +459,7 @@ function actor_fresh(render3d_st: mut Render3dState) -> Actor { # 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. +@releases(Actor) 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) diff --git a/packages/ludic.render3d/gltf.ludic b/packages/ludic.render3d/gltf.ludic index 778386cd..f6ce8f02 100644 --- a/packages/ludic.render3d/gltf.ludic +++ b/packages/ludic.render3d/gltf.ludic @@ -176,6 +176,7 @@ function gltf_prim(render3d_st: mut Render3dState, p: Val) -> Prim { # Load the mesh of the node called `node_name` from dir/file. @alloc_ok("loading a model, a texture or a font: a load, not a frame (a guest loading a teammate's look is one)") +@creates(Model) function gltf_load(render3d_st: mut Render3dState, dir: string, file: string, node_name: string) -> Model { render3d_st.gltf_dir = dir let at = dir + "/" + file diff --git a/packages/ludic.render3d/gltf_release.ludic b/packages/ludic.render3d/gltf_release.ludic index d4a0ff5d..355759a3 100644 --- a/packages/ludic.render3d/gltf_release.ludic +++ b/packages/ludic.render3d/gltf_release.ludic @@ -60,6 +60,7 @@ function gltf_mats_forget(render3d_st: mut Render3dState, id: int) -> void { # 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). +@releases(Model) function model_release(render3d_st: mut Render3dState, m: Model) -> void { if m == null { return } gltf_cached_forget(render3d_st, m) @@ -78,6 +79,7 @@ function model_release(render3d_st: mut Render3dState, m: Model) -> void { } # a mesh and what it holds: its buffers (their handles given back), its layout and buffer lists +@releases(Mesh) function mesh_release(render3d_st: mut Render3dState, me: Mesh) -> void { gpu_mesh_free(render3d_st, me) if me.attrs != null { free(me.attrs) } diff --git a/packages/ludic.render3d/gpu.ludic b/packages/ludic.render3d/gpu.ludic index c6ef609d..07ea1439 100644 --- a/packages/ludic.render3d/gpu.ludic +++ b/packages/ludic.render3d/gpu.ludic @@ -367,6 +367,7 @@ function gpu_gl_usage(u: int) -> int { # a new mesh, its vertex array bound: the vertex, attribute and index calls below describe it @alloc_ok("loading a model, a texture or a font: a load, not a frame (a guest loading a teammate's look is one)") +@creates(Mesh) function gpu_mesh_new(render3d_st: Render3dState) -> Mesh { let m = new Mesh m.attrs = words(GPU_MAX_ATTRS * GPU_ATTR_W) @@ -423,10 +424,12 @@ function gpu_mesh_attr_inst(render3d_st: Render3dState, m: Mesh, index: int, com } # a buffer on its own (instances, a stream): made, filled whole, freed +@creates(GpuBuffer) function gpu_buffer_new(render3d_st: mut Render3dState) -> int { return gvk_buf_new(render3d_st) } function gpu_buffer_upload(render3d_st: mut Render3dState, buf: int, nbytes: int, data: pointer, usage: int) -> void { gvk_buf_upload(render3d_st, buf, nbytes, data) } +@releases(GpuBuffer) function gpu_buffer_free(render3d_st: mut Render3dState, buf: int) -> void { if buf > 0 { gvk_buf_release(render3d_st, buf) } } @@ -497,6 +500,7 @@ function gpu_draw_range(render3d_st: mut Render3dState, m: Mesh, first: int, cou gvk_draw_now(render3d_st, m, first, count, 1) } +@releases(Mesh) function gpu_mesh_free(render3d_st: mut Render3dState, m: Mesh) -> void { gvk_mesh_free(render3d_st, m) } @@ -530,6 +534,7 @@ function gpu_tx_at(render3d_st: mut Render3dState, tex: int) -> int { } function gpu_bound(render3d_st: Render3dState, kind: int) -> int { if kind == GPU_TEX2D_ARRAY { return render3d_st.gpu_bound_array }; return render3d_st.gpu_bound_2d } +@creates(GpuTexture) function gpu_tex_new(render3d_st: mut Render3dState) -> int { return gvk_tex_new(render3d_st) } # does texture `tex` have an image behind it? Always on OpenGL; on Vulkan an image whose memory could # not be had is never made, and a caller that can fall back (a smaller shadow map) asks here. @@ -604,6 +609,7 @@ function gpu_tex_mips(render3d_st: mut Render3dState, kind: int) -> void { function gpu_tex_read(render3d_st: mut Render3dState, kind: int, fmt: int, ty: int, out: pointer) -> void { gvk_flush(render3d_st); let rt = gpu_bound(render3d_st, kind); let ro = gpu_tx_at(render3d_st, rt); if ro >= 0 { gvk_tex_read(render3d_st, rt, render3d_st.gpu_tx[ro + 4], render3d_st.gpu_tx[ro + 1], render3d_st.gpu_tx[ro + 2], fmt, ty, out) } } +@releases(GpuTexture) function gpu_tex_free(render3d_st: mut Render3dState, tex: int) -> void { if tex == 0 { return } let ids = gpu_tmp(render3d_st) @@ -654,6 +660,7 @@ function gpu_check(render3d_st: Render3dState, tag: string) -> int { return 0 } # a named checkpoint that costs nothing unless R3D_GLCHECK is set function gpu_debug_check(render3d_st: mut Render3dState, tag: string) -> void { } +@creates(GpuFramebuffer) function gpu_fb_new(render3d_st: mut Render3dState) -> int { render3d_st.gvk_fb_counter += 1; return render3d_st.gvk_fb_counter } function gpu_fb_bind(render3d_st: mut Render3dState, fb: int) -> void { gvk_rebind(render3d_st, fb) @@ -681,6 +688,7 @@ function gpu_fb_depth_layer(render3d_st: mut Render3dState, tex: int, layer: int let o = gpu_fb_at(render3d_st, render3d_st.gpu_fb_cur) if o >= 0 { render3d_st.gpu_fb[o + 2] = tex; render3d_st.gpu_fb[o + 3] = layer + 1 } } +@creates(GpuRenderbuffer) function gpu_rb_new(render3d_st: mut Render3dState) -> int { return gvk_tex_new(render3d_st) } @@ -706,9 +714,11 @@ function gpu_fb_no_color(render3d_st: mut Render3dState) -> void { gvk_fb_colors(render3d_st, render3d_st.gpu_fb_cur, 0) } function gpu_fb_status(render3d_st: Render3dState) -> int { return GL_FRAMEBUFFER_COMPLETE } +@releases(GpuFramebuffer) 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 } } } +@releases(GpuRenderbuffer) function gpu_rb_free(render3d_st: mut Render3dState, rb: int) -> void { gvk_flush(render3d_st) gvk_tex_give_back(render3d_st, rb) diff --git a/packages/ludic.render3d/programs.ludic b/packages/ludic.render3d/programs.ludic index 6b0b1ac6..8ef33db4 100644 --- a/packages/ludic.render3d/programs.ludic +++ b/packages/ludic.render3d/programs.ludic @@ -71,6 +71,7 @@ property Target { h: int = 0 } @alloc_ok("made once per resource and kept for its life (a texture, program, sampler, view, layout or memory block is created when first asked for)") +@creates(Target) function target_new(render3d_st: mut Render3dState, w: int, h: int, ifmt: int, fmt: int, ty: int, with_depth: bool, filter: int) -> Target { let t = new Target t.w = w; t.h = h @@ -87,6 +88,7 @@ function target_new(render3d_st: mut Render3dState, w: int, h: int, ifmt: int, f gpu_fb_bind(render3d_st, 0) return t } +@releases(Target) function target_free(render3d_st: mut Render3dState, t: Target) -> void { if t == null { return } gpu_fb_free(render3d_st, t.fbo) diff --git a/packages/ludic.render3d/skin.ludic b/packages/ludic.render3d/skin.ludic index 92d6f554..03175ca2 100644 --- a/packages/ludic.render3d/skin.ludic +++ b/packages/ludic.render3d/skin.ludic @@ -212,6 +212,7 @@ function skin_bind(render3d_st: mut Render3dState, sk: Skin, prog: int) -> void } # the same skeleton posed on its own: shares the rest data, owns the pose and the matrices +@creates(SkinClone) function skin_clone(src: Skin) -> Skin { let sk = new Skin sk.n_nodes = src.n_nodes; sk.par = src.par; sk.walk = src.walk @@ -249,6 +250,7 @@ function skin_free(sk: Skin) -> void { # A skin_clone given back: what the clone made for itself (its pose, its matrices and their views, its # scratch). The rest data, the joints and the names are its source's and stay. +@releases(SkinClone) function skin_clone_free(sk: Skin) -> void { if sk == null { return } free(sk.pose_r); free(sk.pose_t); free(sk.gmat); free(sk.bones)