render3d: heightgen's plain and SMOOTH variants compiled (61 programs) - a smoothed or noise-only terrain had no height field on Vulkan; run-time defines as pure functions (program_defs.ludic) and manifest_test holds every program the source can ask for, literal or computed, to the manifest
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a8ccd34559
commit
9f07fc8a62
12 changed files with 254 additions and 11 deletions
9
changes/r3d-variants-all.md
Normal file
9
changes/r3d-variants-all.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
bump: patch
|
||||
type: fix
|
||||
**Every program render3d can ask for is compiled.** heightgen with no survey and with SMOOTH were never in
|
||||
variants.list, so a smoothed or noise-only terrain (examples/rendering/chunks, smooth, reload) had no
|
||||
height field on Vulkan - silently until render3d's faults were made loud. Both are compiled now (61
|
||||
programs). A program's run-time defines are pure functions in program_defs.ludic (the height field, the
|
||||
ground's tiers, the blades), and tests/manifest_test reads every r3d_program / gpu_program / ac_prog_new call
|
||||
in render3d's source, holds each literal one and every value of each computed one to the shipped manifest,
|
||||
and fails a new computed call site until it is given its function.
|
||||
|
|
@ -96,9 +96,8 @@ function grass_init(render3d_st: mut Render3dState) -> void {
|
|||
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
|
||||
function grass_init__t(render3d_st: mut Render3dState) -> void {
|
||||
render3d_st.grass_merge = gpu_has_mdi(render3d_st) and not r3d_env_has(render3d_st, "R3D_GRASS_TILES")
|
||||
var defs = "#define FOLIAGE\n#define BLADE\n#define GBLADE\n"
|
||||
let defs = grass_defs(render3d_st.grass_merge)
|
||||
if render3d_st.grass_merge {
|
||||
defs = defs + "#define TILES\n"
|
||||
render3d_st.grass_rec = words(GRASS_RECS * 5); render3d_st.grass_tv = floats(GRASS_RECS * 4); render3d_st.grass_chunk_tv = floats(GRASS_CHUNK * 4)
|
||||
render3d_st.grass_band_start = words(4); render3d_st.grass_band_cells = words(4)
|
||||
render3d_st.grass_band_mesh = new []Mesh
|
||||
|
|
|
|||
25
packages/ludic.render3d/program_defs.ludic
Normal file
25
packages/ludic.render3d/program_defs.ludic
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# program_defs.ludic - the defines of every program whose defines are decided at run time, each a pure
|
||||
# function of what decides it, so tests/manifest_test can ask for every one a run could and hold the
|
||||
# SPIR-V manifest to them. A new program made with computed defines gets its function here, and a line there.
|
||||
|
||||
# the height field's generator: from a survey (DEM), smoothed (SMOOTH wins), or noise alone
|
||||
function ter_heightgen_defs(dem: bool, smooth: bool) -> string {
|
||||
if smooth { return "#define SMOOTH\n" }
|
||||
if dem { return "#define DEM\n" }
|
||||
return ""
|
||||
}
|
||||
# the ground's program: `debug` the dev switches' defines ("" in every shipped run), the cascades read
|
||||
# inside it on Vulkan (SUN_INLINE; R3D_SUN_PASS keeps a pass), tier 0 both, 1 far only, 2 near only
|
||||
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
|
||||
function ter_prog_defs(debug: string, sun_inline: bool, tier: int) -> string {
|
||||
var d = debug
|
||||
if sun_inline { d = d + "#define SUN_INLINE\n" }
|
||||
if tier == 1 { d = d + "#define FAR_ONLY\n" }
|
||||
if tier == 2 { d = d + "#define NEAR_ONLY\n" }
|
||||
return d
|
||||
}
|
||||
# the blades: merged into tiles where the device draws indirect (TILES), else a draw a chunk
|
||||
function grass_defs(merge: bool) -> string {
|
||||
if merge { return "#define FOLIAGE\n#define BLADE\n#define GBLADE\n#define TILES\n" }
|
||||
return "#define FOLIAGE\n#define BLADE\n#define GBLADE\n"
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ import "prof.ludic"
|
|||
import "drawstats.ludic"
|
||||
import "r3d_root.ludic"
|
||||
import "programs.ludic"
|
||||
import "program_defs.ludic"
|
||||
import "texture.ludic"
|
||||
import "baked_tex.ludic"
|
||||
import "mesh.ludic"
|
||||
|
|
|
|||
BIN
packages/ludic.render3d/shaders/spv/5b8c3ae8.frag.spv
Normal file
BIN
packages/ludic.render3d/shaders/spv/5b8c3ae8.frag.spv
Normal file
Binary file not shown.
BIN
packages/ludic.render3d/shaders/spv/5b8c3ae8.vert.spv
Normal file
BIN
packages/ludic.render3d/shaders/spv/5b8c3ae8.vert.spv
Normal file
Binary file not shown.
BIN
packages/ludic.render3d/shaders/spv/c60a9a25.frag.spv
Normal file
BIN
packages/ludic.render3d/shaders/spv/c60a9a25.frag.spv
Normal file
Binary file not shown.
BIN
packages/ludic.render3d/shaders/spv/c60a9a25.vert.spv
Normal file
BIN
packages/ludic.render3d/shaders/spv/c60a9a25.vert.spv
Normal file
Binary file not shown.
|
|
@ -199,6 +199,53 @@ T 5161e6ba u_src 7
|
|||
T 5161e6ba u_tershadow 8
|
||||
T 5161e6ba u_tershadow_aux 9
|
||||
T 5161e6ba u_ts_height 10
|
||||
P 5b8c3ae8 fullscreen.vert heightgen.frag
|
||||
B 5b8c3ae8 frag 1 836
|
||||
U 5b8c3ae8 frag u_cascade_vp 0 mat4 5 64
|
||||
U 5b8c3ae8 frag u_cascade_split 320 float 5 16
|
||||
U 5b8c3ae8 frag u_cascade_range 400 float 5 16
|
||||
U 5b8c3ae8 frag u_cascade_texel 480 float 5 16
|
||||
U 5b8c3ae8 frag u_sun_dir 560 vec3 1 0
|
||||
U 5b8c3ae8 frag u_sun_color 576 vec3 1 0
|
||||
U 5b8c3ae8 frag u_cam_pos 592 vec3 1 0
|
||||
U 5b8c3ae8 frag u_prefilter_levels 604 float 1 0
|
||||
U 5b8c3ae8 frag u_fog_density 608 float 1 0
|
||||
U 5b8c3ae8 frag u_fog_height_falloff 612 float 1 0
|
||||
U 5b8c3ae8 frag u_fog_base 616 float 1 0
|
||||
U 5b8c3ae8 frag u_clip_y 620 float 1 0
|
||||
U 5b8c3ae8 frag u_spec_scale 624 float 1 0
|
||||
U 5b8c3ae8 frag u_sky_rot 632 vec2 1 0
|
||||
U 5b8c3ae8 frag u_ground_alb 640 vec3 1 0
|
||||
U 5b8c3ae8 frag u_ground_alb_hi 656 vec3 1 0
|
||||
U 5b8c3ae8 frag u_ground_hi_y 668 float 1 0
|
||||
U 5b8c3ae8 frag u_ground_hi_w 672 float 1 0
|
||||
U 5b8c3ae8 frag u_ibl_scale 688 vec3 1 0
|
||||
U 5b8c3ae8 frag u_daylight 700 float 1 0
|
||||
U 5b8c3ae8 frag u_fire_pos 704 vec3 1 0
|
||||
U 5b8c3ae8 frag u_fire_color 720 vec3 1 0
|
||||
U 5b8c3ae8 frag u_hand_pos 736 vec3 1 0
|
||||
U 5b8c3ae8 frag u_hand_color 752 vec3 1 0
|
||||
U 5b8c3ae8 frag u_hand_dir 768 vec3 1 0
|
||||
U 5b8c3ae8 frag u_hand_cone 780 float 1 0
|
||||
U 5b8c3ae8 frag u_hand_reach 784 float 1 0
|
||||
U 5b8c3ae8 frag u_ts_origin 792 vec2 1 0
|
||||
U 5b8c3ae8 frag u_ts_half 800 float 1 0
|
||||
U 5b8c3ae8 frag u_ts_on 804 float 1 0
|
||||
U 5b8c3ae8 frag u_force_cascade 808 int 1 0
|
||||
U 5b8c3ae8 frag u_cloud_shadow 812 float 1 0
|
||||
U 5b8c3ae8 frag u_time 816 float 1 0
|
||||
U 5b8c3ae8 frag u_fog_inscatter 820 float 1 0
|
||||
U 5b8c3ae8 frag u_fog_desat 824 float 1 0
|
||||
U 5b8c3ae8 frag u_fog_wall 828 float 1 0
|
||||
U 5b8c3ae8 frag u_half 832 float 1 0
|
||||
T 5b8c3ae8 u_brdf 2
|
||||
T 5b8c3ae8 u_dem 3
|
||||
T 5b8c3ae8 u_irradiance 4
|
||||
T 5b8c3ae8 u_prefilter 5
|
||||
T 5b8c3ae8 u_shadow 6
|
||||
T 5b8c3ae8 u_tershadow 7
|
||||
T 5b8c3ae8 u_tershadow_aux 8
|
||||
T 5b8c3ae8 u_ts_height 9
|
||||
P 4be7acd9 fullscreen.vert heightgen.frag #define DEM;
|
||||
B 4be7acd9 frag 1 920
|
||||
U 4be7acd9 frag u_cascade_vp 0 mat4 5 64
|
||||
|
|
@ -257,6 +304,53 @@ T 4be7acd9 u_shadow 6
|
|||
T 4be7acd9 u_tershadow 7
|
||||
T 4be7acd9 u_tershadow_aux 8
|
||||
T 4be7acd9 u_ts_height 9
|
||||
P c60a9a25 fullscreen.vert heightgen.frag #define SMOOTH;
|
||||
B c60a9a25 frag 1 836
|
||||
U c60a9a25 frag u_cascade_vp 0 mat4 5 64
|
||||
U c60a9a25 frag u_cascade_split 320 float 5 16
|
||||
U c60a9a25 frag u_cascade_range 400 float 5 16
|
||||
U c60a9a25 frag u_cascade_texel 480 float 5 16
|
||||
U c60a9a25 frag u_sun_dir 560 vec3 1 0
|
||||
U c60a9a25 frag u_sun_color 576 vec3 1 0
|
||||
U c60a9a25 frag u_cam_pos 592 vec3 1 0
|
||||
U c60a9a25 frag u_prefilter_levels 604 float 1 0
|
||||
U c60a9a25 frag u_fog_density 608 float 1 0
|
||||
U c60a9a25 frag u_fog_height_falloff 612 float 1 0
|
||||
U c60a9a25 frag u_fog_base 616 float 1 0
|
||||
U c60a9a25 frag u_clip_y 620 float 1 0
|
||||
U c60a9a25 frag u_spec_scale 624 float 1 0
|
||||
U c60a9a25 frag u_sky_rot 632 vec2 1 0
|
||||
U c60a9a25 frag u_ground_alb 640 vec3 1 0
|
||||
U c60a9a25 frag u_ground_alb_hi 656 vec3 1 0
|
||||
U c60a9a25 frag u_ground_hi_y 668 float 1 0
|
||||
U c60a9a25 frag u_ground_hi_w 672 float 1 0
|
||||
U c60a9a25 frag u_ibl_scale 688 vec3 1 0
|
||||
U c60a9a25 frag u_daylight 700 float 1 0
|
||||
U c60a9a25 frag u_fire_pos 704 vec3 1 0
|
||||
U c60a9a25 frag u_fire_color 720 vec3 1 0
|
||||
U c60a9a25 frag u_hand_pos 736 vec3 1 0
|
||||
U c60a9a25 frag u_hand_color 752 vec3 1 0
|
||||
U c60a9a25 frag u_hand_dir 768 vec3 1 0
|
||||
U c60a9a25 frag u_hand_cone 780 float 1 0
|
||||
U c60a9a25 frag u_hand_reach 784 float 1 0
|
||||
U c60a9a25 frag u_ts_origin 792 vec2 1 0
|
||||
U c60a9a25 frag u_ts_half 800 float 1 0
|
||||
U c60a9a25 frag u_ts_on 804 float 1 0
|
||||
U c60a9a25 frag u_force_cascade 808 int 1 0
|
||||
U c60a9a25 frag u_cloud_shadow 812 float 1 0
|
||||
U c60a9a25 frag u_time 816 float 1 0
|
||||
U c60a9a25 frag u_fog_inscatter 820 float 1 0
|
||||
U c60a9a25 frag u_fog_desat 824 float 1 0
|
||||
U c60a9a25 frag u_fog_wall 828 float 1 0
|
||||
U c60a9a25 frag u_half 832 float 1 0
|
||||
T c60a9a25 u_brdf 2
|
||||
T c60a9a25 u_dem 3
|
||||
T c60a9a25 u_irradiance 4
|
||||
T c60a9a25 u_prefilter 5
|
||||
T c60a9a25 u_shadow 6
|
||||
T c60a9a25 u_tershadow 7
|
||||
T c60a9a25 u_tershadow_aux 8
|
||||
T c60a9a25 u_ts_height 9
|
||||
P 858f5089 fullscreen.vert ibl_brdf.frag
|
||||
B 858f5089 frag 0 832
|
||||
U 858f5089 frag u_cascade_vp 0 mat4 5 64
|
||||
|
|
@ -3995,4 +4089,4 @@ T b20fdcb5 u_shadow 9
|
|||
T b20fdcb5 u_tershadow 10
|
||||
T b20fdcb5 u_tershadow_aux 11
|
||||
T b20fdcb5 u_ts_height 12
|
||||
E 59
|
||||
E 61
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ fullscreen.vert|adapt.frag|
|
|||
fullscreen.vert|bloom_down.frag|
|
||||
fullscreen.vert|bloom_up.frag|
|
||||
fullscreen.vert|dof.frag|
|
||||
fullscreen.vert|heightgen.frag|
|
||||
fullscreen.vert|heightgen.frag|#define DEM;
|
||||
fullscreen.vert|heightgen.frag|#define SMOOTH;
|
||||
fullscreen.vert|ibl_brdf.frag|
|
||||
fullscreen.vert|ibl_irradiance.frag|
|
||||
fullscreen.vert|ibl_prefilter.frag|
|
||||
|
|
|
|||
|
|
@ -191,10 +191,7 @@ function terrain_generate(render3d_st: mut Render3dState) -> void {
|
|||
render3d_st.gvk_tag = was
|
||||
}
|
||||
function terrain_generate__t(render3d_st: mut Render3dState) -> void {
|
||||
var defs = ""
|
||||
if render3d_st.ter_dem_tex != 0 { defs = "#define DEM\n" }
|
||||
if render3d_st.ter_smooth { defs = "#define SMOOTH\n" }
|
||||
let p = r3d_program(render3d_st, "fullscreen.vert", "heightgen.frag", defs)
|
||||
let p = r3d_program(render3d_st, "fullscreen.vert", "heightgen.frag", ter_heightgen_defs(render3d_st.ter_dem_tex != 0, render3d_st.ter_smooth))
|
||||
# a map generated over another: its 256 MB height texture goes first
|
||||
if render3d_st.ter_height_tex != 0 { gpu_tex_free(render3d_st, render3d_st.ter_height_tex); render3d_st.ter_height_tex = 0 }
|
||||
render3d_st.ter_height_tex = tex_target(render3d_st, TERRAIN_RES, TERRAIN_RES, GL_R32F, GL_RED, GL_FLOAT, GL_LINEAR)
|
||||
|
|
@ -453,10 +450,9 @@ function terrain_init_finish__t(render3d_st: mut Render3dState) -> void {
|
|||
# (terrain.frag, SUN_INLINE): one rasterisation of the patches instead of two, in the frame and in
|
||||
# the reflection. R3D_SUN_PASS=1 keeps the pass there too, for comparing.
|
||||
render3d_st.ter_sun_inline = render3d_st.gpu_kind == GPU_VK and not r3d_env_has(render3d_st, "R3D_SUN_PASS")
|
||||
if render3d_st.ter_sun_inline { defs = defs + "#define SUN_INLINE\n" }
|
||||
render3d_st.ter_prog = r3d_program(render3d_st, "terrain.vert", "terrain.frag", defs)
|
||||
render3d_st.ter_prog_far = r3d_program(render3d_st, "terrain.vert", "terrain.frag", defs + "#define FAR_ONLY\n")
|
||||
render3d_st.ter_prog_near = r3d_program(render3d_st, "terrain.vert", "terrain.frag", defs + "#define NEAR_ONLY\n")
|
||||
render3d_st.ter_prog = r3d_program(render3d_st, "terrain.vert", "terrain.frag", ter_prog_defs(defs, render3d_st.ter_sun_inline, 0))
|
||||
render3d_st.ter_prog_far = r3d_program(render3d_st, "terrain.vert", "terrain.frag", ter_prog_defs(defs, render3d_st.ter_sun_inline, 1))
|
||||
render3d_st.ter_prog_near = r3d_program(render3d_st, "terrain.vert", "terrain.frag", ter_prog_defs(defs, render3d_st.ter_sun_inline, 2))
|
||||
render3d_st.ter_sun_prog = r3d_program(render3d_st, "terrain.vert", "tersun.frag", "")
|
||||
render3d_st.ter_far_split = 200.0
|
||||
if r3d_env_has(render3d_st, "R3D_TFAR") { render3d_st.ter_far_split = float(Text.to_int(r3d_env(render3d_st, "R3D_TFAR"))) }
|
||||
|
|
|
|||
|
|
@ -64,4 +64,121 @@ program ManifestTest {
|
|||
expect_eq(r3d_tilde("~other/x", "/Users/me"), "~other/x")
|
||||
expect_eq(r3d_tilde("~/x", null), "~/x")
|
||||
}
|
||||
|
||||
# ---- every program render3d can ask for, not only what variants.list names ----
|
||||
# A request is r3d_program / gpu_program / ac_prog_new(state, vertex, fragment, defines). With all three
|
||||
# literal it is read straight out of the source; with computed defines its site is one of KNOWN, whose
|
||||
# defines are program_defs.ludic's functions, asked below for every value. A new computed site fails
|
||||
# here by name until it is given a function there and a line in `computed_all`.
|
||||
function shipped() -> []GpuVariant { return gpu_manifest_parse(Fs.read_text("packages/ludic.render3d/shaders/spv/manifest.txt")) }
|
||||
function known_computed(file: string) -> int {
|
||||
if file == "programs.ludic" { return 1 } # r3d_program itself, handing on to gpu_program
|
||||
if file == "actor.ludic" { return 1 } # ac_prog_new's own call: its callers are literal
|
||||
if file == "terrain.ludic" { return 4 } # heightgen and the ground's three tiers
|
||||
if file == "grass.ludic" { return 1 } # the blades
|
||||
return 0
|
||||
}
|
||||
# the defines as the manifest keys them: the source's "\n" escapes as ';'
|
||||
function key_defs(lit: string) -> string {
|
||||
var out = ""
|
||||
var i = 0
|
||||
while i < len(lit) {
|
||||
if lit[i] == 92 and i + 1 < len(lit) and lit[i + 1] == 110 {
|
||||
out = out + ";"
|
||||
i += 2
|
||||
} else {
|
||||
out = out + lit[i .. i + 1]
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
function is_lit(a: string) -> bool { return len(a) >= 2 and a[0] == 34 and a[len(a) - 1] == 34 }
|
||||
function unq(a: string) -> string { return a[1 .. len(a) - 1] }
|
||||
# the top-level arguments of the call whose '(' is at `open`, trimmed
|
||||
function args_at(src: string, open: int) -> []string {
|
||||
let out = new []string
|
||||
var depth = 0
|
||||
var inq = false
|
||||
var start = open + 1
|
||||
var i = open + 1
|
||||
while i < len(src) {
|
||||
let c = src[i]
|
||||
if c == 34 { inq = not inq }
|
||||
if not inq {
|
||||
if c == 40 { depth += 1 }
|
||||
if c == 41 and depth == 0 {
|
||||
push(out, Text.trim(src[start .. i]))
|
||||
return out
|
||||
}
|
||||
if c == 41 { depth -= 1 }
|
||||
if c == 44 and depth == 0 {
|
||||
push(out, Text.trim(src[start .. i]))
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
# checks every request in one file against the manifest; returns how many had computed arguments
|
||||
function file_requests(vs: []GpuVariant, file: string, missing: []string) -> int {
|
||||
let src = Fs.read_text(`packages/ludic.render3d/{file}`)
|
||||
var computed = 0
|
||||
let names = ["r3d_program(", "gpu_program(", "ac_prog_new("]
|
||||
for n in 0 .. len(names) {
|
||||
let parts = Text.split(src, names[n])
|
||||
var at = 0
|
||||
for k in 0 .. len(parts) - 1 {
|
||||
at += len(parts[k])
|
||||
let before = parts[k]
|
||||
let fn_decl = Text.ends_with(before, "function ")
|
||||
let ident = len(before) > 0 and (char_word(before[len(before) - 1]))
|
||||
if not fn_decl and not ident {
|
||||
let a = args_at(src, at + len(names[n]) - 1)
|
||||
if len(a) == 4 and is_lit(a[1]) and is_lit(a[2]) and is_lit(a[3]) {
|
||||
if gpu_variant_in(vs, unq(a[1]), unq(a[2]), key_defs(unq(a[3]))) == null { push(missing, `{file}: {a[1]} {a[2]} {a[3]}`) }
|
||||
} else {
|
||||
computed += 1
|
||||
}
|
||||
}
|
||||
at += len(names[n])
|
||||
}
|
||||
}
|
||||
return computed
|
||||
}
|
||||
function char_word(c: int) -> bool { return (c >= 97 and c <= 122) or (c >= 65 and c <= 90) or (c >= 48 and c <= 57) or c == 95 or c == 46 }
|
||||
# every value a computed site can take (dev switches' debug views left out: they are not shipped)
|
||||
function computed_all(vs: []GpuVariant, missing: []string) -> void {
|
||||
let flags = [false, true]
|
||||
for a in 0 .. 2 {
|
||||
for b in 0 .. 2 { need(vs, "fullscreen.vert", "heightgen.frag", ter_heightgen_defs(flags[a], flags[b]), missing) }
|
||||
for t in 0 .. 3 { need(vs, "terrain.vert", "terrain.frag", ter_prog_defs("", flags[a], t), missing) }
|
||||
need(vs, "grass.vert", "model.frag", grass_defs(flags[a]), missing)
|
||||
}
|
||||
}
|
||||
function need(vs: []GpuVariant, v: string, f: string, defs: string, missing: []string) -> void {
|
||||
let k = Text.replace(defs, "\n", ";")
|
||||
if gpu_variant_in(vs, v, f, k) == null { push(missing, `{v}|{f}|{k}`) }
|
||||
}
|
||||
|
||||
test "every program render3d can ask for has a variant in the shipped manifest" {
|
||||
let vs = shipped()
|
||||
expect(vs != null)
|
||||
let missing = new []string
|
||||
let files = Fs.list("packages/ludic.render3d")
|
||||
var asked_files = 0
|
||||
for i in 0 .. len(files) {
|
||||
if not Text.ends_with(files[i], ".ludic") { continue }
|
||||
let computed = file_requests(vs, files[i], missing)
|
||||
if computed != known_computed(files[i]) { print(`{files[i]}: {computed} requests with computed arguments, {known_computed(files[i])} known - give the new one a function in program_defs.ludic`) }
|
||||
expect_eq(computed, known_computed(files[i]))
|
||||
asked_files += 1
|
||||
}
|
||||
expect(asked_files > 20)
|
||||
computed_all(vs, missing)
|
||||
for i in 0 .. len(missing) { print(`no SPIR-V variant for {missing[i]}`) }
|
||||
expect_eq(len(missing), 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue