Merge commit 'a12f1b1' into lang/foundations
This commit is contained in:
commit
7f3b842d59
8 changed files with 397 additions and 149 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
181
packages/ludic.render3d/terrain_ltt2_read.ludic
Normal file
181
packages/ludic.render3d/terrain_ltt2_read.ludic
Normal 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
|
||||
}
|
||||
199
packages/ludic.render3d/terrain_ltt2_write.ludic
Normal file
199
packages/ludic.render3d/terrain_ltt2_write.ludic
Normal 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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue