Merge branch 'r3d/frame-allocs2' into lang/foundations

This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 16:22:21 +03:00
commit bb9ec40c18
17 changed files with 37 additions and 8 deletions

View file

@ -76,6 +76,7 @@ function ac_prog_new(render3d_st: mut Render3dState, vs: string, fs: string, def
u_i(render3d_st, gpu_uniform(render3d_st, p, "u_diff"), 0); u_i(render3d_st, gpu_uniform(render3d_st, p, "u_nrm"), 1); u_i(render3d_st, gpu_uniform(render3d_st, p, "u_arm"), 2)
return a
}
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function actor_init(render3d_st: mut Render3dState) -> void {
# the records play takes, made now: an actor first placed in play (a fish, a sign, a bed) made its
# record, matrix and tint then (actor_release gives them back to this pool)
@ -266,6 +267,7 @@ function outline_frame(render3d_st: mut Render3dState) -> void { if render3d_st.
# R3D_ACTOR_CENSUS=<frame>: at that frame, the lit pass's actors grouped by model - skinned or rigid,
# how many share it, the primitives each draws - so instancing is aimed at what repeats
@alloc_ok("a debug census and dumps, only under their switches")
function actor_census(render3d_st: Render3dState) -> void {
let keys = new []Model
let cnt = new []int
@ -364,7 +366,6 @@ function actor_draw_outline_one(render3d_st: mut Render3dState, a: Actor, ap: Ac
# depth, only the draw. (Each actor used to draw into every cascade out to 300 m - in town that was
# about 1500 skinned shadow draws a frame against 69 in the lit pass.)
function ac_in_light(render3d_st: mut Render3dState, vp: floats, x: float, y: float, z: float, r: float) -> bool {
if render3d_st.ac_lp0 == null { render3d_st.ac_lp0 = floats(3); render3d_st.ac_lpx = floats(3); render3d_st.ac_lpy = floats(3); render3d_st.ac_lpz = floats(3) }
m4_xform_point(render3d_st.ac_lp0, vp, x, y, z)
m4_xform_point(render3d_st.ac_lpx, vp, x + r, y, z)
m4_xform_point(render3d_st.ac_lpy, vp, x, y + r, z)

View file

@ -692,6 +692,7 @@ export state Render3dState {
stream_debug_n: int = 0
stream_scratch: floats = null
gvk_pipe_bufs: words = null # a pipeline's vertex buffers, while it is made
ov_nine_buf: floats = null # ov_nine's corners and uvs
gvk_ac: pointer = null # the VkAllocationCallbacks every create and destroy is given (R3D_ALLOC_VK=1)
gvk_prime_b: words = null # buffers made since the frame began, read once so MoltenVK makes their
gvk_prime_h: []long = null # Metal buffers now (gvk_prime), and their handles

View file

@ -33,6 +33,7 @@ function gpu_name(kind: int) -> string { return "vulkan" }
# Start Vulkan. There is nothing to fall back to: a machine whose Vulkan cannot start is told why
# (gpu_fallback_reason, printed) and r3d_init fails, so the program can say so and stop.
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gpu_select(render3d_st: mut Render3dState) -> int {
if render3d_st.gpu_kind != 0 { return render3d_st.gpu_kind }
# a window needs a surface: Win32 on Windows, a CAMetalLayer through MoltenVK on macOS

View file

@ -35,6 +35,7 @@ property GpuVariant {
}
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gpu_variant_new(id: string, vs: string, fs: string, defs: string) -> GpuVariant {
let v = new GpuVariant
v.id = id; v.vs = vs; v.fs = fs; v.defs = defs
@ -46,6 +47,7 @@ function gpu_variant_new(id: string, vs: string, fs: string, defs: string) -> Gp
}
# Read a manifest; returns how many programs it holds (0: none, or no file).
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gpu_manifest_load(render3d_st: mut Render3dState, path: string) -> int {
render3d_st.gpu_variants = new []GpuVariant
let text = Fs.read_text(path)

View file

@ -8,6 +8,7 @@
# ---- the device ---------------------------------------------------------------------------
@alloc_ok("a failure, said once: the message is built only when something is already wrong")
function gvk_fail(render3d_st: mut Render3dState, what: string, r: int) -> bool {
render3d_st.gvk_why = `{what} failed (VkResult {r})`
gvk_note(render3d_st, `r3d: vulkan: {render3d_st.gvk_why}`)
@ -57,6 +58,7 @@ function gvk_no_command_pooling() -> void {
Os.set_env("MVK_CONFIG_USE_COMMAND_POOLING", "0")
}
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gvk_init(render3d_st: mut Render3dState) -> bool {
if render3d_st.gvk_ready { return true }
gvk_no_command_pooling()
@ -514,6 +516,7 @@ function gvk_mem_id(x: long) -> int { return int(x) }
# ---- one-shot commands --------------------------------------------------------------------
# Uploads, bakes and read-backs record into a command buffer, submit it and wait. The frame
# itself does not go through here.
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gvk_cmd_init(render3d_st: mut Render3dState) -> bool {
let cpi = bytes(VkCommandPoolCreateInfo_sizeof)
Vk.zero(cpi, VkCommandPoolCreateInfo_sizeof)
@ -730,6 +733,7 @@ function gvk_labels_close(render3d_st: mut Render3dState) -> void {
# 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
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gvk_startup_state(render3d_st: mut Render3dState) -> void {
let zero: long = 0
render3d_st.gvk_tmp_buf = bytes(GVK_TMP_BYTES)
@ -765,4 +769,6 @@ function gvk_startup_state(render3d_st: mut Render3dState) -> void {
for i in 0 .. 32 { render3d_st.gpu_unit_2d[i] = -1 }
}
if render3d_st.cam_planes == null { render3d_st.cam_planes = floats(16) }
if render3d_st.ov_nine_buf == null { render3d_st.ov_nine_buf = floats(16) }
if render3d_st.ac_lp0 == null { render3d_st.ac_lp0 = floats(3); render3d_st.ac_lpx = floats(3); render3d_st.ac_lpy = floats(3); render3d_st.ac_lpz = floats(3) }
}

View file

@ -267,6 +267,7 @@ function gvk_dispatch_tex(render3d_st: mut Render3dState, c: int, params: pointe
}
# R3D_VK_PROBE=1: one dispatch over a GPU-owned buffer, read back - the compute path end to end
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gvk_compute_probe(render3d_st: mut Render3dState) -> void {
let c = gvk_compute_new(render3d_st, "probe", 1)
if c == 0 { print("r3d: vulkan compute probe: FAILED (no program)"); return }
@ -681,6 +682,7 @@ function gvk_bind_texture(render3d_st: mut Render3dState, p: int, name: string,
# gvk_frame_reset.
const GVK_RING_BYTES: int = 64 * 1024 * 1024
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gvk_frame_init(render3d_st: mut Render3dState) -> bool {
let props = bytes(VkPhysicalDeviceProperties_sizeof)
Vk.get_physical_device_properties(render3d_st.gvk_pd, props)
@ -754,6 +756,7 @@ function gvk_ring_put(render3d_st: mut Render3dState, blk: pointer, n: int) -> i
# sampler, so a set is never matched against a texture it no longer describes.
const GVK_KPOOL_SETS: int = 8192
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gvk_kpool_make(render3d_st: mut Render3dState) -> bool {
let sizes = bytes(VkDescriptorPoolSize_sizeof * 2)
Vk.put_i32(sizes, VkDescriptorPoolSize_type, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC)
@ -775,6 +778,7 @@ function gvk_kpool_make(render3d_st: mut Render3dState) -> bool {
return true
}
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gvk_sc_clear(render3d_st: mut Render3dState) -> void {
# emptied, not replaced: five fresh lists each time the pool filled were never given back
if render3d_st.gvk_sc_prog == null {
@ -1413,6 +1417,7 @@ function gvk_screenshot(render3d_st: mut Render3dState, path: string) -> bool {
# ---- what gpu.ludic's Vulkan branches call -----------------------------------------------------
# the manifest the programs are looked up in, from the renderer's own shader directory
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gvk_manifest(render3d_st: mut Render3dState) -> bool {
r3d_find_root(render3d_st)
render3d_st.gvk_spv_dir = `{render3d_st.r3d_root}/shaders/spv`

View file

@ -87,6 +87,7 @@ function grass_blade_mesh(render3d_st: mut Render3dState, rows: int) -> Mesh {
return m
}
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function grass_init(render3d_st: mut Render3dState) -> void {
render3d_st.grass_merge = gpu_has_mdi(render3d_st) and not r3d_env_has(render3d_st, "R3D_GRASS_TILES")
var defs = "#define FOLIAGE\n#define BLADE\n#define GBLADE\n"

View file

@ -26,6 +26,7 @@ function gg_base(b: int) -> int {
}
# once, after grass_init: the programs, the buffers and the three instanced blades
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gg_init(render3d_st: mut Render3dState) -> void {
if not gpu_has_compute(render3d_st) or render3d_st.grass_prog == 0 { return }
if r3d_env_has(render3d_st, "R3D_GRASS_GPU") and r3d_env(render3d_st, "R3D_GRASS_GPU") == "0" { return }

View file

@ -363,15 +363,14 @@ function ov_sub(render3d_st: mut Render3dState, tex: int, x: int, y: int, w: int
function ov_nine(render3d_st: mut Render3dState, tex: int, tw: int, src: int, x: int, y: int, w: int, h: int, dst: int, r: float, g: float, b: float, a: float) -> void {
ov_use_tex(render3d_st, tex)
let s = float(src) / float(tw)
let xs = floats(4); let ys = floats(4); let us = floats(4); let vs = floats(4)
xs[0] = float(x); xs[1] = float(x + dst); xs[2] = float(x + w - dst); xs[3] = float(x + w)
ys[0] = float(y); ys[1] = float(y + dst); ys[2] = float(y + h - dst); ys[3] = float(y + h)
us[0] = 0.0; us[1] = s; us[2] = 1.0 - s; us[3] = 1.0
vs[0] = 0.0; vs[1] = s; vs[2] = 1.0 - s; vs[3] = 1.0
let nb = render3d_st.ov_nine_buf # made once (gvk_startup_state): xs, ys, us, vs
nb[0 + 0] = float(x); nb[0 + 1] = float(x + dst); nb[0 + 2] = float(x + w - dst); nb[0 + 3] = float(x + w)
nb[4 + 0] = float(y); nb[4 + 1] = float(y + dst); nb[4 + 2] = float(y + h - dst); nb[4 + 3] = float(y + h)
nb[8 + 0] = 0.0; nb[8 + 1] = s; nb[8 + 2] = 1.0 - s; nb[8 + 3] = 1.0
nb[12 + 0] = 0.0; nb[12 + 1] = s; nb[12 + 2] = 1.0 - s; nb[12 + 3] = 1.0
for j in 0 .. 3 {
for i in 0 .. 3 { ov_quad(render3d_st, xs[i], ys[j], xs[i + 1], ys[j + 1], us[i], vs[j], us[i + 1], vs[j + 1], r, g, b, a) }
for i in 0 .. 3 { ov_quad(render3d_st, nb[0 + i], nb[4 + j], nb[0 + i + 1], nb[4 + j + 1], nb[8 + i], nb[12 + j], nb[8 + i + 1], nb[12 + j + 1], r, g, b, a) }
}
free(xs); free(ys); free(us); free(vs)
}
# an arbitrary quad (float-bit pixel corners, clockwise from top-left) of a texture
function ov_quad4(render3d_st: mut Render3dState, x0: float, y0: float, x1: float, y1: float, x2: float, y2: float, x3: float, y3: float, u0: float, v0: float, u1: float, v1: float, r: float, g: float, b: float, a: float) -> void {

View file

@ -15,6 +15,7 @@ const PROF_SLOTS: int = 16
const PROF_RING: int = 4 # frames of latency before a result is read
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function prof_init(render3d_st: mut Render3dState) -> void {
render3d_st.prof_on = r3d_env_has(render3d_st, "R3D_PROF")
if not render3d_st.prof_on { return }

View file

@ -24,6 +24,7 @@ function r3d_home() -> string {
# Settle r3d_root on first use: the package as checked out beside the project, else the
# copy that ships with the toolchain.
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function r3d_find_root(render3d_st: mut Render3dState) -> void {
if render3d_st.r3d_root_found { return }
render3d_st.r3d_root_found = true

View file

@ -505,6 +505,7 @@ function impostor_bake(render3d_st: mut Render3dState, model: Model, tiles: int,
# Give a layer a LOD chain. `dists` (float bits) are the outer distances of each level;
# the last one becomes the layer's `near` so the impostor (if any) starts there.
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function layer_set_lods(render3d_st: mut Render3dState, l: Layer, models: []Model, dists: floats) -> void {
l.lods = models
l.n_lods = len(models)

View file

@ -8,6 +8,7 @@
const SHADOW_CASCADES: int = 5
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function shadow_init(render3d_st: mut Render3dState) -> void {
shadow_make_tex(render3d_st)
render3d_st.sh_fbo = gpu_fb_new(render3d_st)

View file

@ -37,6 +37,7 @@ function sky_dir_from_uv(o: floats, u: float, v: float) -> void {
v3_set(o, st * Math.sin(phi), Math.cos(theta), -(st * Math.cos(phi)))
}
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function sky_load(render3d_st: mut Render3dState, path: string) -> bool {
render3d_st.sky_tex = tex_load_hdr(render3d_st, path)
if render3d_st.sky_tex == 0 { return false }

View file

@ -66,6 +66,7 @@ function gsl_boot(render3d_st: mut Render3dState) -> void {
}
# After Vk.open(), before vkCreateInstance.
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gsl_init(render3d_st: mut Render3dState) -> void {
if render3d_st.gsl_on or Vk.sl_active() == 0 { return }
render3d_st.gsl_feats = gsl_struct(20)

View file

@ -325,6 +325,7 @@ function terrain_height_smooth(render3d_st: mut Render3dState, x: float, z: floa
return h
}
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function terrain_load_textures(render3d_st: mut Render3dState) -> void {
render3d_st.ter_tex = words(15)
let a = render3d_st.r3d_assets + "/textures/"
@ -361,6 +362,7 @@ function terrain_init_step(render3d_st: mut Render3dState, i: int) -> void {
terrain_init_finish(render3d_st)
}
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function terrain_init_finish(render3d_st: mut Render3dState) -> void {
cdlod_init(render3d_st)
render3d_st.ter_wire = r3d_env_has(render3d_st, "R3D_WIRE")
@ -591,6 +593,7 @@ function terrain_draw(render3d_st: mut Render3dState) -> void {
# One 32x32 patch mesh (a_xz in 0..1) drawn once per selected quadtree node; the vertex
# shader places, scales and morphs it. Levels are drawn out to cd_range[k] = 48 * 2^k m,
# so cells are 1 m within 48 m, 2 m to 96 m, 4 m to 192 m ... 256 m at the root.
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function cdlod_init(render3d_st: mut Render3dState) -> void {
let m = gpu_mesh_new(render3d_st)
let n = CD_G + 1
@ -624,6 +627,7 @@ function cdlod_init(render3d_st: mut Render3dState) -> void {
}
# min/max height per patch at every level, from the CPU copy of the height field
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function cdlod_bounds(render3d_st: mut Render3dState) -> void {
render3d_st.cd_min = new []floats; render3d_st.cd_max = new []floats
let t = TERRAIN_RES / CD_LEAVES # texels per leaf patch side

View file

@ -321,6 +321,7 @@ function tex_solid(render3d_st: mut Render3dState, r: int, g: int, b: int, a: in
# ---- Radiance .hdr (RGBE, new-style RLE) -> RGB float bits -------------------------
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function hdr_decode(render3d_st: mut Render3dState, path: pointer) -> floats {
let d = r3d_read_file(render3d_st, path)
if d == null { print(`hdr: cannot read {path}`); return null }
@ -437,6 +438,7 @@ function tex_target(render3d_st: mut Render3dState, w: int, h: int, ifmt: int, f
}
# Debug: the brightest texel of an RGBA float texture and where it is.
@alloc_ok("a debug census and dumps, only under their switches")
function tex_max(render3d_st: mut Render3dState, tex: int, w: int, h: int, tag: pointer) -> void {
let buf = floats(w * h * 4)
gpu_tex_bind(render3d_st, GPU_TEX2D, tex)