Merge branch 'r3d/skin-own' into lang/foundations

This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 16:04:00 +03:00
commit 14140e26cd
6 changed files with 69 additions and 29 deletions

View file

@ -34,6 +34,7 @@ property Actor {
cutout: bool = false, # alpha-tested (a flame's cards)
emissive: float = 0.0, # float bits: self-lit strength
skin: Skin, # this instance's own pose (skin_clone); null: the model's
skin_owned: bool = false, # made by actor_own_skin: actor_release frees it
ptint: words, # 4 per primitive: on flag, r, g, b (a part's own colour)
hide: words, # 1 per primitive: skip it (a cap taken off)
radius: float = 0.0, # float bits: bounding radius for culling (0 = never culled)
@ -443,7 +444,7 @@ function actor_fresh(render3d_st: mut Render3dState) -> Actor {
m4_identity(a.mat)
a.visible = true; a.cast_hidden = false; a.casts = true; a.row = -1
a.cutout = false; a.emissive = 0.0; a.radius = 0.0; a.cull = 0.0; a.outline = 0.0
a.skin = null; a.ocol = null
a.skin = null; a.skin_owned = false; a.ocol = null
return a
}
@ -455,8 +456,21 @@ function actor_release(render3d_st: mut Render3dState, a: Actor) -> void {
actor_remove(render3d_st, a)
if a.ptint != null { free(a.ptint); a.ptint = null }
if a.hide != null { free(a.hide); a.hide = null }
if a.skin_owned and a.skin != null { skin_clone_free(a.skin) }
a.skin_owned = false
a.model = null; a.skin = null; a.ocol = null
a.id = 0
if render3d_st.ac_spare == null { render3d_st.ac_spare = new []Actor }
push(render3d_st.ac_spare, a)
}
# The actor poses on its own copy of its model's skeleton (a person, an animal), which it keeps until
# actor_release gives it back with the record: a clone made by the caller and dropped with the actor
# was one kept skeleton per figure ever placed.
function actor_own_skin(a: Actor) -> Skin {
if a == null or a.model == null or a.model.skin == null { return null }
if a.skin_owned and a.skin != null { skin_clone_free(a.skin) }
a.skin = skin_clone(a.model.skin)
a.skin_owned = true
return a.skin
}

View file

@ -279,6 +279,7 @@ function gvk_init(render3d_st: mut Render3dState) -> bool {
if not gvk_cmd_init(render3d_st) { return false }
render3d_st.gvk_ready = true
gvk_startup_state(render3d_st)
print(`r3d: vulkan on {render3d_st.gvk_device_name}, anisotropy up to {gvk_aniso_x(render3d_st.gvk_max_aniso)}x, multi-draw indirect {render3d_st.gvk_has_mdi}, indirect count {render3d_st.gvk_has_dic}, mesh shaders {render3d_st.gvk_has_mesh}`)
return true
}
@ -543,7 +544,6 @@ function gvk_cmd_init(render3d_st: mut Render3dState) -> bool {
const GVK_TMP_BYTES: int = 1048576
function gvk_tmp(render3d_st: mut Render3dState, n: int) -> pointer {
if render3d_st.gvk_tmp_buf == null { render3d_st.gvk_tmp_buf = bytes(GVK_TMP_BYTES) }
let sz = (n + 15) / 16 * 16
if sz > GVK_TMP_BYTES { print(`r3d: vulkan: {n} bytes of scratch asked for at once`); return null }
if render3d_st.gvk_tmp_off + sz > GVK_TMP_BYTES { render3d_st.gvk_tmp_off = 0 }
@ -726,3 +726,43 @@ function gvk_labels_close(render3d_st: mut Render3dState) -> void {
render3d_st.gvk_label_depth -= 1
}
}
# Everything a frame's draws use as scratch or as a table, made once the device is up: each was made
# on the first draw that needed it, which put an allocation in whichever frame that was (plan 25.2)
const GVK_PROGS_MAX: int = 4096
function gvk_startup_state(render3d_st: mut Render3dState) -> void {
let zero: long = 0
render3d_st.gvk_tmp_buf = bytes(GVK_TMP_BYTES)
render3d_st.gvk_skip_said = words(4096)
for i in 0 .. 4096 { render3d_st.gvk_skip_said[i] = 0 }
render3d_st.gvk_seen = words(GPU_MAX_VBUFS)
if render3d_st.gvk_state == null { render3d_st.gvk_state = new GvkState }
render3d_st.gvk_size_buf = words(4)
if render3d_st.gvk_tex_spare == null { render3d_st.gvk_tex_spare = new []int }
if render3d_st.gvk_buf_spare == null { render3d_st.gvk_buf_spare = new []int }
if render3d_st.gvk_retired_frame == null { render3d_st.gvk_retired_frame = new []int }
if render3d_st.gvk_buf_gpu == null { render3d_st.gvk_buf_gpu = new []int }
render3d_st.gvk_sc_tmp = new []long
let sct = render3d_st.gvk_sc_tmp
for i in 0 .. 130 { push(sct, zero) }
render3d_st.gvk_set_offs = bytes(16)
render3d_st.gvk_ub_frame = new []int; render3d_st.gvk_ub_offv = new []int; render3d_st.gvk_ub_offf = new []int
let ubf = render3d_st.gvk_ub_frame
let ubv = render3d_st.gvk_ub_offv
let ubo = render3d_st.gvk_ub_offf
for i in 0 .. GVK_PROGS_MAX {
push(ubf, 0); push(ubv, 0); push(ubo, 0)
}
render3d_st.gvk_pc_prog = new []int; render3d_st.gvk_pc_layout = new []int; render3d_st.gvk_pc_state = new []int; render3d_st.gvk_pc_bias = new []int
render3d_st.gvk_pc_pass = new []int; render3d_st.gvk_pc_pipe = new []long
render3d_st.gvk_pc_last = words(4096)
for i in 0 .. 4096 { render3d_st.gvk_pc_last[i] = -1 }
render3d_st.gg_rb = words(4); render3d_st.gg_cb = words(1); render3d_st.gg_bufs = words(3)
render3d_st.gg_texs = words(3); render3d_st.gg_pr = words(48)
if render3d_st.gpu_u_tmp == null { render3d_st.gpu_u_tmp = words(4) }
if render3d_st.gpu_unit_2d == null {
render3d_st.gpu_unit_2d = words(32)
for i in 0 .. 32 { render3d_st.gpu_unit_2d[i] = -1 }
}
if render3d_st.cam_planes == null { render3d_st.cam_planes = floats(16) }
}

View file

@ -799,11 +799,6 @@ function gvk_draw_set(render3d_st: mut Render3dState, p: int, tx: words, tx_w: i
let zero: long = 0
let v = render3d_st.gvk_prog_var[p]
gvk_uniform_blocks(render3d_st, p)
if render3d_st.gvk_sc_tmp == null {
render3d_st.gvk_sc_tmp = new []long
render3d_st.gvk_set_offs = bytes(16)
render3d_st.gvk_ub_frame = new []int; render3d_st.gvk_ub_offv = new []int; render3d_st.gvk_ub_offf = new []int
}
while len(render3d_st.gvk_ub_frame) <= p { push(render3d_st.gvk_ub_frame, 0); push(render3d_st.gvk_ub_offv, 0); push(render3d_st.gvk_ub_offf, 0) }
let nt = len(v.t_name)
# the key: every texture as this draw resolves it, and its sampler
@ -1268,7 +1263,6 @@ function gvk_draw(render3d_st: mut Render3dState, p: int, m: Mesh, st: GvkState,
if pipe == 0 {
# Say so, once per program: a driver that refuses a pipeline other drivers accept (MoltenVK
# rejected every skinned one) otherwise leaves a whole layer missing from the frame in silence.
if render3d_st.gvk_skip_said == null { render3d_st.gvk_skip_said = words(4096); for i in 0 .. 4096 { render3d_st.gvk_skip_said[i] = 0 } }
if p < 4096 and render3d_st.gvk_skip_said[p] == 0 {
render3d_st.gvk_skip_said[p] = 1
print(`r3d: vulkan: no pipeline for {gpu_program_key(render3d_st, p)}; its draws are skipped`)
@ -1291,7 +1285,6 @@ function gvk_draw(render3d_st: mut Render3dState, p: int, m: Mesh, st: GvkState,
let bufs = gvk_tmp(render3d_st, 8 * (GPU_MAX_VBUFS + 1))
let offs = gvk_tmp(render3d_st, 8 * (GPU_MAX_VBUFS + 1))
Vk.zero(offs, 8 * (GPU_MAX_VBUFS + 1))
if render3d_st.gvk_seen == null { render3d_st.gvk_seen = words(GPU_MAX_VBUFS) }
let seen = render3d_st.gvk_seen
var nbd = 0
for i in 0 .. m.n_attrs {
@ -1435,8 +1428,7 @@ function gvk_open(render3d_st: mut Render3dState, w: int, h: int, title: string)
win_gl_resize(w, h)
# macOS: the Metal layer sets the backing scale, so it exists before the drawable is measured
if Os.platform() == "macos" and win_metal_layer() == null { render3d_st.gvk_why = "no Metal layer on the window"; return false }
if render3d_st.gvk_size_buf == null { render3d_st.gvk_size_buf = words(4) }
win_gl_drawable(render3d_st.gvk_size_buf)
win_gl_drawable(render3d_st.gvk_size_buf)
if render3d_st.gvk_size_buf[0] > 0 and render3d_st.gvk_size_buf[1] > 0 { gl_set_drawable(render3d_st.gvk_size_buf[0], render3d_st.gvk_size_buf[1]) }
gl_set_pixel_scale(win_gl_scale())
}
@ -1508,7 +1500,6 @@ function gvk_fb_forget(render3d_st: mut Render3dState, fb: int) -> void {
# the render state gpu.ludic has cached, with OpenGL's defaults where nothing was set yet
function gvk_state_now(render3d_st: mut Render3dState) -> GvkState {
if render3d_st.gvk_state == null { render3d_st.gvk_state = new GvkState }
let st = render3d_st.gvk_state
st.depth_test = 0; if render3d_st.gpu_s_depth_test == 1 { st.depth_test = 1 }
st.depth_write = 1; if render3d_st.gpu_s_depth_write == 0 { st.depth_write = 0 }
@ -1833,7 +1824,6 @@ function gvk_present_window(render3d_st: mut Render3dState) -> void {
# a window's client area changed: the screen images and the swapchain follow it
function gvk_resize_check(render3d_st: mut Render3dState) -> bool {
if render3d_st.gvk_swap == 0 { return false }
if render3d_st.gvk_size_buf == null { render3d_st.gvk_size_buf = words(4) }
win_gl_drawable(render3d_st.gvk_size_buf)
let w = render3d_st.gvk_size_buf[0]
let h = render3d_st.gvk_size_buf[1]
@ -1967,12 +1957,6 @@ function gvk_pipeline_fast(render3d_st: mut Render3dState, p: int, m: Mesh, st:
state = state | ((st.depth_func & 15) << 8) | ((st.cull_face & 15) << 12) | (gvk_blend_index(st.blend_src) << 16) | (gvk_blend_index(st.blend_dst) << 20)
let bias = st.bias_factor * 31 + st.bias_units
let pass = ((n_color * 1000 + color_fmt) * 1000 + depth_fmt) * 64 + samples
if render3d_st.gvk_pc_prog == null {
render3d_st.gvk_pc_prog = new []int; render3d_st.gvk_pc_layout = new []int; render3d_st.gvk_pc_state = new []int; render3d_st.gvk_pc_bias = new []int
render3d_st.gvk_pc_pass = new []int; render3d_st.gvk_pc_pipe = new []long
render3d_st.gvk_pc_last = words(4096)
for i in 0 .. 4096 { render3d_st.gvk_pc_last[i] = -1 }
}
if p < 4096 {
let k = render3d_st.gvk_pc_last[p]
if k >= 0 and render3d_st.gvk_pc_layout[k] == layout and render3d_st.gvk_pc_state[k] == state and render3d_st.gvk_pc_pass[k] == pass and render3d_st.gvk_pc_bias[k] == bias { return render3d_st.gvk_pc_pipe[k] }

View file

@ -62,7 +62,6 @@ function gvk_channel_bytes(ifmt: int) -> int {
function gvk_tex_give_back(render3d_st: mut Render3dState, tex: int) -> void {
if tex <= 0 or tex >= len(render3d_st.gvk_tex_image) or render3d_st.gvk_tex_image[tex] == 0 { return }
gvk_tex_release(render3d_st, tex)
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)")
@ -660,7 +659,6 @@ function gvk_buf_delete(render3d_st: mut Render3dState, b: int) -> void {
if b <= 0 or b >= len(render3d_st.gvk_buf) { return }
gvk_buf_release(render3d_st, b)
if render3d_st.gvk_buf_gpu != null and b < len(render3d_st.gvk_buf_gpu) { render3d_st.gvk_buf_gpu[b] = 0 }
if render3d_st.gvk_buf_spare == null { render3d_st.gvk_buf_spare = new []int }
let sp = render3d_st.gvk_buf_spare
for i in 0 .. len(sp) { if sp[i] == b { return } }
push(sp, b)
@ -672,7 +670,6 @@ function gvk_buf_release(render3d_st: mut Render3dState, b: int) -> void {
if gvk_buf_busy(render3d_st, b) {
# a draw recorded this frame (or the frame in flight) still reads it: destroy it once the frame has been submitted
push(render3d_st.gvk_retired_buf, render3d_st.gvk_buf[b]); push(render3d_st.gvk_retired_mem, render3d_st.gvk_buf_mem[b])
if render3d_st.gvk_retired_frame == null { render3d_st.gvk_retired_frame = new []int }
while len(render3d_st.gvk_retired_frame) < len(render3d_st.gvk_retired_buf) - 1 { push(render3d_st.gvk_retired_frame, 0) }
push(render3d_st.gvk_retired_frame, render3d_st.gvk_buf_used[b])
} else {
@ -695,7 +692,6 @@ function gvk_retire_flush(render3d_st: mut Render3dState) -> void { gvk_retire_u
# the frame being recorded read stays
function gvk_retire_upto(render3d_st: mut Render3dState, done: int) -> void {
if render3d_st.gvk_retired_buf == null { return }
if render3d_st.gvk_retired_frame == null { render3d_st.gvk_retired_frame = new []int }
while len(render3d_st.gvk_retired_frame) < len(render3d_st.gvk_retired_buf) { push(render3d_st.gvk_retired_frame, 0) }
# compacted in place: three fresh lists a frame were never given back
var w = 0
@ -721,7 +717,6 @@ function gvk_retire_upto(render3d_st: mut Render3dState, done: int) -> void {
# A buffer a compute pass writes: a draw later in the same frame reads what the GPU put there,
# so it is never swapped for fresh storage because it was used this frame (gvk_buf_reserve).
function gvk_buf_gpu_owned(render3d_st: mut Render3dState, b: int) -> void {
if render3d_st.gvk_buf_gpu == null { render3d_st.gvk_buf_gpu = new []int }
while len(render3d_st.gvk_buf_gpu) <= b { push(render3d_st.gvk_buf_gpu, 0) }
render3d_st.gvk_buf_gpu[b] = 1
}

View file

@ -102,11 +102,7 @@ function gg_cull_frame(render3d_st: mut Render3dState) -> void {
gg_tiles(render3d_st, 4, 0.0, 16.0)
gg_tiles(render3d_st, 8, 16.0, 40.0)
gg_tiles(render3d_st, 16, 40.0, render3d_st.grass_radius)
# made once: a words() a frame is a calloc nothing frees
if render3d_st.gg_rb == null {
render3d_st.gg_rb = words(4); render3d_st.gg_cb = words(1); render3d_st.gg_bufs = words(3)
render3d_st.gg_texs = words(3); render3d_st.gg_pr = words(48)
}
# made once, with the device (gvk_startup_state)
let rb = render3d_st.gg_rb
rb[0] = GG_BANDS; rb[1] = 0; rb[2] = 0; rb[3] = 0
let cb = render3d_st.gg_cb

View file

@ -246,3 +246,14 @@ function skin_free(sk: Skin) -> void {
if sk.bones_v != null { free(sk.bones_v) }
free(sk)
}
# 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.
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)
if sk.gmat_v != null { free(sk.gmat_v) }
if sk.bones_v != null { free(sk.bones_v) }
free(sk.tmp_l); free(sk.tmp_q); free(sk.tmp_a); free(sk.tmp_b); free(sk.tmp_c); free(sk.tmp_v)
free(sk)
}