impostor_from_baked / impostor_refill read a bundle of two DX10 .dds (albedo, normal: BC7_UNORM, every level) through tex_load_dds_at and gpu_tex_compressed - no gpu_tex_mips - and check each is the impostor's size; sampled as a painted atlas is (clamped, no anisotropy). No BC on the GPU, or a bake that does not fit, and the caller paints RGBA8 and makes mips as before; the fog re-open reads the BC7 file again. The handles and every draw are unchanged. Compile-only: nothing run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
139 lines
6.8 KiB
Text
139 lines
6.8 KiB
Text
# bake_load.ludic — the game names where a bake of the renderer's work lives, and the renderer takes the
|
|
# textures from it when it is current and makes them as it always has when it is not: an impostor's
|
|
# atlases (and again when a fog opens past its cards), the sky's image-based light, the grass carpet.
|
|
|
|
# ---- a bundle's parts, for whoever writes a preview of one (bake_pack's layout) ----
|
|
function bake_part_count(data: []byte) -> int {
|
|
if data == null or len(data) < 4 { return 0 }
|
|
return Vk.get_i32(data_of(data), 0)
|
|
}
|
|
function bake_part_len(data: []byte, i: int) -> int { return Vk.get_i32(data_of(data), 4 + i * 4) }
|
|
# where part i's bytes begin in the bundle
|
|
function bake_part_off(data: []byte, i: int) -> int {
|
|
var at = 4 + bake_part_count(data) * 4
|
|
for k in 0 .. i { at += bake_part_len(data, k) }
|
|
return at
|
|
}
|
|
|
|
# ---- an impostor ----
|
|
# where an impostor's bake lives (the game's name for it), whether or not its atlases came from it
|
|
function impostor_source(im: Impostor, path: string, key: string, version: int) -> void {
|
|
im.src_path = intern(path)
|
|
im.src_key = intern(key)
|
|
im.src_version = version
|
|
}
|
|
|
|
# an impostor of this shape from its bake - both atlases BC7 with their mips, as tools/textures/bake_impostors.py
|
|
# makes them - or null (missing, stale, not this shape, or a GPU without BC) for the caller to paint
|
|
@alloc_ok("a bake read once at a map load")
|
|
function impostor_from_baked(render3d_st: mut Render3dState, model: Model, tiles: int, tw: int, th: int, path: string, key: string, version: int) -> Impostor {
|
|
let im = new Impostor
|
|
im.tiles = tiles; im.radius = model.radius * 1.02; im.height = model.height
|
|
im.model = model; im.tw = tw; im.th = th; im.flower = render3d_st.sc_bake_flower
|
|
impostor_source(im, path, key, version)
|
|
if not impostor_refill(render3d_st, im) { return null }
|
|
im.src_ok = true
|
|
return im
|
|
}
|
|
# the atlases made from the bake (at a load, and again when a fog opens past the cards); false, and none
|
|
# kept, when it cannot be
|
|
@alloc_ok("a bake read once at a load or when a fog opens")
|
|
function impostor_refill(render3d_st: mut Render3dState, im: Impostor) -> bool {
|
|
if not render3d_st.gvk_has_bc { return false }
|
|
let b = r3d_baked_read(im.src_path, im.src_key, im.src_version)
|
|
if b == null { return false }
|
|
let ok = impostor_fill_bc7(render3d_st, im, b)
|
|
free(b)
|
|
return ok
|
|
}
|
|
# the two atlases from a bundle of two BC7 .dds (albedo, normal) whose size is the impostor's
|
|
function impostor_fill_bc7(render3d_st: mut Render3dState, im: Impostor, data: []byte) -> bool {
|
|
let was = render3d_st.gvk_tag
|
|
render3d_st.gvk_tag = VKM_IMPOSTOR
|
|
im.albedo = 0; im.normal = 0
|
|
if bake_part_count(data) == 2 {
|
|
im.albedo = impostor_bc7_part(render3d_st, im, data, 0)
|
|
im.normal = impostor_bc7_part(render3d_st, im, data, 1)
|
|
}
|
|
let ok = im.albedo != 0 and im.normal != 0
|
|
if not ok {
|
|
gpu_tex_free(render3d_st, im.albedo); gpu_tex_free(render3d_st, im.normal)
|
|
im.albedo = 0; im.normal = 0
|
|
}
|
|
render3d_st.gvk_tag = was
|
|
return ok
|
|
}
|
|
# one atlas: its baked levels uploaded as they are (no gpu_tex_mips), sampled as a painted atlas is
|
|
function impostor_bc7_part(render3d_st: mut Render3dState, im: Impostor, data: []byte, part: int) -> int {
|
|
let at = bake_part_off(data, part)
|
|
if at + bake_part_len(data, part) > len(data) { return 0 }
|
|
let id = tex_load_dds_at(render3d_st, data, at, false)
|
|
if id == 0 { return 0 }
|
|
if render3d_st.tex_w != im.tiles * im.tw or render3d_st.tex_h != im.th {
|
|
gpu_tex_free(render3d_st, id)
|
|
return 0
|
|
}
|
|
# tex_load_dds_at repeats and filters anisotropically; a painted atlas (tex_target) is clamped, with neither
|
|
gpu_tex_param(render3d_st, GPU_TEX2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
|
|
gpu_tex_param(render3d_st, GPU_TEX2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
|
|
gpu_tex_paramf(render3d_st, GPU_TEX2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fixed(1.0)) # 1.0 is off, as a painted atlas has it
|
|
return id
|
|
}
|
|
|
|
# ---- the sky's image-based light ----
|
|
# the directory the sky's bakes are in, under the key and version they were baked as: before r3d_init.
|
|
# A bake is one HDRI at the start yaw at one prefilter width; any other turn of the sky is convolved
|
|
function sky_baked_in(render3d_st: mut Render3dState, dir: string, key: string, version: int) -> void {
|
|
render3d_st.sky_bake_dir = intern(dir)
|
|
render3d_st.sky_bake_key = intern(key)
|
|
render3d_st.sky_bake_version = version
|
|
}
|
|
# the HDRI's name without its directory or ".hdr": what the sky's bakes are named by
|
|
@alloc_ok("a bake's name, made at a load")
|
|
function sky_hdri_name(render3d_st: Render3dState) -> string {
|
|
let p = render3d_st.r3d_sky_path
|
|
if p == null { return SKY_DEFAULT_HDRI }
|
|
var from = 0
|
|
for i in 0 .. len(p) { if p[i] == 47 { from = i + 1 } }
|
|
var to = len(p)
|
|
if to - from > 4 and p[to - 4] == 46 { to -= 4 }
|
|
return p[from .. to]
|
|
}
|
|
# <dir>/<hdri>_y<start yaw in whole degrees>_w<prefilter width>.bin, the file one bake is
|
|
@alloc_ok("a bake's name, made at a load")
|
|
function sky_bake_file(render3d_st: Render3dState, w: int) -> string {
|
|
let deg = int(Math.round(Math.rad_to_deg(render3d_st.sky_start_yaw)))
|
|
return `{render3d_st.sky_bake_dir}/{sky_hdri_name(render3d_st)}_y{deg}_w{w}.bin`
|
|
}
|
|
# the IBL set from its bake when the sky is at the start yaw and a bake of this width is current
|
|
@alloc_ok("a bake read once at a load or a new light quality")
|
|
function sky_ibl_try_baked(render3d_st: mut Render3dState) -> bool {
|
|
if len(render3d_st.sky_bake_dir) == 0 or render3d_st.sky_yaw != render3d_st.sky_start_yaw { return false }
|
|
let path = sky_bake_file(render3d_st, render3d_st.sky_prefilter_w)
|
|
let b = r3d_baked_read(path, render3d_st.sky_bake_key, render3d_st.sky_bake_version)
|
|
var ok = false
|
|
if b != null { ok = sky_ibl_from_bytes(render3d_st, b) }
|
|
if b != null { free(b) }
|
|
if not ok and not render3d_st.sky_bake_said { sky_say_missing(path) }
|
|
if not ok { render3d_st.sky_bake_said = true }
|
|
return ok
|
|
}
|
|
function sky_say_missing(path: string) -> void { print(`bake: {path} missing or stale - the sky's light made at run time (ludic bake)`) }
|
|
|
|
# ---- the grass carpet ----
|
|
function carpet_bytes(render3d_st: mut Render3dState, tex: int) -> []byte { return bake_pack(render3d_st, bake_list1(tex)) }
|
|
# the carpet texture (RGBA8, res square) from its bake, finished as carpet_bake finishes it; 0 when missing
|
|
@alloc_ok("a bake read once at a map load")
|
|
function carpet_from_baked(render3d_st: mut Render3dState, path: string, key: string, version: int, res: int) -> int {
|
|
let b = r3d_baked_read(path, key, version)
|
|
if b == null { return 0 }
|
|
let was = render3d_st.gvk_tag
|
|
render3d_st.gvk_tag = VKM_SCATTER
|
|
let tex = tex_target(render3d_st, res, res, GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, GL_LINEAR)
|
|
let ok = bake_unpack(render3d_st, b, bake_list1(tex))
|
|
free(b)
|
|
if ok { carpet_finish(render3d_st, tex) } else { gpu_tex_free(render3d_st, tex) }
|
|
render3d_st.gvk_tag = was
|
|
if ok { return tex }
|
|
return 0
|
|
}
|