render3d: a command buffer holds at most 512 draws, and that many are drawn at start-up

Metal pools the command storage a command buffer grew to and grows it only when one encodes more than
any before (IOGPU under vkQueueSubmit < fn_gvk_once_end: +2-7 KB a window while a frame's draws
climbed, 192 KB in the trail scene). gvk_draw submits the frame's command buffer after
gvk_cb_cap_of draws (R3D_CB_DRAWS, 0 = none) and r3d_warm_commands draws that many through the
normal path once r3d is ready. steady: a frame drawing 20 to 200 actors grows 0 (was 2.7-7.5 KB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 13:59:00 +03:00
parent 404ee2ee3b
commit 22c104f766
4 changed files with 47 additions and 0 deletions

View file

@ -448,3 +448,31 @@ function actor_release(render3d_st: mut Render3dState, a: Actor) -> void {
if render3d_st.ac_spare == null { render3d_st.ac_spare = new []Actor }
push(render3d_st.ac_spare, a)
}
# One command buffer's worth of draws at start-up, through the path every draw takes (pipeline, set,
# buffers, draw), so Metal's pooled command storage is already the size a frame can need: a bigger
# frame than any before once grew it in play (IOGPU, under vkQueueSubmit). Drawn into the screen
# image before the first frame clears it.
function r3d_warm_commands(render3d_st: mut Render3dState) -> void {
let n = gvk_cb_cap_of(render3d_st)
if Os.platform() != "macos" or n <= 0 or render3d_st.ac_lit == null { return }
let m = gpu_mesh_new(render3d_st)
let v = floats(24)
for k in 0 .. 3 { v[k * 8 + k] = 0.01; v[k * 8 + 4] = 1.0 }
gpu_mesh_vertices(render3d_st, m, data_of(v), 96, GPU_STATIC)
gpu_mesh_attr(render3d_st, m, 0, 3, GPU_F32, 32, 0, false)
gpu_mesh_attr(render3d_st, m, 1, 3, GPU_F32, 32, 12, false)
gpu_mesh_attr(render3d_st, m, 2, 2, GPU_F32, 32, 24, false)
let ix = words(3)
ix[0] = 0; ix[1] = 1; ix[2] = 2
gpu_mesh_indices(render3d_st, m, data_of(ix), 12, 4)
m.count = 3
gpu_mesh_done(render3d_st, m)
gpu_fb_bind(render3d_st, 0)
gpu_use_program(render3d_st, render3d_st.ac_lit.prog)
for i in 0 .. n { gpu_draw_mesh(render3d_st, m) }
gvk_flush(render3d_st)
mesh_release(render3d_st, m)
free(v)
free(ix)
}

View file

@ -339,6 +339,8 @@ export state Render3dState {
gvk_us_set: long = 0
gvk_us_draw: long = 0
gvk_n_draws: int = 0
gvk_cb_draws: int = 0 # draws recorded into the frame's command buffer so far
gvk_cb_cap: int = -1 # a command buffer is submitted after this many (0 = never; -1 unread)
gvk_n_asked: int = 0 # draws that reached gvk_draw_now: every one the renderer asked for
gvk_n_flush: int = 0
gvk_prof_frames: int = 0

View file

@ -1248,6 +1248,10 @@ function gvk_set_view(render3d_st: mut Render3dState, cb: pointer) -> void {
# first / count select vertices or indices; count 0 means the mesh's own count. instances >= 1.
function gvk_draw(render3d_st: mut Render3dState, p: int, m: Mesh, st: GvkState, first: int, count: int, instances: int, tx: words, tx_w: int, tx_cap: int, rec: words, rec_o: int) -> void {
if render3d_st.gvk_prog_var == null or p <= 0 or p >= len(render3d_st.gvk_prog_var) or render3d_st.gvk_prog_var[p] == null { return }
let cap = gvk_cb_cap_of(render3d_st)
if cap > 0 and render3d_st.gvk_cb_draws >= cap { gvk_flush(render3d_st) }
if render3d_st.gvk_cb == null { render3d_st.gvk_cb_draws = 0 }
render3d_st.gvk_cb_draws += 1
let prof = gvk_prof(render3d_st)
var t0: long = 0
if prof { t0 = gl_now_us() }
@ -1974,3 +1978,15 @@ function gvk_pipeline_fast(render3d_st: mut Render3dState, p: int, m: Mesh, st:
if p < 4096 { render3d_st.gvk_pc_last[p] = len(render3d_st.gvk_pc_prog) - 1 }
return pipe
}
# Draws a command buffer holds before it is submitted and the frame goes on in the next. Metal pools
# the storage a command buffer grew to and grows it only when one encodes more than any before, so
# with a bound and that many drawn at start-up (r3d_warm_commands) play never grows it. 0: no bound.
const GVK_CB_DRAWS: int = 512
function gvk_cb_cap_of(render3d_st: mut Render3dState) -> int {
if render3d_st.gvk_cb_cap < 0 {
render3d_st.gvk_cb_cap = GVK_CB_DRAWS
if r3d_env_has(render3d_st, "R3D_CB_DRAWS") { render3d_st.gvk_cb_cap = Text.to_int(r3d_env(render3d_st, "R3D_CB_DRAWS")) }
}
return render3d_st.gvk_cb_cap
}

View file

@ -127,6 +127,7 @@ function r3d_load_step(render3d_st: mut Render3dState, i: int) -> bool {
render3d_st.r3d_fog_density = 0.00024
render3d_st.r3d_fog_falloff = 0.002
render3d_st.r3d_ready = true
r3d_warm_commands(render3d_st)
gpu_check(render3d_st, "r3d init")
}
return true