At the cut the whole 4096 height, normal and photograph textures go, replaced by a coarse 2048 level (the CPU's tt_coarse uploaded; the normal and photograph blitted down on the GPU, the photograph mipmapped) and a pool of fine tiles in three array textures - heights, normals, photograph, each layer a tile with a one-texel border - addressed through a tt_n^2 page table (u_tp_page: slot + 1, 0 = the coarse level). The pool holds the tiles within the reach (900 m, or the fog wall's when nearer) and a ring, and is made again when the fog wall changes its size (terrain_pages_fog). Once a frame (tp_frame, beside tt_frame) tiles past the reach and two tiles go and the wanted ones come in nearest first, 8 a frame, written into a staging buffer kept for the process (two halves, one per frame in flight) and copied into their layers inside the frame's own command buffer - no submit of their own - with the page table re-uploaded only when it changed. tp_bind binds the pool (or 1-layer stand-in arrays while paging is off, u_tp_on = 0) for every program that reads the ground: terrain_bind_height (models, scatter, shadow_bind, grass), terrain_bind_prog, the sun pass and the shadow bake. grass_cull.comp reads through the same page table. R3D_VKMEM prints the pool's line. Tiles off, nothing changes. Compile-only: not run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
74 lines
3.4 KiB
Text
74 lines
3.4 KiB
Text
# terrain_pages_fill.ludic — a tile's three layers written into the staging, border and all, and the
|
|
# frame's copies recorded (terrain_pages_frame.ludic). The staging half is laid out as the page table,
|
|
# then TP_BUDGET heights, TP_BUDGET normals and TP_BUDGET photographs.
|
|
|
|
# the k-th load of the frame: tile t's heights and normals, (TT_TEX + 2) a side, each texel the one
|
|
# the whole copy held at that place, clamped at the map's edge
|
|
function tp_fill(render3d_st: mut Render3dState, t: int, base: int, k: int) -> void {
|
|
let n = render3d_st.tt_n
|
|
let hs = TT_TEX + 2
|
|
let hb = hs * hs * 4
|
|
let ho = base + n * n * 4 + k * hb
|
|
let no = base + n * n * 4 + TP_BUDGET * hb + k * hb
|
|
let i0 = (t % n) * TT_TEX - 1
|
|
let j0 = (t / n) * TT_TEX - 1
|
|
let p = render3d_st.tp_st_ptr
|
|
for b in 0 .. hs {
|
|
let tz = min(max(j0 + b, 0), TERRAIN_RES - 1)
|
|
for a in 0 .. hs {
|
|
let tx = min(max(i0 + a, 0), TERRAIN_RES - 1)
|
|
let o = (b * hs + a) * 4
|
|
Vk.put_i32(p, ho + o, float_bits(ter_h(render3d_st, tx, tz)))
|
|
Vk.put_i32(p, no + o, ter_n(render3d_st, tx, tz))
|
|
}
|
|
}
|
|
tp_fill_ortho(render3d_st, t, base + n * n * 4 + 2 * TP_BUDGET * hb, k)
|
|
}
|
|
|
|
# ... and its photograph, (tt_oside + 2) a side, as sRGB RGBA8 bytes with alpha 255
|
|
function tp_fill_ortho(render3d_st: mut Render3dState, t: int, at: int, k: int) -> void {
|
|
let n = render3d_st.tt_n
|
|
let side = render3d_st.tt_oside
|
|
let os = side + 2
|
|
let top = n * side - 1
|
|
let o0 = at + k * os * os * 4
|
|
let i0 = (t % n) * side - 1
|
|
let j0 = (t / n) * side - 1
|
|
let p: pointer = render3d_st.tp_st_ptr
|
|
for b in 0 .. os {
|
|
let pz = min(max(j0 + b, 0), top)
|
|
for a in 0 .. os {
|
|
let v = ter_o(render3d_st, min(max(i0 + a, 0), top), pz)
|
|
let o = o0 + (b * os + a) * 4
|
|
p[o] = (v >> 16) & 255
|
|
p[o + 1] = (v >> 8) & 255
|
|
p[o + 2] = v & 255
|
|
p[o + 3] = 255
|
|
}
|
|
}
|
|
}
|
|
|
|
# the frame's k loads into their slots, then the page table if it changed: all in cb, no submit
|
|
function tp_upload(render3d_st: mut Render3dState, cb: pointer, base: int, k: int) -> void {
|
|
let n = render3d_st.tt_n
|
|
let hs = TT_TEX + 2
|
|
let hb = hs * hs * 4
|
|
let os = render3d_st.tt_oside + 2
|
|
let p0 = base + n * n * 4
|
|
let slots = render3d_st.tp_cand_s
|
|
gpu_layers_copy(render3d_st, cb, render3d_st.tp_h_tex, hs, hs, p0, hb, slots, k)
|
|
gpu_layers_copy(render3d_st, cb, render3d_st.tp_n_tex, hs, hs, p0 + TP_BUDGET * hb, hb, slots, k)
|
|
gpu_layers_copy(render3d_st, cb, render3d_st.tp_o_tex, os, os, p0 + 2 * TP_BUDGET * hb, os * os * 4, slots, k)
|
|
if not render3d_st.tp_dirty { return }
|
|
mem_copy(mem_off(render3d_st.tp_st_ptr, base), data_of(render3d_st.tp_page), n * n * 4)
|
|
gpu_layers_copy(render3d_st, cb, render3d_st.tp_page_tex, n, n, base, 0, render3d_st.tp_zero, 1)
|
|
render3d_st.tp_dirty = false
|
|
}
|
|
|
|
# R3D_VKMEM: one line on the pool, next to the rest of the report
|
|
function tp_report(render3d_st: Render3dState) -> void {
|
|
if not render3d_st.tp_on { return }
|
|
tp_say_report(render3d_st.tp_slots - render3d_st.tp_nfree, render3d_st.tp_slots, render3d_st.tp_frame_loads, render3d_st.tp_loads, render3d_st.tp_bytes, render3d_st.tp_reach)
|
|
}
|
|
@alloc_ok("a report printed once, under R3D_VKMEM")
|
|
function tp_say_report(res: int, slots: int, now: int, all: int, bytes: int, r: float) -> void { print(`vkmem: terrain pages: {res} of {slots} slots resident, {now} loaded this frame, {all} over the run, {bytes / 1024} KB on the GPU, {int(r)} m round the camera`) }
|