diff --git a/packages/ludic.render3d/env.ludic b/packages/ludic.render3d/env.ludic index 7d4ed7c8..4d4dde90 100644 --- a/packages/ludic.render3d/env.ludic +++ b/packages/ludic.render3d/env.ludic @@ -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 diff --git a/packages/ludic.render3d/r3d.ludic b/packages/ludic.render3d/r3d.ludic index 0c97e623..19ddcab7 100644 --- a/packages/ludic.render3d/r3d.ludic +++ b/packages/ludic.render3d/r3d.ludic @@ -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" diff --git a/packages/ludic.render3d/terrain.ludic b/packages/ludic.render3d/terrain.ludic index dd509379..264ea1ad 100644 --- a/packages/ludic.render3d/terrain.ludic +++ b/packages/ludic.render3d/terrain.ludic @@ -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) } diff --git a/packages/ludic.render3d/terrain_ltt2_read.ludic b/packages/ludic.render3d/terrain_ltt2_read.ludic new file mode 100644 index 00000000..131074ac --- /dev/null +++ b/packages/ludic.render3d/terrain_ltt2_read.ludic @@ -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 +} diff --git a/packages/ludic.render3d/terrain_ltt2_write.ludic b/packages/ludic.render3d/terrain_ltt2_write.ludic new file mode 100644 index 00000000..da418855 --- /dev/null +++ b/packages/ludic.render3d/terrain_ltt2_write.ludic @@ -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 +} diff --git a/packages/ludic.render3d/terrain_pages_coarse.ludic b/packages/ludic.render3d/terrain_pages_coarse.ludic deleted file mode 100644 index 4f084272..00000000 --- a/packages/ludic.render3d/terrain_pages_coarse.ludic +++ /dev/null @@ -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) -} diff --git a/packages/ludic.render3d/terrain_tiles.ludic b/packages/ludic.render3d/terrain_tiles.ludic index dc65f521..04804ff0 100644 --- a/packages/ludic.render3d/terrain_tiles.ludic +++ b/packages/ludic.render3d/terrain_tiles.ludic @@ -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 diff --git a/packages/ludic.render3d/terrain_tiles_cut.ludic b/packages/ludic.render3d/terrain_tiles_cut.ludic index 89c6f8df..36899fbe 100644 --- a/packages/ludic.render3d/terrain_tiles_cut.ludic +++ b/packages/ludic.render3d/terrain_tiles_cut.ludic @@ -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)