diff --git a/packages/ludic.render3d/actor.ludic b/packages/ludic.render3d/actor.ludic index 91bd697b..748584b4 100644 --- a/packages/ludic.render3d/actor.ludic +++ b/packages/ludic.render3d/actor.ludic @@ -245,6 +245,7 @@ property OutlineReq { # on the actor; a queue does not, if the first pass empties it. So a flush CLOSES the # batch rather than clearing it, and the next add opens a new one: every pass in a frame # sees the same requests, and the caller needs no frame hook. +@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)") function outline_model(render3d_st: mut Render3dState, m: Model, mat: floats, width: float, r: float, g: float, b: float) -> void { if m == null or mat == null or width == 0.0 { return } if render3d_st.ac_oq_closed { render3d_st.ac_oq_n = 0; render3d_st.ac_oq_closed = false } diff --git a/packages/ludic.render3d/camera.ludic b/packages/ludic.render3d/camera.ludic index 1adc76b0..acd9fd87 100644 --- a/packages/ludic.render3d/camera.ludic +++ b/packages/ludic.render3d/camera.ludic @@ -33,10 +33,10 @@ function cam_update(render3d_st: mut Render3dState) -> void { let cp = Math.cos(render3d_st.cam_pitch); let sp = Math.sin(render3d_st.cam_pitch) v3_set(render3d_st.cam_fwd, -(sy * cp), sp, -(cy * cp)) v3_set(render3d_st.cam_right, cy, 0.0, -sy) - let at = floats(3) - v3_add(at, render3d_st.cam_pos, render3d_st.cam_fwd) - let up = v3_new(0.0, 1.0, 0.0) - m4_look_at(render3d_st.cam_view, render3d_st.cam_pos, at, up) + let px = render3d_st.cam_pos[0] + let py = render3d_st.cam_pos[1] + let pz = render3d_st.cam_pos[2] + m4_look_at_xyz(render3d_st.cam_view, px, py, pz, px + render3d_st.cam_fwd[0], py + render3d_st.cam_fwd[1], pz + render3d_st.cam_fwd[2], 0.0, 1.0, 0.0) m4_perspective(render3d_st.cam_proj, render3d_st.cam_fov, render3d_st.cam_aspect, render3d_st.cam_near, render3d_st.cam_far) m4_mul(render3d_st.cam_vp_clean, render3d_st.cam_proj, render3d_st.cam_view) m4_inverse(render3d_st.cam_inv_vp_clean, render3d_st.cam_vp_clean) @@ -49,7 +49,6 @@ function cam_update(render3d_st: mut Render3dState) -> void { m4_mul(render3d_st.cam_vp, render3d_st.cam_proj, render3d_st.cam_view) m4_inverse(render3d_st.cam_inv_vp, render3d_st.cam_vp) m4_inverse(render3d_st.cam_inv_proj, render3d_st.cam_proj) - free(at); free(up) cam_planes_update(render3d_st) } function cam_planes_update(render3d_st: mut Render3dState) -> void { diff --git a/packages/ludic.render3d/drawstats.ludic b/packages/ludic.render3d/drawstats.ludic index d6e32bb9..756674a5 100644 --- a/packages/ludic.render3d/drawstats.ludic +++ b/packages/ludic.render3d/drawstats.ludic @@ -31,6 +31,7 @@ const DS_KINDS: int = 5 # rows: (pass, program, kind) +@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS") function ds_enabled(render3d_st: mut Render3dState) -> bool { if render3d_st.ds_on < 0 { render3d_st.ds_on = 0 @@ -49,6 +50,7 @@ function ds_enabled(render3d_st: mut Render3dState) -> bool { } # the profiler's pass boundaries (prof_begin / prof_end), whether or not R3D_PROF is on +@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS") function ds_set_pass(render3d_st: mut Render3dState, name: pointer) -> void { if render3d_st.ds_on != 1 { return } render3d_st.ds_last = -1 @@ -76,6 +78,7 @@ function ds_frame(render3d_st: mut Render3dState) -> void { } } +@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS") function ds_draw(render3d_st: mut Render3dState, kind: int, instances: int, indices: int) -> void { if render3d_st.ds_on != 1 or not render3d_st.ds_counting { return } let p = render3d_st.gpu_prog_cur @@ -125,6 +128,7 @@ function ds_backend(render3d_st: Render3dState) -> pointer { return "opengl" } +@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS") function ds_report(render3d_st: mut Render3dState) -> void { let n_rows = len(render3d_st.ds_row_pass) let n_pass = len(render3d_st.ds_pass_names) diff --git a/packages/ludic.render3d/fmath.ludic b/packages/ludic.render3d/fmath.ludic index 0349ceb1..54962cd6 100644 --- a/packages/ludic.render3d/fmath.ludic +++ b/packages/ludic.render3d/fmath.ludic @@ -157,45 +157,55 @@ function m4_ortho(m: floats, l: float, r: float, b: float, t: float, n: float, f m[14] = -((f + n) / (f - n)) } function m4_look_at(m: floats, eye: floats, at: floats, up: floats) -> void { - let fwd = floats(3); let side = floats(3); let u = floats(3); let t = floats(3) - v3_sub(t, at, eye) - v3_normalize(fwd, t) - v3_cross(t, fwd, up) - v3_normalize(side, t) - v3_cross(u, side, fwd) + m4_look_at_xyz(m, eye[0], eye[1], eye[2], at[0], at[1], at[2], up[0], up[1], up[2]) +} +# the same from nine numbers, on scalars: the camera asks it every frame, and four scratch vectors +# made and freed per call were churn +function m4_look_at_xyz(m: floats, ex: float, ey: float, ez: float, ax: float, ay: float, az: float, ux: float, uy: float, uz: float) -> void { + var fx = ax - ex + var fy = ay - ey + var fz = az - ez + var fl = Math.sqrt(fx * fx + fy * fy + fz * fz) + if fl > 0.0 { fx = fx / fl; fy = fy / fl; fz = fz / fl } + var sx = fy * uz - fz * uy + var sy = fz * ux - fx * uz + var sz = fx * uy - fy * ux + let sl = Math.sqrt(sx * sx + sy * sy + sz * sz) + if sl > 0.0 { sx = sx / sl; sy = sy / sl; sz = sz / sl } + let vx = sy * fz - sz * fy + let vy = sz * fx - sx * fz + let vz = sx * fy - sy * fx m4_identity(m) - m[0] = side[0]; m[4] = side[1]; m[8] = side[2] - m[1] = u[0]; m[5] = u[1]; m[9] = u[2] - m[2] = -fwd[0]; m[6] = -fwd[1]; m[10] = -fwd[2] - m[12] = -v3_dot(side, eye) - m[13] = -v3_dot(u, eye) - m[14] = v3_dot(fwd, eye) - free(fwd); free(side); free(u); free(t) + m[0] = sx; m[4] = sy; m[8] = sz + m[1] = vx; m[5] = vy; m[9] = vz + m[2] = -fx; m[6] = -fy; m[10] = -fz + m[12] = -(sx * ex + sy * ey + sz * ez) + m[13] = -(vx * ex + vy * ey + vz * ez) + m[14] = fx * ex + fy * ey + fz * ez } # general 4x4 inverse (cofactor expansion); o may not alias a function m4_inverse(o: floats, a: floats) -> bool { - let inv = floats(16) - inv[0] = a[5] * (a[10] * a[15]) + -(a[5] * (a[11] * a[14])) - a[9] * (a[6] * a[15]) + (a[9] * (a[7] * a[14]) + (a[13] * (a[6] * a[11]) - a[13] * (a[7] * a[10]))) - inv[4] = -(a[4] * (a[10] * a[15])) + a[4] * (a[11] * a[14]) - a[8] * (a[7] * a[14]) + (a[8] * (a[6] * a[15]) + (a[12] * (a[7] * a[10]) - a[12] * (a[6] * a[11]))) - inv[8] = a[4] * (a[9] * a[15]) + -(a[4] * (a[11] * a[13])) - a[8] * (a[5] * a[15]) + (a[8] * (a[7] * a[13]) + (a[12] * (a[5] * a[11]) - a[12] * (a[7] * a[9]))) - inv[12] = -(a[4] * (a[9] * a[14])) + a[4] * (a[10] * a[13]) - a[8] * (a[6] * a[13]) + (a[8] * (a[5] * a[14]) + (a[12] * (a[6] * a[9]) - a[12] * (a[5] * a[10]))) - inv[1] = -(a[1] * (a[10] * a[15])) + a[1] * (a[11] * a[14]) - a[9] * (a[3] * a[14]) + (a[9] * (a[2] * a[15]) + (a[13] * (a[3] * a[10]) - a[13] * (a[2] * a[11]))) - inv[5] = a[0] * (a[10] * a[15]) + -(a[0] * (a[11] * a[14])) - a[8] * (a[2] * a[15]) + (a[8] * (a[3] * a[14]) + (a[12] * (a[2] * a[11]) - a[12] * (a[3] * a[10]))) - inv[9] = -(a[0] * (a[9] * a[15])) + a[0] * (a[11] * a[13]) - a[8] * (a[3] * a[13]) + (a[8] * (a[1] * a[15]) + (a[12] * (a[3] * a[9]) - a[12] * (a[1] * a[11]))) - inv[13] = a[0] * (a[9] * a[14]) + -(a[0] * (a[10] * a[13])) - a[8] * (a[1] * a[14]) + (a[8] * (a[2] * a[13]) + (a[12] * (a[1] * a[10]) - a[12] * (a[2] * a[9]))) - inv[2] = a[1] * (a[6] * a[15]) + -(a[1] * (a[7] * a[14])) - a[5] * (a[2] * a[15]) + (a[5] * (a[3] * a[14]) + (a[13] * (a[2] * a[7]) - a[13] * (a[3] * a[6]))) - inv[6] = -(a[0] * (a[6] * a[15])) + a[0] * (a[7] * a[14]) - a[4] * (a[3] * a[14]) + (a[4] * (a[2] * a[15]) + (a[12] * (a[3] * a[6]) - a[12] * (a[2] * a[7]))) - inv[10] = a[0] * (a[5] * a[15]) + -(a[0] * (a[7] * a[13])) - a[4] * (a[1] * a[15]) + (a[4] * (a[3] * a[13]) + (a[12] * (a[1] * a[7]) - a[12] * (a[3] * a[5]))) - inv[14] = -(a[0] * (a[5] * a[14])) + a[0] * (a[6] * a[13]) - a[4] * (a[2] * a[13]) + (a[4] * (a[1] * a[14]) + (a[12] * (a[2] * a[5]) - a[12] * (a[1] * a[6]))) - inv[3] = -(a[1] * (a[6] * a[11])) + a[1] * (a[7] * a[10]) - a[5] * (a[3] * a[10]) + (a[5] * (a[2] * a[11]) + (a[9] * (a[3] * a[6]) - a[9] * (a[2] * a[7]))) - inv[7] = a[0] * (a[6] * a[11]) + -(a[0] * (a[7] * a[10])) - a[4] * (a[2] * a[11]) + (a[4] * (a[3] * a[10]) + (a[8] * (a[2] * a[7]) - a[8] * (a[3] * a[6]))) - inv[11] = -(a[0] * (a[5] * a[11])) + a[0] * (a[7] * a[9]) - a[4] * (a[3] * a[9]) + (a[4] * (a[1] * a[11]) + (a[8] * (a[3] * a[5]) - a[8] * (a[1] * a[7]))) - inv[15] = a[0] * (a[5] * a[10]) + -(a[0] * (a[6] * a[9])) - a[4] * (a[1] * a[10]) + (a[4] * (a[2] * a[9]) + (a[8] * (a[1] * a[6]) - a[8] * (a[2] * a[5]))) - let det = a[0] * inv[0] + a[1] * inv[4] + (a[2] * inv[8] + a[3] * inv[12]) - if det == 0.0 { free(inv); return false } + let i0 = a[5] * (a[10] * a[15]) + -(a[5] * (a[11] * a[14])) - a[9] * (a[6] * a[15]) + (a[9] * (a[7] * a[14]) + (a[13] * (a[6] * a[11]) - a[13] * (a[7] * a[10]))) + let i4 = -(a[4] * (a[10] * a[15])) + a[4] * (a[11] * a[14]) - a[8] * (a[7] * a[14]) + (a[8] * (a[6] * a[15]) + (a[12] * (a[7] * a[10]) - a[12] * (a[6] * a[11]))) + let i8 = a[4] * (a[9] * a[15]) + -(a[4] * (a[11] * a[13])) - a[8] * (a[5] * a[15]) + (a[8] * (a[7] * a[13]) + (a[12] * (a[5] * a[11]) - a[12] * (a[7] * a[9]))) + let i12 = -(a[4] * (a[9] * a[14])) + a[4] * (a[10] * a[13]) - a[8] * (a[6] * a[13]) + (a[8] * (a[5] * a[14]) + (a[12] * (a[6] * a[9]) - a[12] * (a[5] * a[10]))) + let i1 = -(a[1] * (a[10] * a[15])) + a[1] * (a[11] * a[14]) - a[9] * (a[3] * a[14]) + (a[9] * (a[2] * a[15]) + (a[13] * (a[3] * a[10]) - a[13] * (a[2] * a[11]))) + let i5 = a[0] * (a[10] * a[15]) + -(a[0] * (a[11] * a[14])) - a[8] * (a[2] * a[15]) + (a[8] * (a[3] * a[14]) + (a[12] * (a[2] * a[11]) - a[12] * (a[3] * a[10]))) + let i9 = -(a[0] * (a[9] * a[15])) + a[0] * (a[11] * a[13]) - a[8] * (a[3] * a[13]) + (a[8] * (a[1] * a[15]) + (a[12] * (a[3] * a[9]) - a[12] * (a[1] * a[11]))) + let i13 = a[0] * (a[9] * a[14]) + -(a[0] * (a[10] * a[13])) - a[8] * (a[1] * a[14]) + (a[8] * (a[2] * a[13]) + (a[12] * (a[1] * a[10]) - a[12] * (a[2] * a[9]))) + let i2 = a[1] * (a[6] * a[15]) + -(a[1] * (a[7] * a[14])) - a[5] * (a[2] * a[15]) + (a[5] * (a[3] * a[14]) + (a[13] * (a[2] * a[7]) - a[13] * (a[3] * a[6]))) + let i6 = -(a[0] * (a[6] * a[15])) + a[0] * (a[7] * a[14]) - a[4] * (a[3] * a[14]) + (a[4] * (a[2] * a[15]) + (a[12] * (a[3] * a[6]) - a[12] * (a[2] * a[7]))) + let i10 = a[0] * (a[5] * a[15]) + -(a[0] * (a[7] * a[13])) - a[4] * (a[1] * a[15]) + (a[4] * (a[3] * a[13]) + (a[12] * (a[1] * a[7]) - a[12] * (a[3] * a[5]))) + let i14 = -(a[0] * (a[5] * a[14])) + a[0] * (a[6] * a[13]) - a[4] * (a[2] * a[13]) + (a[4] * (a[1] * a[14]) + (a[12] * (a[2] * a[5]) - a[12] * (a[1] * a[6]))) + let i3 = -(a[1] * (a[6] * a[11])) + a[1] * (a[7] * a[10]) - a[5] * (a[3] * a[10]) + (a[5] * (a[2] * a[11]) + (a[9] * (a[3] * a[6]) - a[9] * (a[2] * a[7]))) + let i7 = a[0] * (a[6] * a[11]) + -(a[0] * (a[7] * a[10])) - a[4] * (a[2] * a[11]) + (a[4] * (a[3] * a[10]) + (a[8] * (a[2] * a[7]) - a[8] * (a[3] * a[6]))) + let i11 = -(a[0] * (a[5] * a[11])) + a[0] * (a[7] * a[9]) - a[4] * (a[3] * a[9]) + (a[4] * (a[1] * a[11]) + (a[8] * (a[3] * a[5]) - a[8] * (a[1] * a[7]))) + let i15 = a[0] * (a[5] * a[10]) + -(a[0] * (a[6] * a[9])) - a[4] * (a[1] * a[10]) + (a[4] * (a[2] * a[9]) + (a[8] * (a[1] * a[6]) - a[8] * (a[2] * a[5]))) + let det = a[0] * i0 + a[1] * i4 + (a[2] * i8 + a[3] * i12) + if det == 0.0 { return false } let id = 1.0 / det - for i in 0 .. 16 { o[i] = inv[i] * id } - free(inv) + o[0] = i0 * id; o[1] = i1 * id; o[2] = i2 * id; o[3] = i3 * id; o[4] = i4 * id; o[5] = i5 * id; o[6] = i6 * id; o[7] = i7 * id + o[8] = i8 * id; o[9] = i9 * id; o[10] = i10 * id; o[11] = i11 * id; o[12] = i12 * id; o[13] = i13 * id; o[14] = i14 * id; o[15] = i15 * id return true } # transform a point (w = 1) by m: o = m * (x, y, z, 1), returns w diff --git a/packages/ludic.render3d/gltf.ludic b/packages/ludic.render3d/gltf.ludic index 92c8e138..778386cd 100644 --- a/packages/ludic.render3d/gltf.ludic +++ b/packages/ludic.render3d/gltf.ludic @@ -51,6 +51,7 @@ function gltf_mat_remember(render3d_st: mut Render3dState, name: string, diff: i render3d_st.gltf_mat_n += 1 } +@alloc_ok("loading a model, a texture or a font: a load, not a frame (a guest loading a teammate's look is one)") function gltf_texture(render3d_st: mut Render3dState, uri: string, srgb: bool) -> int { 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 @@ -174,6 +175,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)") 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_cached.ludic b/packages/ludic.render3d/gltf_cached.ludic index eb5fcf7f..740e901a 100644 --- a/packages/ludic.render3d/gltf_cached.ludic +++ b/packages/ludic.render3d/gltf_cached.ludic @@ -4,15 +4,22 @@ # read after the load (ludic.anim's clips) still goes through gltf_load. A model loaded here is shared # 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 { + for i in 0 .. len(render3d_st.gltf_kept) { + if render3d_st.gltf_kept_node[i] == node_name and render3d_st.gltf_kept_file[i] == file and render3d_st.gltf_kept_dir[i] == dir { return render3d_st.gltf_kept[i] } + } + } + return gltf_cached_load(render3d_st, dir, file, node_name) +} +# the first ask for a model: loaded, and kept for every ask after it +@alloc_ok("a model loaded the first time it is asked for, kept for every ask after it") +function gltf_cached_load(render3d_st: mut Render3dState, dir: string, file: string, node_name: string) -> Model { if render3d_st.gltf_kept == null { render3d_st.gltf_kept = new []Model render3d_st.gltf_kept_dir = new []string render3d_st.gltf_kept_file = new []string render3d_st.gltf_kept_node = new []string } - for i in 0 .. len(render3d_st.gltf_kept) { - if render3d_st.gltf_kept_node[i] == node_name and render3d_st.gltf_kept_file[i] == file and render3d_st.gltf_kept_dir[i] == dir { return render3d_st.gltf_kept[i] } - } let m = gltf_load(render3d_st, dir, file, node_name) push(render3d_st.gltf_kept, m) push(render3d_st.gltf_kept_dir, dir) diff --git a/packages/ludic.render3d/gpu.ludic b/packages/ludic.render3d/gpu.ludic index 55123123..6930e378 100644 --- a/packages/ludic.render3d/gpu.ludic +++ b/packages/ludic.render3d/gpu.ludic @@ -357,6 +357,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)") function gpu_mesh_new(render3d_st: Render3dState) -> Mesh { let m = new Mesh m.attrs = words(GPU_MAX_ATTRS * GPU_ATTR_W) diff --git a/packages/ludic.render3d/gpu_vk.ludic b/packages/ludic.render3d/gpu_vk.ludic index f6b19194..c338d05e 100644 --- a/packages/ludic.render3d/gpu_vk.ludic +++ b/packages/ludic.render3d/gpu_vk.ludic @@ -336,6 +336,7 @@ const GVK_BLOCK_DEVICE: int = 67108864 const GVK_BLOCK_HOST: int = 67108864 const GVK_OWN_OVER: int = 16777216 +@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)") function gvk_mem_init(render3d_st: mut Render3dState) -> void { if render3d_st.gvk_al_blk != null { return } let zero: long = 0 @@ -377,6 +378,7 @@ function gvk_fr_put(render3d_st: mut Render3dState, b: int, off: int, len_: int) } # Memory for a resource from its requirements (a VkMemoryRequirements): an allocation id, 0 if none. +@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)") function gvk_mem_new(render3d_st: mut Render3dState, req: bytes, want: int, image: bool) -> int { gvk_mem_init(render3d_st) let allowed = Vk.get_i32(req, VkMemoryRequirements_memoryTypeBits) diff --git a/packages/ludic.render3d/gpu_vk_draw.ludic b/packages/ludic.render3d/gpu_vk_draw.ludic index f9451d52..7bfa552d 100644 --- a/packages/ludic.render3d/gpu_vk_draw.ludic +++ b/packages/ludic.render3d/gpu_vk_draw.ludic @@ -25,6 +25,7 @@ function gvk_read_spv(render3d_st: mut Render3dState, path: string) -> bytes { return b } +@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)") function gvk_module(render3d_st: mut Render3dState, path: string) -> long { let zero: long = 0 let spv = gvk_read_spv(render3d_st, path) @@ -49,6 +50,7 @@ function gvk_stage_first(render3d_st: Render3dState, p: int) -> int { if render3d_st.gvk_prog_mesh != null and p < len(render3d_st.gvk_prog_mesh) and render3d_st.gvk_prog_mesh[p] == 1 { return VK_SHADER_STAGE_MESH_BIT_EXT } return VK_SHADER_STAGE_VERTEX_BIT } +@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)") function gvk_program(render3d_st: mut Render3dState, p: int, key: string, spv_dir: string) -> bool { let zero: long = 0 if render3d_st.gvk_prog_var == null { @@ -125,6 +127,7 @@ function gvk_program(render3d_st: mut Render3dState, p: int, key: string, spv_di function gvk_compute_new(render3d_st: mut Render3dState, name: string, n_bufs: int) -> int { return gvk_compute_new_tex(render3d_st, name, n_bufs, 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)") function gvk_compute_new_tex(render3d_st: mut Render3dState, name: string, n_bufs: int, n_tex: int) -> int { let zero: long = 0 if render3d_st.gvk_cp_pipe == null { @@ -592,6 +595,7 @@ function gvk_same(a: pointer, b: pointer, n: int) -> bool { return true } +@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)") function gvk_uniform_blocks(render3d_st: mut Render3dState, p: int) -> void { if render3d_st.gvk_ublk_v == null { render3d_st.gvk_ublk_v = new []bytes; render3d_st.gvk_ublk_f = new []bytes; render3d_st.gvk_prog_tex = new []words; render3d_st.gvk_prog_unit = new []words @@ -861,6 +865,7 @@ function gvk_draw_set(render3d_st: mut Render3dState, p: int, tx: words, tx_w: i } # a new kept set for program p with the textures in gvk_sc_tmp, written and cached +@alloc_ok("the window changed size or a surface was lost: the screen, the swapchain and its images are made again") function gvk_sc_make(render3d_st: mut Render3dState, p: int, nt: int) -> long { let zero: long = 0 let v = render3d_st.gvk_prog_var[p] @@ -995,6 +1000,7 @@ function gvk_hdr_metadata(render3d_st: Render3dState) -> void { Vk.set_hdr_metadata_ext(render3d_st.gvk_dev, 1, chains, md) } +@alloc_ok("the window changed size or a surface was lost: the screen, the swapchain and its images are made again") function gvk_screen_make(render3d_st: mut Render3dState, w: int, h: int) -> bool { if render3d_st.gvk_vp == null { render3d_st.gvk_vp = words(4); render3d_st.gvk_sc = words(5); render3d_st.gvk_clear_rgba = floats(4) @@ -1024,6 +1030,7 @@ function gvk_frame_cb(render3d_st: mut Render3dState) -> pointer { # The view a pass draws into: level 0 only (an attachment view has exactly one level, and a target # the exposure measure mipmaps has several), and one layer of an array image for a cascade drawn # on its own. Keyed by the image's generation, so a replaced image never reuses a stale view. +@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)") function gvk_view_of(render3d_st: mut Render3dState, tex: int, layer1: int) -> long { if layer1 == 0 and render3d_st.gvk_tex_levels[tex] <= 1 and render3d_st.gvk_tex_layers[tex] <= 1 { return render3d_st.gvk_tex_view[tex] } # keyed by numbers, not a string built on every call: the texture, its generation and the layer @@ -1383,6 +1390,7 @@ function gvk_present(render3d_st: mut Render3dState) -> void { # The screen as a binary PPM, top row first. The frame so far is finished first, then read back; # row 0 of the image is OpenGL's bottom row, so rows are written last to first, as gl_screenshot does. +@alloc_ok("asked for by the player or a tool, not by the frame") function gvk_screenshot(render3d_st: mut Render3dState, path: string) -> bool { # the screen image is PQ-encoded 10-bit while HDR is on, which an 8-bit PPM would misread if render3d_st.gvk_hdr_on { print("r3d: vulkan: screenshots are SDR only - turn HDR output off to take one"); return false } @@ -1441,6 +1449,7 @@ function gvk_open(render3d_st: mut Render3dState, w: int, h: int, title: string) # A variant is made once and shared: a program is immutable here, so every actor asking for the # one it wants (actor.ludic, one per actor) gets the same handle, not its own modules and pipelines. +@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)") function gvk_program_new(render3d_st: mut Render3dState, vs: string, fs: string, defines: string) -> int { let key = `{vs}|{fs}|{Text.replace(defines, "\n", ";")}` if render3d_st.gpu_prog_ids == null { render3d_st.gpu_prog_ids = new []int; render3d_st.gpu_prog_keys = new []string } @@ -1655,6 +1664,7 @@ function gvk_surface_os(render3d_st: mut Render3dState, out: pointer) -> bool { return true } +@alloc_ok("the window changed size or a surface was lost: the screen, the swapchain and its images are made again") function gvk_surface_make(render3d_st: mut Render3dState) -> bool { if render3d_st.gvk_surface != 0 { return true } let out = bytes(8) @@ -1672,6 +1682,7 @@ function gvk_surface_make(render3d_st: mut Render3dState) -> bool { } # (Re)make the swapchain for a w x h client area. The old one is handed over and then destroyed. +@alloc_ok("the window changed size or a surface was lost: the screen, the swapchain and its images are made again") function gvk_swap_make(render3d_st: mut Render3dState, w: int, h: int) -> bool { if not gvk_surface_make(render3d_st) { return false } gvk_frame_wait(render3d_st) @@ -1854,6 +1865,7 @@ function gvk_prof(render3d_st: mut Render3dState) -> bool { if render3d_st.gvk_prof_state < 0 { render3d_st.gvk_prof_state = 0; if r3d_env_has(render3d_st, "R3D_VK_PROF") { render3d_st.gvk_prof_state = 1 } } return render3d_st.gvk_prof_state == 1 } +@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS") function gvk_prof_frame(render3d_st: mut Render3dState) -> void { if not gvk_prof(render3d_st) { return } render3d_st.gvk_prof_frames += 1 @@ -1876,6 +1888,7 @@ function gvk_prof_frame(render3d_st: mut Render3dState) -> void { # what was made and destroyed over those frames, and how long the caches are: a kind made every # frame and never destroyed, or a cache that only grows, is a leak +@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS") function gvk_prof_made(render3d_st: mut Render3dState) -> void { let r = render3d_st print(`r3d: vulkan made/destroyed: images {r.gvk_mk_img}/{r.gvk_mk_x_img}, views {r.gvk_mk_view}/{r.gvk_mk_x_view}, buffers {r.gvk_mk_buf}/{r.gvk_mk_x_buf}, memory {r.gvk_mk_mem}/{r.gvk_mk_x_mem}, samplers {r.gvk_mk_smp}, sets {r.gvk_mk_set}, pools {r.gvk_mk_dpool}, cmds {r.gvk_mk_cmd}`) @@ -1894,6 +1907,7 @@ function gvk_len_l(xs: []long) -> int { if xs == null { return 0 }; return len( # state packs into one int, and the pass's formats into another; the program's last hit is tried # first, then the entries it owns. +@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)") function gvk_layout_id(render3d_st: mut Render3dState, m: Mesh) -> int { if m == null or m.attrs == null { return 1 } if m.vk_layout > 0 { return m.vk_layout } @@ -1975,6 +1989,7 @@ function gvk_pipeline_fast(render3d_st: mut Render3dState, p: int, m: Mesh, st: } # The one place the pipeline cache grows: once per variant the game draws (program, vertex layout, # render state, formats), kept for the program's life - the fence's declared warm-up (plan 25.2) +@alloc_ok("pipeline cache: one per variant the game draws") function gvk_pipe_build(render3d_st: mut Render3dState, p: int, m: Mesh, st: GvkState, n_color: int, color_fmt: int, depth_fmt: int, samples: int, layout: int, state: int, bias: int, pass: int) -> long { let pipe = gvk_pipeline(render3d_st, p, m, st, n_color, color_fmt, depth_fmt, samples) push(render3d_st.gvk_pc_prog, p); push(render3d_st.gvk_pc_layout, layout); push(render3d_st.gvk_pc_state, state) diff --git a/packages/ludic.render3d/gpu_vk_res.ludic b/packages/ludic.render3d/gpu_vk_res.ludic index 317b43b3..b6e89595 100644 --- a/packages/ludic.render3d/gpu_vk_res.ludic +++ b/packages/ludic.render3d/gpu_vk_res.ludic @@ -65,6 +65,7 @@ function gvk_tex_give_back(render3d_st: mut Render3dState, tex: int) -> void { if render3d_st.gvk_tex_spare == null { render3d_st.gvk_tex_spare = new []int } push(render3d_st.gvk_tex_spare, tex) } +@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)") function gvk_tex_new(render3d_st: mut Render3dState) -> int { let zero: long = 0 if render3d_st.gvk_tex_image == null { @@ -176,6 +177,7 @@ function gvk_staging_free(render3d_st: mut Render3dState) -> void { } # (Re)make the image behind a handle: glTexImage2D on a texture that already has one replaces it. +@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)") function gvk_tex_storage(render3d_st: mut Render3dState, tex: int, array: bool, ifmt: int, w: int, h: int, layers: int, with_mips: bool) -> bool { gvk_frame_wait(render3d_st) # the frame in flight may still read it if tex <= 0 or tex >= len(render3d_st.gvk_tex_image) { return false } @@ -571,6 +573,7 @@ function gvk_tex_sampler(render3d_st: mut Render3dState, tex: int, min_f: int, m if tex > 0 and tex < len(render3d_st.gvk_tex_smp_sig) { render3d_st.gvk_tex_smp_sig[tex] = sig; render3d_st.gvk_tex_smp[tex] = s } return s } +@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)") function gvk_sampler(render3d_st: mut Render3dState, min_f: int, mag_f: int, wrap_s: int, wrap_t: int, compare: int, aniso: int) -> long { let bias = float_bits(gvk_mip_bias(render3d_st)) # looked up by the numbers themselves: a key string built on every call (and a texture read two @@ -631,6 +634,7 @@ function gvk_sampler(render3d_st: mut Render3dState, min_f: int, mag_f: int, wra # Storage a buffer was moved off, or freed, while the frame that used it has not been submitted: # destroyed at gvk_retire_flush, after the frame's work is done. +@alloc_ok("loading a model, a texture or a font: a load, not a frame (a guest loading a teammate's look is one)") function gvk_buf_new(render3d_st: mut Render3dState) -> int { let zero: long = 0 if render3d_st.gvk_buf == null { diff --git a/packages/ludic.render3d/overlay.ludic b/packages/ludic.render3d/overlay.ludic index 9625c8ab..1841f531 100644 --- a/packages/ludic.render3d/overlay.ludic +++ b/packages/ludic.render3d/overlay.ludic @@ -69,6 +69,7 @@ function overlay_init(render3d_st: mut Render3dState, font_dir: string) -> bool # Load (or swap to) the font atlas in `font_dir`: font.png and font.json. A game calls it # again to change fonts at run time - a language whose script the default atlas does not # carry brings its own. Returns false, and keeps the font it had, if there is none there. +@alloc_ok("loading a model, a texture or a font: a load, not a frame (a guest loading a teammate's look is one)") function overlay_font(render3d_st: mut Render3dState, font_dir: string) -> bool { let meta = Fs.read_text(font_dir + "/font.json") if meta == null { return false } diff --git a/packages/ludic.render3d/post.ludic b/packages/ludic.render3d/post.ludic index 20ca2711..059347fd 100644 --- a/packages/ludic.render3d/post.ludic +++ b/packages/ludic.render3d/post.ludic @@ -54,6 +54,7 @@ function post_set_msaa(render3d_st: mut Render3dState, n: int) -> void { } } +@alloc_ok("a world being set up (a stream, a layer, water, post, a bake): the map loading or swapping, not a frame") function post_init(render3d_st: mut Render3dState, w: int, h: int) -> void { render3d_st.post_w = w; render3d_st.post_h = h render3d_st.post_hdr = target_new(render3d_st, w, h, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, true, GL_LINEAR) diff --git a/packages/ludic.render3d/prof.ludic b/packages/ludic.render3d/prof.ludic index 6fe9fc53..d151bfcf 100644 --- a/packages/ludic.render3d/prof.ludic +++ b/packages/ludic.render3d/prof.ludic @@ -30,6 +30,7 @@ function prof_init(render3d_st: mut Render3dState) -> void { } # the slot index for `name`, registering it on first sight (order = frame order) +@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS") function prof_slot(render3d_st: mut Render3dState, name: pointer) -> int { var i = 0 while i < render3d_st.prof_n { @@ -82,6 +83,7 @@ function prof_collect(render3d_st: mut Render3dState) -> void { } } +@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS") function prof_report(render3d_st: Render3dState) -> void { if not render3d_st.prof_on { return } print("") @@ -130,6 +132,7 @@ function prof_before_swap(render3d_st: mut Render3dState) -> void { if render3d_ # name it without keeping a timeline. function prof_mark_start(render3d_st: mut Render3dState) -> void { if render3d_st.prof_on { render3d_st.prof_mark_last = gl_now_us(); render3d_st.prof_mark_best = 0; render3d_st.prof_mark_name = null } } # ... and every phase's share of the average frame, past the first eight frames (loading, first fills) +@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS") function prof_cpu_mark(render3d_st: mut Render3dState, name: pointer) -> void { if not render3d_st.prof_on { return } let now = gl_now_us() @@ -158,6 +161,7 @@ function prof_layer_add(render3d_st: mut Render3dState, us: long, bytes: long) - } function prof_bake_add(render3d_st: mut Render3dState, us: long) -> void { if render3d_st.prof_on { render3d_st.prof_bake_us_cur = render3d_st.prof_bake_us_cur + us } } +@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS") function prof_chunk(render3d_st: mut Render3dState, kind: int, band: int, count: int, us: long) -> void { if not render3d_st.prof_on { return } if render3d_st.prof_chunk_us == null { @@ -169,6 +173,7 @@ function prof_chunk(render3d_st: mut Render3dState, kind: int, band: int, count: render3d_st.prof_gen_us_cur += us } +@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS") function prof_chunk_report(render3d_st: Render3dState) -> void { if not render3d_st.prof_on or render3d_st.prof_chunk_us == null { return } print("") @@ -220,6 +225,7 @@ function prof_chunk_report(render3d_st: Render3dState) -> void { print(` generation per frame (us): p95 {string(sorted[(n * 95) / 100])} p99 {string(sorted[(n * 99) / 100])} worst {string(sorted[n - 1])} frames that generated: {string(busy)} of {string(n)} total {string(t / 1000)} ms`) } +@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS") function prof_gen_frame(render3d_st: mut Render3dState) -> void { ds_frame(render3d_st) if not render3d_st.prof_on { return } @@ -256,6 +262,7 @@ function prof_gen_frame(render3d_st: mut Render3dState) -> void { } # the distribution of REAL frame times: stutter lives in the tail, not the mean +@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS") function prof_ft_report(render3d_st: Render3dState) -> void { if not render3d_st.prof_on { return } if render3d_st.prof_ft == null or len(render3d_st.prof_ft) < 16 { return } @@ -320,6 +327,7 @@ function prof_ft_report(render3d_st: Render3dState) -> void { # The slowest frames of the run, with the once-in-a-while CPU work that landed in them. # An average never shows a hitch; this is the list of the frames you actually felt. +@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS") function prof_hitch_report(render3d_st: Render3dState) -> void { if not render3d_st.prof_on or render3d_st.prof_dt == null or len(render3d_st.prof_dt) < 32 { return } let n = len(render3d_st.prof_dt) @@ -357,6 +365,7 @@ function prof_hitch_report(render3d_st: Render3dState) -> void { } } +@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS") function prof_gen_report(render3d_st: Render3dState) -> void { if not render3d_st.prof_on { return } if render3d_st.prof_gen == null or len(render3d_st.prof_gen) < 8 { return } diff --git a/packages/ludic.render3d/programs.ludic b/packages/ludic.render3d/programs.ludic index 4e552680..be4bdda3 100644 --- a/packages/ludic.render3d/programs.ludic +++ b/packages/ludic.render3d/programs.ludic @@ -37,6 +37,7 @@ function r3d_find_root(render3d_st: mut Render3dState) -> void { # Build a program from a vertex + fragment file pair (defines apply to both). # R3D_PROGRAMS_LOG=: append every program built (vertex, fragment, defines) - the list a # backend that cannot compile shaders at run time (Vulkan: SPIR-V is built ahead) must cover +@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS") function r3d_program_log(render3d_st: mut Render3dState, vs: string, fs: string, defines: string) -> void { if render3d_st.r3d_prog_log < 0 { render3d_st.r3d_prog_log = 0; if r3d_env_has(render3d_st, "R3D_PROGRAMS_LOG") { render3d_st.r3d_prog_log = 1 } } if render3d_st.r3d_prog_log == 0 { return } @@ -48,6 +49,7 @@ function r3d_program_log(render3d_st: mut Render3dState, vs: string, fs: string, file_write(f, line, len(line)) file_close(f) } +@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)") function r3d_program(render3d_st: mut Render3dState, vs: string, fs: string, defines: string) -> int { r3d_program_log(render3d_st, vs, fs, defines) let p = gpu_program(render3d_st, vs, fs, `{render3d_st.r3d_global_defs}{defines}`) @@ -67,6 +69,7 @@ property Target { w: int = 0, 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)") 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 diff --git a/packages/ludic.render3d/scatter.ludic b/packages/ludic.render3d/scatter.ludic index 1a3aefdc..2a63b5c0 100644 --- a/packages/ludic.render3d/scatter.ludic +++ b/packages/ludic.render3d/scatter.ludic @@ -94,6 +94,7 @@ function sc_model_layout(render3d_st: Render3dState, m: Mesh) -> void { # Two crossed unit quads (x in [-0.5, 0.5], y in [0, 1]), attribute 0 = pos, # 1 = the quad's facing normal, 2 = uv. Scaled per layer to the atlas card size. +@alloc_ok("a world being set up (a stream, a layer, water, post, a bake): the map loading or swapping, not a frame") function model_cross_card(render3d_st: mut Render3dState) -> Model { let model = new Model model.prims = new []Prim @@ -378,6 +379,7 @@ function scatter_attach(render3d_st: Render3dState, m: Mesh, buf: int) -> void { gpu_mesh_done(render3d_st, m) } +@alloc_ok("a world being set up (a stream, a layer, water, post, a bake): the map loading or swapping, not a frame") function layer_new(render3d_st: mut Render3dState, model: Model, cap: int, foliage: bool, wind: float, near: float, cull: float) -> Layer { let l = new Layer l.model = model @@ -428,6 +430,7 @@ function layer_add(l: Layer, x: float, y: float, z: float, scale: float, yaw: fl } # ---- impostors --------------------------------------------------------------------- +@alloc_ok("a world being set up (a stream, a layer, water, post, a bake): the map loading or swapping, not a frame") function impostor_bake(render3d_st: mut Render3dState, model: Model, tiles: int, tw: int, th: int) -> Impostor { let im = new Impostor im.tiles = tiles @@ -583,6 +586,7 @@ function layer_arena_ok(render3d_st: Render3dState, l: Layer) -> bool { return true } +@alloc_ok("a world being set up (a stream, a layer, water, post, a bake): the map loading or swapping, not a frame") function layer_arena_build(render3d_st: mut Render3dState, l: Layer) -> void { let n_mat = len(l.lods[0].prims) l.g_arena = new []Prim @@ -706,6 +710,7 @@ function layer_gpu_cull(render3d_st: mut Render3dState, l: Layer) -> void { # Sort a static layer's instances into square cells (call once, after placement; a # large layer that was never gridded gets a 96 m grid on its first update). The # shadow buffer is uploaded here once — casters are never culled by the view. +@alloc_ok("a world being set up (a stream, a layer, water, post, a bake): the map loading or swapping, not a frame") function layer_grid_build(render3d_st: mut Render3dState, l: Layer, cell: float) -> void { if l.count == 0 { return } var minx = l.inst[0]; var maxx = minx; var minz = l.inst[2]; var maxz = minz diff --git a/packages/ludic.render3d/shadow.ludic b/packages/ludic.render3d/shadow.ludic index 307789f7..96ed642d 100644 --- a/packages/ludic.render3d/shadow.ludic +++ b/packages/ludic.render3d/shadow.ludic @@ -185,6 +185,7 @@ function shadow_pass(render3d_st: mut Render3dState) -> void { } # Debug: cascade depths as grey PPMs (build/dbg_shadow_.ppm) +@alloc_ok("asked for by the player or a tool, not by the frame") function shadow_dump(render3d_st: mut Render3dState) -> void { let n = render3d_st.shadow_res * render3d_st.shadow_res let buf = floats(n * SHADOW_CASCADES) diff --git a/packages/ludic.render3d/skin.ludic b/packages/ludic.render3d/skin.ludic index bbfb3aa1..1752d451 100644 --- a/packages/ludic.render3d/skin.ludic +++ b/packages/ludic.render3d/skin.ludic @@ -72,6 +72,7 @@ function skin_attribs(render3d_st: mut Render3dState, m: Mesh, attrs: Val) -> bo } # the skin `idx` of the document being loaded (gltf_load holds gltf_doc / gltf_bin open) +@alloc_ok("loading a model, a texture or a font: a load, not a frame (a guest loading a teammate's look is one)") function skin_load(render3d_st: mut Render3dState, idx: int) -> Skin { let sk = new Skin let nodes = value_get(render3d_st.gltf_doc, "nodes") diff --git a/packages/ludic.render3d/stream.ludic b/packages/ludic.render3d/stream.ludic index b006ea59..9dc573ce 100644 --- a/packages/ludic.render3d/stream.ludic +++ b/packages/ludic.render3d/stream.ludic @@ -44,6 +44,7 @@ property Stream { # microseconds spent per frame, split so the hitch can be attributed (R3D_PROF=1) +@alloc_ok("a world being set up (a stream, a layer, water, post, a bake): the map loading or swapping, not a frame") function stream_new(render3d_st: mut Render3dState, layer: Layer, size: float, reach: float, b0: float, b1: float, b2: float, b3: float) -> Stream { if not render3d_st.stream_cap_read { render3d_st.stream_cap_read = true @@ -315,6 +316,7 @@ function stream_chunk_visible(render3d_st: Render3dState, s: Stream, cx: int, cz } # what the caches hold, and whether they are being churned (R3D_PROF) +@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS") function stream_census(render3d_st: Render3dState) -> void { if render3d_st.stream_all == null { return } print("") diff --git a/packages/ludic.render3d/texture.ludic b/packages/ludic.render3d/texture.ludic index f0659c5e..ade9b9a3 100644 --- a/packages/ludic.render3d/texture.ludic +++ b/packages/ludic.render3d/texture.ludic @@ -112,6 +112,7 @@ function png_unfilter(raw: pointer, cur: int, prev: int, stride: int, fbpp: int, } } +@alloc_ok("loading a model, a texture or a font: a load, not a frame (a guest loading a teammate's look is one)") function png_decode(render3d_st: mut Render3dState, path: pointer) -> pointer { let d = r3d_read_file(render3d_st, path) if d == null { print(`png: cannot read {path}`); return null } @@ -260,8 +261,10 @@ function tex_upload(render3d_st: mut Render3dState, px: pointer, srgb: bool, mip } # Load a PNG as a mipmapped, anisotropic texture (0 on failure). srgb for albedo. +@alloc_ok("loading a model, a texture or a font: a load, not a frame (a guest loading a teammate's look is one)") function tex_load(render3d_st: mut Render3dState, path: pointer, srgb: bool) -> int { return tex_load_ex(render3d_st, path, srgb, 0) } # ... with `dilate` passes of edge padding for a cut-out atlas (0 = none) +@alloc_ok("loading a model, a texture or a font: a load, not a frame (a guest loading a teammate's look is one)") function tex_load_ex(render3d_st: mut Render3dState, path: pointer, srgb: bool, dilate: int) -> int { # a .dds encoded beside the .png at build time goes to the GPU compressed, its mips included # (not for a cut-out atlas: its edge padding is made here, from the .png) @@ -306,6 +309,7 @@ function tex_height(render3d_st: Render3dState, id: int) -> int { } # A small solid-colour fallback texture (linear rgb 0..255), for missing maps. +@alloc_ok("loading a model, a texture or a font: a load, not a frame (a guest loading a teammate's look is one)") function tex_solid(render3d_st: mut Render3dState, r: int, g: int, b: int, a: int) -> int { let px = bytes(16) for i in 0 .. 4 { px[i * 4] = r; px[i * 4 + 1] = g; px[i * 4 + 2] = b; px[i * 4 + 3] = a } @@ -453,6 +457,7 @@ function tex_max(render3d_st: mut Render3dState, tex: int, w: int, h: int, tag: free(buf) } # Debug: write a 2D texture's level 0 (RGBA8, alpha dropped) as a binary PPM. +@alloc_ok("asked for by the player or a tool, not by the frame") function tex_dump(render3d_st: mut Render3dState, tex: int, w: int, h: int, path: pointer) -> void { let f = file_open(path, "wb") if f == null { return } diff --git a/packages/ludic.render3d/texture_dds.ludic b/packages/ludic.render3d/texture_dds.ludic index dfdd6376..71b90e06 100644 --- a/packages/ludic.render3d/texture_dds.ludic +++ b/packages/ludic.render3d/texture_dds.ludic @@ -18,6 +18,7 @@ function dds_path_of(path: string) -> string { } # Load the .dds at `path` as a texture (srgb: colour, sampled as sRGB), or 0 +@alloc_ok("loading a model, a texture or a font: a load, not a frame (a guest loading a teammate's look is one)") function tex_load_dds(render3d_st: mut Render3dState, path: string, srgb: bool) -> int { let b = Fs.read_bytes(path) if b == null { return 0 } diff --git a/packages/ludic.render3d/water.ludic b/packages/ludic.render3d/water.ludic index 8d86701d..b3fb0cee 100644 --- a/packages/ludic.render3d/water.ludic +++ b/packages/ludic.render3d/water.ludic @@ -196,6 +196,7 @@ function wb_wet(render3d_st: mut Render3dState, x: float, z: float, off: float) # walk of every rectangle, every frame. The body beyond the map (a sea runs far past it) is 64 x 64 # coarse rectangles tested at their centre, in one last block, which only ever matter kilometres away. const WB_BLOCK: int = 8 +@alloc_ok("a world being set up (a stream, a layer, water, post, a bake): the map loading or swapping, not a frame") function water_cells_build(render3d_st: mut Render3dState) -> void { let bx0 = render3d_st.water_cx - render3d_st.water_ex; let bx1 = render3d_st.water_cx + render3d_st.water_ex let bz0 = render3d_st.water_cz - render3d_st.water_ez; let bz1 = render3d_st.water_cz + render3d_st.water_ez @@ -256,6 +257,7 @@ function water_cells_build(render3d_st: mut Render3dState) -> void { } # the program and the plane are the process's, built once; the bodies are the map's +@alloc_ok("a world being set up (a stream, a layer, water, post, a bake): the map loading or swapping, not a frame") function water_setup(render3d_st: mut Render3dState) -> void { if render3d_st.water_prog != 0 { return } render3d_st.water_mesh = mesh_grid(render3d_st, 2, 0.5)