Merge branch 'lang/foundations' into r3d/fog-wall
This commit is contained in:
commit
f8d811aa97
78 changed files with 43349 additions and 40047 deletions
75
packages/ludic.render3d/baked_tex.ludic
Normal file
75
packages/ludic.render3d/baked_tex.ludic
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# baked_tex.ludic - textures a game baked at build time (`ludic bake`, assets/baked/), read before the PNG
|
||||
# they came from: a boot decodes, pads and converts nothing it can take ready-made. The file is ludic.base's
|
||||
# baked form (a 32-byte "LBAK" header, then the payload), read by hand here since render3d uses no package.
|
||||
# Missing, or of another key or version, and the PNG is decoded as it always was.
|
||||
# assets/baked/png/<path under assets/>.tex a PNG's samples: u32 w, h, channels, depth, then them
|
||||
# assets/baked/cutouts/<path under assets/>.bc7 a cut-out atlas, padded as tex_dilate pads it: a .dds
|
||||
const BAKED_PNG_KEY: string = "png_sheets" # the Bakes row (Maroon Lake's bakes.lres) and the key in its header
|
||||
const BAKED_PNG_VERSION: int = 1
|
||||
const BAKED_CUT_KEY: string = "cutouts"
|
||||
const BAKED_CUT_VERSION: int = 1
|
||||
|
||||
function baked_u32(b: []byte, o: int) -> int { return b[o] | (b[o + 1] << 8) | (b[o + 2] << 16) | (b[o + 3] << 24) }
|
||||
|
||||
# where the bake of `path` (a PNG under assets/) lives, or "" for any other path
|
||||
@alloc_ok("a load: the baked file's name")
|
||||
function baked_tex_path(path: string, dir: string, ext: string) -> string {
|
||||
let n = len(path)
|
||||
if n < 12 or path[0 .. 7] != "assets/" or path[n - 4 .. n] != ".png" { return "" }
|
||||
return `assets/baked/{dir}/{path[7 .. n - 4]}{ext}`
|
||||
}
|
||||
|
||||
# the payload's offset in a baked file of this key and version, or -1 (not one, or another's)
|
||||
function baked_payload(b: []byte, key: string, version: int) -> int {
|
||||
if b == null or len(b) < 40 { return -1 }
|
||||
if b[0] != 76 or b[1] != 66 or b[2] != 65 or b[3] != 75 { return -1 } # "LBAK"
|
||||
if baked_u32(b, 4) != 1 or baked_u32(b, 8) != version { return -1 }
|
||||
let off = baked_u32(b, 12)
|
||||
if off < 32 + len(key) or off > len(b) { return -1 }
|
||||
for i in 0 .. len(key) { if b[32 + i] != key[i] { return -1 } }
|
||||
if b[32 + len(key)] != 0 { return -1 }
|
||||
return off
|
||||
}
|
||||
|
||||
# a PNG's samples as the bake left them (tex_w .. tex_depth set, the caller frees them), or null
|
||||
@alloc_ok("a load: the baked file read, its samples kept by the caller")
|
||||
function baked_png(render3d_st: mut Render3dState, path: pointer) -> pointer {
|
||||
let bp = baked_tex_path(path, "png", ".tex")
|
||||
if bp == "" or not Fs.exists(bp) { return null }
|
||||
let b = Fs.read_bytes(bp)
|
||||
let off = baked_payload(b, BAKED_PNG_KEY, BAKED_PNG_VERSION)
|
||||
if off < 0 or off + 16 > len(b) {
|
||||
if b != null { free(b) }
|
||||
print(`r3d: {bp} is not this build's bake; {path} is decoded`)
|
||||
return null
|
||||
}
|
||||
let w = baked_u32(b, off)
|
||||
let h = baked_u32(b, off + 4)
|
||||
let ch = baked_u32(b, off + 8)
|
||||
let depth = baked_u32(b, off + 12)
|
||||
let n = w * h * ch * (depth / 8)
|
||||
if w < 1 or h < 1 or ch < 1 or ch > 4 or (depth != 8 and depth != 16) or off + 16 + n > len(b) {
|
||||
free(b)
|
||||
return null
|
||||
}
|
||||
let out = bytes(n + 8)
|
||||
mem_copy(out, mem_off(data_of(b), off + 16), n)
|
||||
free(b)
|
||||
render3d_st.tex_w = w; render3d_st.tex_h = h; render3d_st.tex_channels = ch; render3d_st.tex_depth = depth
|
||||
return out
|
||||
}
|
||||
|
||||
# a cut-out atlas as the bake padded and compressed it (a texture id), or 0
|
||||
@alloc_ok("a load: the baked file read and let go")
|
||||
function baked_cutout(render3d_st: mut Render3dState, path: pointer, srgb: bool) -> int {
|
||||
if not render3d_st.gvk_has_bc { return 0 }
|
||||
let bp = baked_tex_path(path, "cutouts", ".bc7")
|
||||
if bp == "" or not Fs.exists(bp) { return 0 }
|
||||
let b = Fs.read_bytes(bp)
|
||||
let off = baked_payload(b, BAKED_CUT_KEY, BAKED_CUT_VERSION)
|
||||
var id = 0
|
||||
if off >= 0 { id = tex_load_dds_at(render3d_st, b, off, srgb) }
|
||||
if b != null { free(b) }
|
||||
if id == 0 { print(`r3d: {bp} is not this build's bake; {path} is padded here`) }
|
||||
return id
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import "prof.ludic"
|
|||
import "drawstats.ludic"
|
||||
import "programs.ludic"
|
||||
import "texture.ludic"
|
||||
import "baked_tex.ludic"
|
||||
import "mesh.ludic"
|
||||
import "gpu_vk_draw.ludic"
|
||||
import "camera.ludic"
|
||||
|
|
|
|||
|
|
@ -39,3 +39,8 @@ function png_decode_bytes(render3d_st: mut Render3dState, path: string, reuse: [
|
|||
function tex_upload_bytes(render3d_st: mut Render3dState, px: []byte, srgb: bool, mips: bool) -> int {
|
||||
return tex_upload(render3d_st, data_of(px), srgb, mips)
|
||||
}
|
||||
# cut-out edge padding over samples laid out as the last decode left them (tex_dilate)
|
||||
function tex_dilate_bytes(render3d_st: Render3dState, px: []byte, thresh: int, passes: int) -> void {
|
||||
if px == null or len(px) < render3d_st.tex_w * render3d_st.tex_h * render3d_st.tex_channels { return }
|
||||
tex_dilate(render3d_st, data_of(px), thresh, passes)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,6 +115,8 @@ function png_unfilter(raw: pointer, cur: int, prev: int, stride: int, fbpp: int,
|
|||
|
||||
@alloc_ok("loading a model, a texture or a font: a load, not a frame (a guest loading a teammate's look is one)")
|
||||
function png_decode(render3d_st: mut Render3dState, path: pointer) -> pointer {
|
||||
let bk = baked_png(render3d_st, path) # a build that baked it (baked_tex.ludic)
|
||||
if bk != null { return bk }
|
||||
let d = r3d_read_file(render3d_st, path)
|
||||
if d == null { print(`png: cannot read {path}`); return null }
|
||||
let size = render3d_st.tex_file_len
|
||||
|
|
@ -198,30 +200,51 @@ function tex_dilate(render3d_st: Render3dState, px: pointer, thresh: int, passes
|
|||
var i = 0
|
||||
while i < w * h { let o = i * c; if px[o] + px[o + 1] + px[o + 2] < thresh { mask[i] = 1 } else { mask[i] = 0 }; i += 1 }
|
||||
let next = bytes(w * h)
|
||||
let j = new DilateJob
|
||||
j.px = px; j.mask = mask; j.next = next; j.w = w; j.h = h; j.c = c
|
||||
let blocks = (h + TEX_DILATE_ROWS - 1) / TEX_DILATE_ROWS
|
||||
for pass in 0 .. passes {
|
||||
mem_copy(next, mask, w * h)
|
||||
var y = 0
|
||||
while y < h {
|
||||
var x = 0
|
||||
while x < w {
|
||||
let k = y * w + x
|
||||
if mask[k] == 1 {
|
||||
var r = 0; var g = 0; var b = 0; var n = 0
|
||||
if x > 0 and mask[k - 1] == 0 { let o = (k - 1) * c; r += px[o]; g += px[o + 1]; b += px[o + 2]; n += 1 }
|
||||
if x < w - 1 and mask[k + 1] == 0 { let o = (k + 1) * c; r += px[o]; g += px[o + 1]; b += px[o + 2]; n += 1 }
|
||||
if y > 0 and mask[k - w] == 0 { let o = (k - w) * c; r += px[o]; g += px[o + 1]; b += px[o + 2]; n += 1 }
|
||||
if y < h - 1 and mask[k + w] == 0 { let o = (k + w) * c; r += px[o]; g += px[o + 1]; b += px[o + 2]; n += 1 }
|
||||
if n > 0 { let o = k * c; px[o] = r / n; px[o + 1] = g / n; px[o + 2] = b / n; next[k] = 0 }
|
||||
}
|
||||
x += 1
|
||||
}
|
||||
y += 1
|
||||
}
|
||||
Job.parallel_for(blocks, fn tex_dilate_rows, j)
|
||||
mem_copy(mask, next, w * h)
|
||||
}
|
||||
free(mask); free(next)
|
||||
}
|
||||
|
||||
# A pass's rows on every core. Within a pass a row writes only its own still-masked texels and reads
|
||||
# only neighbours the mask already let go, which no row writes this pass - so rows are independent and
|
||||
# the result is the one row-by-row order gave. The worker is handed plain buffers and makes nothing.
|
||||
const TEX_DILATE_ROWS: int = 16
|
||||
property DilateJob {
|
||||
px: pointer = null
|
||||
mask: pointer = null
|
||||
next: pointer = null
|
||||
w: int = 0
|
||||
h: int = 0
|
||||
c: int = 0
|
||||
}
|
||||
function tex_dilate_rows(b: int, j: DilateJob) -> void {
|
||||
let w = j.w; let c = j.c; let px = j.px; let mask = j.mask; let next = j.next
|
||||
var y = b * TEX_DILATE_ROWS
|
||||
let y1 = min(j.h, y + TEX_DILATE_ROWS)
|
||||
while y < y1 {
|
||||
var x = 0
|
||||
while x < w {
|
||||
let k = y * w + x
|
||||
if mask[k] == 1 {
|
||||
var r = 0; var g = 0; var bl = 0; var n = 0
|
||||
if x > 0 and mask[k - 1] == 0 { let o = (k - 1) * c; r += px[o]; g += px[o + 1]; bl += px[o + 2]; n += 1 }
|
||||
if x < w - 1 and mask[k + 1] == 0 { let o = (k + 1) * c; r += px[o]; g += px[o + 1]; bl += px[o + 2]; n += 1 }
|
||||
if y > 0 and mask[k - w] == 0 { let o = (k - w) * c; r += px[o]; g += px[o + 1]; bl += px[o + 2]; n += 1 }
|
||||
if y < j.h - 1 and mask[k + w] == 0 { let o = (k + w) * c; r += px[o]; g += px[o + 1]; bl += px[o + 2]; n += 1 }
|
||||
if n > 0 { let o = k * c; px[o] = r / n; px[o + 1] = g / n; px[o + 2] = bl / n; next[k] = 0 }
|
||||
}
|
||||
x += 1
|
||||
}
|
||||
y += 1
|
||||
}
|
||||
}
|
||||
|
||||
# Upload the last-decoded samples as a 2D texture. srgb: colour data (8-bit only).
|
||||
function tex_upload(render3d_st: mut Render3dState, px: pointer, srgb: bool, mips: bool) -> int {
|
||||
let id = gpu_tex_new(render3d_st)
|
||||
|
|
@ -289,6 +312,13 @@ function tex_load_ex__t(render3d_st: mut Render3dState, path: pointer, srgb: boo
|
|||
return t
|
||||
}
|
||||
}
|
||||
if dilate > 0 {
|
||||
let cut = baked_cutout(render3d_st, path, srgb) # padded and compressed at build time
|
||||
if cut != 0 {
|
||||
tex_note_size(render3d_st, cut)
|
||||
return cut
|
||||
}
|
||||
}
|
||||
let px = png_decode(render3d_st, path)
|
||||
if px == null { return 0 }
|
||||
if dilate > 0 { tex_dilate(render3d_st, px, 60, dilate) }
|
||||
|
|
|
|||
|
|
@ -44,14 +44,16 @@ function tex_load_dds_bytes(render3d_st: mut Render3dState, b: []byte, srgb: boo
|
|||
render3d_st.gvk_tag = was
|
||||
return r
|
||||
}
|
||||
function tex_load_dds_bytes__t(render3d_st: mut Render3dState, b: []byte, srgb: bool) -> int {
|
||||
if len(b) < 148 or b[0] != 68 or b[1] != 68 or b[2] != 83 or b[3] != 32 { return 0 }
|
||||
if b[84] != 68 or b[85] != 88 or b[86] != 49 or b[87] != 48 { return 0 } # "DX10"
|
||||
let h = dds_u32(b, 12)
|
||||
let w = dds_u32(b, 16)
|
||||
var levels = dds_u32(b, 28)
|
||||
function tex_load_dds_bytes__t(render3d_st: mut Render3dState, b: []byte, srgb: bool) -> int { return tex_load_dds_at(render3d_st, b, 0, srgb) }
|
||||
# a .dds that starts `at` bytes into b (a baked file's payload, baked_tex.ludic)
|
||||
function tex_load_dds_at(render3d_st: mut Render3dState, b: []byte, at0: int, srgb: bool) -> int {
|
||||
if len(b) < at0 + 148 or b[at0] != 68 or b[at0 + 1] != 68 or b[at0 + 2] != 83 or b[at0 + 3] != 32 { return 0 }
|
||||
if b[at0 + 84] != 68 or b[at0 + 85] != 88 or b[at0 + 86] != 49 or b[at0 + 87] != 48 { return 0 } # "DX10"
|
||||
let h = dds_u32(b, at0 + 12)
|
||||
let w = dds_u32(b, at0 + 16)
|
||||
var levels = dds_u32(b, at0 + 28)
|
||||
if levels < 1 { levels = 1 }
|
||||
let dxgi = dds_u32(b, 128)
|
||||
let dxgi = dds_u32(b, at0 + 128)
|
||||
var ifmt = 0
|
||||
var block = 16
|
||||
if dxgi == DDS_BC7 or dxgi == DDS_BC7_SRGB { ifmt = R3D_BC7; if srgb { ifmt = R3D_BC7_SRGB } }
|
||||
|
|
@ -65,13 +67,13 @@ function tex_load_dds_bytes__t(render3d_st: mut Render3dState, b: []byte, srgb:
|
|||
at += max((max(w >> l, 1) + 3) / 4, 1) * max((max(h >> l, 1) + 3) / 4, 1) * block
|
||||
}
|
||||
offs[levels] = at
|
||||
if 148 + at > len(b) {
|
||||
if at0 + 148 + at > len(b) {
|
||||
free(offs)
|
||||
return 0
|
||||
}
|
||||
let id = gpu_tex_new(render3d_st)
|
||||
gpu_tex_bind(render3d_st, GPU_TEX2D, id)
|
||||
let ok = gpu_tex_compressed(render3d_st, ifmt, w, h, levels, mem_off(data_of(b), 148), offs)
|
||||
let ok = gpu_tex_compressed(render3d_st, ifmt, w, h, levels, mem_off(data_of(b), at0 + 148), offs)
|
||||
free(offs)
|
||||
if not ok {
|
||||
gpu_tex_free(render3d_st, id)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue