Merge commit 'a12f1b1' into lang/foundations

This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 17:24:52 +03:00
commit 7f3b842d59
8 changed files with 397 additions and 149 deletions

View file

@ -840,6 +840,11 @@ export state Render3dState {
tt_h: floats = null # TT_SLOTS tiles of heights
tt_o: words = null # ... and of photograph, 0xRRGGBB
tt_nm: words = null # ... and of normals, RG16F in a word
tt_tab: floats = null # per tile: its heights' minimum and step (terrain_ltt2_*.ludic)
tt_scratch: []byte = null # one tile's bytes as read
tt_cnoff: int = 0 # the coarse normals and photograph in the file
tt_cooff: int = 0
tt_baked: bool = false # the terrain came from a bake: nothing is generated or cut
tt_coarse: floats = null # the heights at TT_COARSE a side, for terrain_height_near
tt_slot_of: words = null # per tile: its slot, or -1
tt_tile_in: words = null # per slot: its tile, or -1

View file

@ -29,8 +29,9 @@ import "terrain.ludic"
import "terrain_chunks.ludic"
import "terrain_tiles.ludic"
import "terrain_tiles_cut.ludic"
import "terrain_ltt2_write.ludic"
import "terrain_ltt2_read.ludic"
import "terrain_pages.ludic"
import "terrain_pages_coarse.ludic"
import "terrain_pages_frame.ludic"
import "terrain_pages_fill.ludic"
import "overlay.ludic"

View file

@ -411,8 +411,9 @@ function terrain_init__t(render3d_st: mut Render3dState) -> void {
# shadow, 2 the ground's materials, 3 the patch tree and the terrain programs.
const TERRAIN_INIT_STEPS: int = 4
function terrain_init_step(render3d_st: mut Render3dState, i: int) -> void {
if i == 0 { terrain_generate(render3d_st); return }
if i == 1 { terrain_bake_shadow(render3d_st); return }
if i == 0 { if not render3d_st.tt_baked { terrain_generate(render3d_st) }; return }
# a baked terrain whose shadow came from its own bake (terrain_shadow_from_bytes) keeps it
if i == 1 { if not (render3d_st.tt_baked and render3d_st.ter_shadow_tex != 0) { terrain_bake_shadow(render3d_st) }; return }
if i == 2 { terrain_load_textures(render3d_st); return }
terrain_init_finish(render3d_st)
}

View file

@ -0,0 +1,181 @@
# terrain_ltt2_read.ludic — the quantized tiles opened for reading (a bake's payload, or the dev cut's
# own file), a tile decoded into the pools as it is read, the coarse level made on the GPU from the
# file, and a boot that takes the terrain from a bake instead of making it.
# the tiles' payload at `base` in the file at `path` opened; false when it is not one this build reads
@alloc_ok("a map being loaded: its tiles' index and pools, once")
function tt_open_ltt2(render3d_st: mut Render3dState, path: string, base: int) -> bool {
tt_close(render3d_st)
let f = file_open(path, "rb")
if f == null { return false }
let head = words(32)
file_seek(f, base, 0)
let got = file_read(f, data_of(head), LT_HEAD)
if got != LT_HEAD or head[0] != LT_MAGIC or head[1] != LT_VERSION or head[2] != TT_TEX or head[5] != TERRAIN_RES or head[7] != TT_COARSE {
free(head)
file_close(f)
return false
}
let n = head[3]
let side = head[4]
render3d_st.tt_file = f
render3d_st.tt_n = n; render3d_st.tt_oside = side; render3d_st.ter_ortho_w = head[6]
render3d_st.tt_hoff = base + head[8] + n * n * 8
render3d_st.tt_noff = base + head[10]; render3d_st.tt_ooff = base + head[11]
render3d_st.tt_cnoff = base + head[13]; render3d_st.tt_cooff = base + head[14]
if render3d_st.tt_tab != null { free(render3d_st.tt_tab) }
render3d_st.tt_tab = floats(n * n * 2)
file_seek(f, base + head[8], 0)
file_read(f, data_of(render3d_st.tt_tab), n * n * 8)
if render3d_st.tt_coarse == null { render3d_st.tt_coarse = floats(TT_COARSE * TT_COARSE) }
file_seek(f, base + head[12], 0)
file_read(f, data_of(render3d_st.tt_coarse), TT_COARSE * TT_COARSE * 4)
free(head)
tt_pools(render3d_st, n, side)
return true
}
# the pools, the index, the clock and the read scratch, for n tiles a side
@alloc_ok("a map being loaded: its tiles' pools, once")
function tt_pools(render3d_st: mut Render3dState, n: int, side: int) -> void {
if render3d_st.tt_h == null { render3d_st.tt_h = floats(TT_SLOTS * TT_TEX * TT_TEX) }
if render3d_st.tt_nm == null { render3d_st.tt_nm = words(TT_SLOTS * TT_TEX * TT_TEX) }
if render3d_st.tt_o == null or len(render3d_st.tt_o) != TT_SLOTS * side * side {
if render3d_st.tt_o != null { free(render3d_st.tt_o) }
render3d_st.tt_o = words(TT_SLOTS * side * side)
}
let sc = max(TT_TEX * TT_TEX * 2, side * side * 3)
if render3d_st.tt_scratch == null or len(render3d_st.tt_scratch) < sc {
if render3d_st.tt_scratch != null { free(render3d_st.tt_scratch) }
render3d_st.tt_scratch = buffer(sc)
}
if render3d_st.tt_slot_of != null { free(render3d_st.tt_slot_of) }
render3d_st.tt_slot_of = words(n * n)
for t in 0 .. n * n { render3d_st.tt_slot_of[t] = -1 }
if render3d_st.tt_tile_in == null { render3d_st.tt_tile_in = words(TT_SLOTS); render3d_st.tt_ref = words(TT_SLOTS) }
for s in 0 .. TT_SLOTS { render3d_st.tt_tile_in[s] = -1; render3d_st.tt_ref[s] = 0 }
render3d_st.tt_hand = 0
}
# tile t's heights, normals and photograph decoded into slot s: exactly the values ltt2_write put back
function tt_decode(render3d_st: mut Render3dState, t: int, s: int) -> void {
let f = render3d_st.tt_file
let sc = render3d_st.tt_scratch
let n2 = TT_TEX * TT_TEX
file_seek(f, render3d_st.tt_hoff + t * n2 * 2, 0)
file_read(f, data_of(sc), n2 * 2)
let lo = render3d_st.tt_tab[t * 2]
let step = render3d_st.tt_tab[t * 2 + 1]
for i in 0 .. n2 { render3d_st.tt_h[s * n2 + i] = lo + float(sc[i * 2] | (sc[i * 2 + 1] << 8)) * step }
file_seek(f, render3d_st.tt_noff + t * n2 * 2, 0)
file_read(f, data_of(sc), n2 * 2)
for i in 0 .. n2 {
let e = sc[i * 2] | (sc[i * 2 + 1] << 8)
render3d_st.tt_nm[s * n2 + i] = gvk_half(float_bits(lt_oct_dec(e, false))) | (gvk_half(float_bits(lt_oct_dec(e, true))) << 16)
}
let side = render3d_st.tt_oside
let o2 = side * side
file_seek(f, render3d_st.tt_ooff + t * o2 * 3, 0)
file_read(f, data_of(sc), o2 * 3)
for i in 0 .. o2 { render3d_st.tt_o[s * o2 + i] = (sc[i * 3] << 16) | (sc[i * 3 + 1] << 8) | sc[i * 3 + 2] }
}
# the GPU's coarse level from the file (the whole textures, if any, let go) and the page pool made
@alloc_ok("a map being loaded: the coarse level, once")
function ltt2_gpu_coarse(render3d_st: mut Render3dState) -> void {
let was = render3d_st.gvk_tag
render3d_st.gvk_tag = VKM_TERRAIN
let c = TT_COARSE
let f = render3d_st.tt_file
let h = tex_target(render3d_st, c, c, GL_R32F, GL_RED, GL_FLOAT, GL_LINEAR)
gpu_tex_fill(render3d_st, h, GL_R32F, c, c, GL_RED, GL_FLOAT, data_of(render3d_st.tt_coarse))
let raw = buffer(c * c * 3)
file_seek(f, render3d_st.tt_cnoff, 0)
file_read(f, data_of(raw), c * c * 2)
let xz = floats(c * c * 2)
for i in 0 .. c * c {
let e = raw[i * 2] | (raw[i * 2 + 1] << 8)
xz[i * 2] = lt_oct_dec(e, false); xz[i * 2 + 1] = lt_oct_dec(e, true)
}
let nm = tex_target(render3d_st, c, c, GL_RG16F, GL_RG, GL_FLOAT, GL_LINEAR)
gpu_tex_fill(render3d_st, nm, GL_RG16F, c, c, GL_RG, GL_FLOAT, data_of(xz))
free(xz)
file_seek(f, render3d_st.tt_cooff, 0)
file_read(f, data_of(raw), c * c * 3)
let o = tex_target(render3d_st, c, c, GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, GL_LINEAR)
gpu_tex_fill(render3d_st, o, GL_SRGB8_ALPHA8, c, c, GL_RGB, GL_UNSIGNED_BYTE, data_of(raw))
free(raw)
gpu_tex_bind(render3d_st, GPU_TEX2D, o)
gpu_tex_param(render3d_st, GPU_TEX2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
gpu_tex_mips(render3d_st, GPU_TEX2D)
gpu_tex_paramf(render3d_st, GPU_TEX2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, render3d_st.tex_anisotropy)
if render3d_st.ter_height_tex != 0 { gpu_tex_free(render3d_st, render3d_st.ter_height_tex) }
if render3d_st.ter_normal_tex != 0 { gpu_tex_free(render3d_st, render3d_st.ter_normal_tex) }
if render3d_st.ter_ortho_tex != 0 { gpu_tex_free(render3d_st, render3d_st.ter_ortho_tex) }
render3d_st.ter_height_tex = h; render3d_st.ter_normal_tex = nm; render3d_st.ter_ortho_tex = o
render3d_st.gvk_tag = was
tp_pool(render3d_st)
}
# ---- the bake -------------------------------------------------------------------------------
# The map as made (heights generated, photograph loaded, normals baked) written as a bake's file:
# ludic.base's LBAK header (render3d cannot import it; the same layout) and the tiles as its payload.
# The whole copy is left holding the quantized values, as everything will read them.
@alloc_ok("a bake: once, at build time")
export function terrain_tiles_bake(render3d_st: mut Render3dState, path: string, key: string, version: int, inputs_hash: long) -> bool {
if render3d_st.ter_heights == null or render3d_st.ter_ortho_px == null or render3d_st.ter_normal_tex == 0 { return false }
let f = file_open(path, "wb")
if f == null { return false }
let base = 32 + (len(key) + 7) / 8 * 8
let head = buffer(base)
for i in 0 .. base { head[i] = 0 }
file_write(f, data_of(head), base)
let n = ltt2_write(render3d_st, f, base)
let p = data_of(head)
Vk.put_i32(p, 0, BAKE_MAGIC); Vk.put_i32(p, 4, 1); Vk.put_i32(p, 8, version); Vk.put_i32(p, 12, base)
Vk.put_i64(p, 16, inputs_hash)
let nl: long = n
Vk.put_i64(p, 24, nl)
for i in 0 .. len(key) { head[32 + i] = key[i] }
file_seek(f, 0, 0)
file_write(f, data_of(head), base)
file_close(f)
free(head)
return true
}
# A boot that takes the terrain from a bake: the tiles opened, the coarse level made, nothing
# generated. Call it where the map was set up (in place of terrain_use_dem / terrain_use_ortho),
# before terrain_init; false (and nothing changed) when the bake is missing or not this version, and
# the caller makes the map as before.
@alloc_ok("a map being loaded from its bake, once")
export function terrain_from_baked(render3d_st: mut Render3dState, path: string, key: string, version: int, half: int, ox: float, oz: float) -> bool {
let base = ltt2_bake_base(path, key, version)
if base < 0 { return false }
render3d_st.TERRAIN_HALF = half
render3d_st.ter_ox = ox; render3d_st.ter_oz = oz
render3d_st.ter_h_scale = 0.0; render3d_st.ter_o_scale = 0.0
if not tt_open_ltt2(render3d_st, path, base) { return false }
render3d_st.tt_baked = true
tp_dummies(render3d_st)
ltt2_gpu_coarse(render3d_st)
return true
}
# where the payload starts in a bake's file when its key and version are these, else -1
@alloc_ok("a bake's header read once at load")
function ltt2_bake_base(path: string, key: string, version: int) -> int {
let f = file_open(path, "rb")
if f == null { return -1 }
let h = buffer(96)
let got = file_read(f, data_of(h), 96)
file_close(f)
let p = data_of(h)
var base = -1
if got >= 32 and Vk.get_i32(p, 0) == BAKE_MAGIC and Vk.get_i32(p, 4) == 1 and Vk.get_i32(p, 8) == version {
base = Vk.get_i32(p, 12)
for i in 0 .. len(key) { if 32 + i >= got or h[32 + i] != key[i] { base = -1 } }
}
free(h)
return base
}

View file

@ -0,0 +1,199 @@
# terrain_ltt2_write.ludic — the terrain as THE quantized tiles every reader answers from (plan 26 of
# maroon-lake): heights as u16 over each tile's own minimum and step, normals octahedral 8 + 8, the
# photograph RGB8, and the coarse level. Written by the build-time bake and, in a dev build with no bake,
# by the cut; either way the whole copy is put back to the quantized values, so the build's own
# queries, the physics, the placements' bake and every machine read the same numbers.
const LT_MAGIC: int = 844383308 # "LTT2"
const LT_VERSION: int = 1
const LT_HEAD: int = 128 # 32 words
# the payload at the file's current place (its start is `base`); its length. Header written last.
@alloc_ok("a map being made or baked: the tiles written once")
function ltt2_write(render3d_st: mut Render3dState, f: pointer, base: int) -> int {
let n = TERRAIN_RES / TT_TEX
let side = render3d_st.ter_ortho_w / n
let head = words(32)
for k in 0 .. 32 { head[k] = 0 }
file_write(f, data_of(head), LT_HEAD)
var at = LT_HEAD
head[8] = at; at += ltt2_write_heights(render3d_st, f, n)
let nrm = ltt2_normals_back(render3d_st)
head[10] = at; at += ltt2_write_normals(f, nrm, n)
head[11] = at; at += ltt2_write_ortho(render3d_st, f, n, side)
head[12] = at; at += ltt2_write_coarse_h(render3d_st, f)
head[13] = at; at += ltt2_write_coarse_n(f, nrm)
free(nrm)
head[14] = at; at += ltt2_write_coarse_o(render3d_st, f)
head[0] = LT_MAGIC; head[1] = LT_VERSION; head[2] = TT_TEX; head[3] = n; head[4] = side
head[5] = TERRAIN_RES; head[6] = render3d_st.ter_ortho_w; head[7] = TT_COARSE; head[15] = at
file_seek(f, base, 0)
file_write(f, data_of(head), LT_HEAD)
file_seek(f, base + at, 0)
free(head)
return at
}
# each tile's (min, step) table, then its heights as u16; the whole copy put back to what they decode to
@alloc_ok("a map being made or baked: one tile's scratch and the table, freed after")
function ltt2_write_heights(render3d_st: mut Render3dState, f: pointer, n: int) -> int {
let tab = floats(n * n * 2)
let h = render3d_st.ter_heights
for j in 0 .. n {
for i in 0 .. n {
var lo = 1000000.0; var hi = -1000000.0
for r in 0 .. TT_TEX { for k in 0 .. TT_TEX { let v = h[(j * TT_TEX + r) * TERRAIN_RES + i * TT_TEX + k]; lo = Math.min(lo, v); hi = Math.max(hi, v) } }
tab[(j * n + i) * 2] = lo; tab[(j * n + i) * 2 + 1] = (hi - lo) / 65535.0
}
}
file_write(f, data_of(tab), n * n * 8)
let b = buffer(TT_TEX * TT_TEX * 2)
for j in 0 .. n {
for i in 0 .. n {
let lo = tab[(j * n + i) * 2]; let step = tab[(j * n + i) * 2 + 1]
for r in 0 .. TT_TEX {
for k in 0 .. TT_TEX {
let at = (j * TT_TEX + r) * TERRAIN_RES + i * TT_TEX + k
var q = 0
if step > 0.0 { q = min(max(int(Math.floor((h[at] - lo) / step + 0.5)), 0), 65535) }
h[at] = lo + float(q) * step
b[(r * TT_TEX + k) * 2] = q & 255; b[(r * TT_TEX + k) * 2 + 1] = q >> 8
}
}
file_write(f, data_of(b), TT_TEX * TT_TEX * 2)
}
}
free(b)
free(tab)
return n * n * 8 + n * n * TT_TEX * TT_TEX * 2
}
# the baked normals read back once (RG16F: two halves a word)
@alloc_ok("a map being made or baked: the normals read back once, freed by the caller")
function ltt2_normals_back(render3d_st: mut Render3dState) -> words {
let all = words(TERRAIN_RES * TERRAIN_RES)
gpu_tex_bind(render3d_st, GPU_TEX2D, render3d_st.ter_normal_tex)
gpu_pixel_store(render3d_st, GL_PACK_ALIGNMENT, 4)
gpu_tex_read(render3d_st, GPU_TEX2D, GL_RG, GL_HALF_FLOAT, data_of(all))
return all
}
@alloc_ok("a map being made or baked: one tile's scratch, freed after")
function ltt2_write_normals(f: pointer, nrm: words, n: int) -> int {
let b = buffer(TT_TEX * TT_TEX * 2)
for j in 0 .. n {
for i in 0 .. n {
for r in 0 .. TT_TEX {
for k in 0 .. TT_TEX {
let w = nrm[(j * TT_TEX + r) * TERRAIN_RES + i * TT_TEX + k]
let e = lt_oct_enc(lt_unhalf(w & 0xFFFF), lt_unhalf((w >> 16) & 0xFFFF))
b[(r * TT_TEX + k) * 2] = e & 255; b[(r * TT_TEX + k) * 2 + 1] = e >> 8
}
}
file_write(f, data_of(b), TT_TEX * TT_TEX * 2)
}
}
free(b)
return n * n * TT_TEX * TT_TEX * 2
}
@alloc_ok("a map being made or baked: one tile's scratch, freed after")
function ltt2_write_ortho(render3d_st: Render3dState, f: pointer, n: int, side: int) -> int {
let b = buffer(side * side * 3)
let w = render3d_st.ter_ortho_w
let c = render3d_st.ter_ortho_c
let px = render3d_st.ter_ortho_px
for j in 0 .. n {
for i in 0 .. n {
for r in 0 .. side {
for k in 0 .. side {
let o = ((j * side + r) * w + i * side + k) * c
let d = (r * side + k) * 3
b[d] = px[o]; b[d + 1] = px[o + 1]; b[d + 2] = px[o + 2]
}
}
file_write(f, data_of(b), side * side * 3)
}
}
free(b)
return n * n * side * side * 3
}
# the coarse heights: the mean of each 2 x 2 of the quantized ones (kept in tt_coarse too)
function ltt2_write_coarse_h(render3d_st: mut Render3dState, f: pointer) -> int {
tt_make_coarse(render3d_st)
file_write(f, data_of(render3d_st.tt_coarse), TT_COARSE * TT_COARSE * 4)
return TT_COARSE * TT_COARSE * 4
}
# the coarse normals: each 2 x 2's mean direction, octahedral
@alloc_ok("a map being made or baked: one row's scratch, freed after")
function ltt2_write_coarse_n(f: pointer, nrm: words) -> int {
let k = TERRAIN_RES / TT_COARSE
let b = buffer(TT_COARSE * 2)
for j in 0 .. TT_COARSE {
for i in 0 .. TT_COARSE {
var sx = 0.0; var sz = 0.0
for bb in 0 .. k { for a in 0 .. k { let w = nrm[(j * k + bb) * TERRAIN_RES + i * k + a]; sx = sx + lt_unhalf(w & 0xFFFF); sz = sz + lt_unhalf((w >> 16) & 0xFFFF) } }
let e = lt_oct_enc(sx / float(k * k), sz / float(k * k))
b[i * 2] = e & 255; b[i * 2 + 1] = e >> 8
}
file_write(f, data_of(b), TT_COARSE * 2)
}
free(b)
return TT_COARSE * TT_COARSE * 2
}
# the coarse photograph: each block's mean colour, RGB8
@alloc_ok("a map being made or baked: one row's scratch, freed after")
function ltt2_write_coarse_o(render3d_st: Render3dState, f: pointer) -> int {
let w = render3d_st.ter_ortho_w
let k = max(w / TT_COARSE, 1)
let c = render3d_st.ter_ortho_c
let px = render3d_st.ter_ortho_px
let b = buffer(TT_COARSE * 3)
for j in 0 .. TT_COARSE {
for i in 0 .. TT_COARSE {
for ch in 0 .. 3 {
var s = 0
for bb in 0 .. k { for a in 0 .. k { s += px[((min(j * k + bb, w - 1)) * w + min(i * k + a, w - 1)) * c + ch] } }
b[i * 3 + ch] = (s + k * k / 2) / (k * k)
}
}
file_write(f, data_of(b), TT_COARSE * 3)
}
free(b)
return TT_COARSE * TT_COARSE * 3
}
# a normal's x and z (y up, rebuilt) as octahedral 8 + 8 bits, and back
function lt_oct_enc(x: float, z: float) -> int {
let y = Math.sqrt(Math.max(1.0 - x * x - z * z, 0.0))
let s = Math.abs(x) + y + Math.abs(z)
var px = 0.0; var pz = 0.0
if s > 0.0 { px = x / s; pz = z / s }
let qx = min(max(int(Math.floor((px * 0.5 + 0.5) * 255.0 + 0.5)), 0), 255)
let qz = min(max(int(Math.floor((pz * 0.5 + 0.5) * 255.0 + 0.5)), 0), 255)
return qx | (qz << 8)
}
# the decoded normal's x (want_z false) or z
function lt_oct_dec(e: int, want_z: bool) -> float {
let px = float(e & 255) / 255.0 * 2.0 - 1.0
let pz = float((e >> 8) & 255) / 255.0 * 2.0 - 1.0
let y = Math.max(1.0 - Math.abs(px) - Math.abs(pz), 0.0)
let l = Math.sqrt(px * px + y * y + pz * pz)
if l <= 0.0 { return 0.0 }
if want_z { return pz / l }
return px / l
}
# an IEEE half's bits as a float
function lt_unhalf(h: int) -> float {
let s = (h >> 15) & 1
let e = (h >> 10) & 31
let m = h & 1023
var v = 0.0
if e == 0 { v = float(m) / 16777216.0 } else { v = float_from_bits(((e - 15 + 127) << 23) | (m << 13)) }
if s == 1 { return -v }
return v
}

View file

@ -1,30 +0,0 @@
# terrain_pages_coarse.ludic — the whole-map level the page pool falls back to (terrain_pages.ludic),
# made on the GPU from the whole textures at the cut, before they go.
# at the cut (terrain_tiles_cut): the coarse level made from the whole textures, which then go - the
# patch bounds and the sun's bake have already read them - and the pool made
function tp_cut(render3d_st: mut Render3dState) -> void {
let was = render3d_st.gvk_tag
render3d_st.gvk_tag = VKM_TERRAIN
tp_cut__t(render3d_st)
render3d_st.gvk_tag = was
}
function tp_cut__t(render3d_st: mut Render3dState) -> void {
if render3d_st.tt_file == null or render3d_st.tt_coarse == null or render3d_st.ter_ortho_tex == 0 { return }
let c = TT_COARSE
let h = tex_target(render3d_st, c, c, GL_R32F, GL_RED, GL_FLOAT, GL_LINEAR)
gpu_tex_fill(render3d_st, h, GL_R32F, c, c, GL_RED, GL_FLOAT, data_of(render3d_st.tt_coarse))
let nm = tex_target(render3d_st, c, c, GL_RG16F, GL_RG, GL_FLOAT, GL_LINEAR)
gpu_tex_blit(render3d_st, render3d_st.ter_normal_tex, TERRAIN_RES, TERRAIN_RES, nm, c, c)
let o = tex_target(render3d_st, c, c, GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, GL_LINEAR)
gpu_tex_blit(render3d_st, render3d_st.ter_ortho_tex, render3d_st.ter_ortho_w, render3d_st.ter_ortho_w, o, c, c)
gpu_tex_bind(render3d_st, GPU_TEX2D, o)
gpu_tex_param(render3d_st, GPU_TEX2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
gpu_tex_mips(render3d_st, GPU_TEX2D)
gpu_tex_paramf(render3d_st, GPU_TEX2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, render3d_st.tex_anisotropy)
gpu_tex_free(render3d_st, render3d_st.ter_height_tex)
gpu_tex_free(render3d_st, render3d_st.ter_normal_tex)
gpu_tex_free(render3d_st, render3d_st.ter_ortho_tex)
render3d_st.ter_height_tex = h; render3d_st.ter_normal_tex = nm; render3d_st.ter_ortho_tex = o
tp_pool(render3d_st)
}

View file

@ -64,16 +64,7 @@ function tt_read(render3d_st: mut Render3dState, t: int) -> int {
render3d_st.tt_hand = (s + 1) % TT_SLOTS
let old = render3d_st.tt_tile_in[s]
if old >= 0 { render3d_st.tt_slot_of[old] = -1 }
let hb = TT_TEX * TT_TEX * 4
file_seek(render3d_st.tt_file, render3d_st.tt_hoff + t * hb, 0)
file_read(render3d_st.tt_file, mem_off(data_of(render3d_st.tt_h), s * hb), hb)
let ob = render3d_st.tt_oside * render3d_st.tt_oside * 4
if ob > 0 {
file_seek(render3d_st.tt_file, render3d_st.tt_ooff + t * ob, 0)
file_read(render3d_st.tt_file, mem_off(data_of(render3d_st.tt_o), s * ob), ob)
}
file_seek(render3d_st.tt_file, render3d_st.tt_noff + t * hb, 0)
file_read(render3d_st.tt_file, mem_off(data_of(render3d_st.tt_nm), s * hb), hb)
tt_decode(render3d_st, t, s)
render3d_st.tt_tile_in[s] = t
render3d_st.tt_slot_of[t] = s
render3d_st.tt_ref[s] = 1

View file

@ -9,120 +9,20 @@ const TT_HEAD: int = 64 # bytes: 16 words
@alloc_ok("a map being made: its tiles' file and their pools, once, not a frame")
function terrain_tiles_cut(render3d_st: mut Render3dState) -> void {
if render3d_st.tt_dir == "" or render3d_st.ter_heights == null or render3d_st.ter_ortho_px == null { return }
if render3d_st.tt_baked or render3d_st.tt_dir == "" or render3d_st.ter_heights == null or render3d_st.ter_ortho_px == null { return }
let t0 = gl_now_us()
tt_close(render3d_st)
let n = TERRAIN_RES / TT_TEX
let side = render3d_st.ter_ortho_w / n
let path = `{render3d_st.tt_dir}/{render3d_st.tt_key}.tiles`
Fs.mkdir(render3d_st.tt_dir)
let f = file_open(path, "wb")
if f == null { tt_say_no_file(path); return }
let head = words(16)
for k in 0 .. 16 { head[k] = 0 }
file_write(f, data_of(head), TT_HEAD)
var sum = tt_write_heights(render3d_st, f, n)
sum = tt_write_ortho(render3d_st, f, n, side, sum)
sum = tt_write_normals(render3d_st, f, n, sum)
head[0] = TT_MAGIC; head[1] = TT_VERSION; head[2] = TT_TEX; head[3] = n; head[4] = side; head[5] = TERRAIN_RES
head[6] = render3d_st.ter_ortho_w; head[7] = sum
file_seek(f, 0, 0)
file_write(f, data_of(head), TT_HEAD)
# the check compares the tiles with the whole copy, so it runs on the quantized copy the write leaves
let bytes = ltt2_write(render3d_st, f, 0)
file_close(f)
free(head)
tt_open(render3d_st, path, n, side)
tt_make_coarse(render3d_st)
if not tt_open_ltt2(render3d_st, path, 0) { tt_say_no_file(path); return }
if r3d_env_has(render3d_st, "R3D_TT_CHECK") { tt_check(render3d_st) }
tp_cut(render3d_st)
tt_say_cut(path, TT_HEAD + n * n * (TT_TEX * TT_TEX * 2 + side * side) * 4, (gl_now_us() - t0) / 1000)
}
# the heights, tile after tile, each a TT_TEX-row block straight out of the whole copy; their checksum
function tt_write_heights(render3d_st: Render3dState, f: pointer, n: int) -> int {
var sum = 0
let base = data_of(render3d_st.ter_heights)
for j in 0 .. n {
for i in 0 .. n {
for r in 0 .. TT_TEX {
let at = (j * TT_TEX + r) * TERRAIN_RES + i * TT_TEX
file_write(f, mem_off(base, at * 4), TT_TEX * 4)
for k in 0 .. TT_TEX { sum = tt_mix(sum, float_bits(render3d_st.ter_heights[at + k])) }
}
}
}
return sum
}
# the photograph, tile after tile, each texel packed 0xRRGGBB into a word
@alloc_ok("a map being made: one tile's scratch, freed after")
function tt_write_ortho(render3d_st: Render3dState, f: pointer, n: int, side: int, sum0: int) -> int {
var sum = sum0
let buf = words(side * side)
let w = render3d_st.ter_ortho_w
let c = render3d_st.ter_ortho_c
let px = render3d_st.ter_ortho_px
for j in 0 .. n {
for i in 0 .. n {
for r in 0 .. side {
for k in 0 .. side {
let o = ((j * side + r) * w + i * side + k) * c
let v = (px[o] << 16) | (px[o + 1] << 8) | px[o + 2]
buf[r * side + k] = v
sum = tt_mix(sum, v)
}
}
file_write(f, data_of(buf), side * side * 4)
}
}
free(buf)
return sum
}
# the baked normals (RG16F: x and z, two halves a word), read back once and written tile after tile
@alloc_ok("a map being made: the normals read back once, freed after")
function tt_write_normals(render3d_st: mut Render3dState, f: pointer, n: int, sum0: int) -> int {
var sum = sum0
let all = words(TERRAIN_RES * TERRAIN_RES)
gpu_tex_bind(render3d_st, GPU_TEX2D, render3d_st.ter_normal_tex)
gpu_pixel_store(render3d_st, GL_PACK_ALIGNMENT, 4)
gpu_tex_read(render3d_st, GPU_TEX2D, GL_RG, GL_HALF_FLOAT, data_of(all))
let base = data_of(all)
for j in 0 .. n {
for i in 0 .. n {
for r in 0 .. TT_TEX {
let at = (j * TT_TEX + r) * TERRAIN_RES + i * TT_TEX
file_write(f, mem_off(base, at * 4), TT_TEX * 4)
for k in 0 .. TT_TEX { sum = tt_mix(sum, all[at + k]) }
}
}
}
free(all)
return sum
}
function tt_mix(h: int, v: int) -> int { return ((h ^ (v & 0xFFFFFFFF)) * 16777619) & 0xFFFFFFFF }
# the file opened for the tiles' reads, and the pools, the index and the clock made for it
@alloc_ok("a map being made: its tiles' file and their pools, once, not a frame")
function tt_open(render3d_st: mut Render3dState, path: string, n: int, side: int) -> void {
render3d_st.tt_file = file_open(path, "rb")
render3d_st.tt_n = n; render3d_st.tt_oside = side
render3d_st.tt_hoff = TT_HEAD
render3d_st.tt_ooff = TT_HEAD + n * n * TT_TEX * TT_TEX * 4
render3d_st.tt_noff = render3d_st.tt_ooff + n * n * side * side * 4
if render3d_st.tt_nm == null { render3d_st.tt_nm = words(TT_SLOTS * TT_TEX * TT_TEX) }
if render3d_st.tt_h == null { render3d_st.tt_h = floats(TT_SLOTS * TT_TEX * TT_TEX) }
if render3d_st.tt_o == null or len(render3d_st.tt_o) != TT_SLOTS * side * side {
if render3d_st.tt_o != null { free(render3d_st.tt_o) }
render3d_st.tt_o = words(TT_SLOTS * side * side)
}
if render3d_st.tt_slot_of != null { free(render3d_st.tt_slot_of) }
render3d_st.tt_slot_of = words(n * n)
for t in 0 .. n * n { render3d_st.tt_slot_of[t] = -1 }
if render3d_st.tt_tile_in == null { render3d_st.tt_tile_in = words(TT_SLOTS); render3d_st.tt_ref = words(TT_SLOTS) }
for s in 0 .. TT_SLOTS { render3d_st.tt_tile_in[s] = -1; render3d_st.tt_ref[s] = 0 }
render3d_st.tt_hand = 0
ltt2_gpu_coarse(render3d_st)
tt_say_cut(path, bytes, (gl_now_us() - t0) / 1000)
}
# the last map's file let go (a world swap, or the map made again)