184 lines
7.9 KiB
Text
184 lines
7.9 KiB
Text
# manifest_test.ludic - the SPIR-V manifest read as the renderer reads it, with no device: a whole one
|
|
# parses, one cut short or miscounted is refused, every program variants.list names resolves in the
|
|
# shipped manifest, and a LUDIC_HOME with a leading "~" is found (it once started a run on nothing)
|
|
import "ludic.render3d/r3d.ludic"
|
|
program ManifestTest {
|
|
# never called: naming Input links the engine's runtime, which render3d's textures stand on
|
|
function links_runtime() -> bool { return Input.key_pressed(0) }
|
|
|
|
function two() -> string {
|
|
var t = "# a manifest\nP aa fullscreen.vert sky.frag \nB aa frag 1 64\nU aa frag u_sun 0 vec3 1 0\n"
|
|
t = t + "P bb model.vert shadow.frag #define SHADOW_PASS;#define WIND;\nT bb u_tex 2\nI bb a_pos 0 vec3\n"
|
|
return t
|
|
}
|
|
|
|
test "a whole manifest parses, and a program is found by its key" {
|
|
let vs = gpu_manifest_parse(two() + "E 2\n")
|
|
expect(vs != null)
|
|
expect_eq(len(vs), 2)
|
|
expect(gpu_variant_in(vs, "fullscreen.vert", "sky.frag", "") != null)
|
|
let sh = gpu_variant_in(vs, "model.vert", "shadow.frag", "#define SHADOW_PASS;#define WIND;")
|
|
expect(sh != null)
|
|
expect_eq(gpu_variant_sampler(sh, "u_tex"), 2)
|
|
expect(gpu_variant_in(vs, "model.vert", "shadow.frag", "#define SHADOW_PASS;") == null)
|
|
}
|
|
|
|
test "a manifest cut short is refused" {
|
|
expect(gpu_manifest_parse(two()) == null) # no end line
|
|
let whole = two() + "E 2\n"
|
|
expect(gpu_manifest_parse(whole[0 .. len(whole) - 30]) == null) # cut mid-program
|
|
expect(gpu_manifest_parse("") == null)
|
|
}
|
|
|
|
test "an end line that miscounts, or anything after it, is refused" {
|
|
expect(gpu_manifest_parse(two() + "E 3\n") == null)
|
|
expect(gpu_manifest_parse(two() + "E 1\n") == null)
|
|
expect(gpu_manifest_parse(two() + "E 2\nP cc a.vert b.frag \n") == null)
|
|
}
|
|
|
|
test "every program variants.list names has a variant in the shipped manifest" {
|
|
let root = "packages/ludic.render3d/shaders"
|
|
let text = Fs.read_text(`{root}/spv/manifest.txt`)
|
|
expect(text != null)
|
|
let vs = gpu_manifest_parse(text)
|
|
expect(vs != null)
|
|
let list = Text.split(Fs.read_text(`{root}/variants.list`), "\n")
|
|
var asked = 0
|
|
for i in 0 .. len(list) {
|
|
let ln = Text.trim(list[i])
|
|
if len(ln) == 0 or ln[0] == '#' { continue }
|
|
let p = Text.split(ln, "|")
|
|
var defs = ""
|
|
if len(p) > 2 { defs = p[2] }
|
|
if gpu_variant_in(vs, p[0], p[1], defs) == null { print(`no variant for {ln}`) }
|
|
expect(gpu_variant_in(vs, p[0], p[1], defs) != null)
|
|
asked += 1
|
|
}
|
|
expect_eq(asked, len(vs))
|
|
}
|
|
|
|
test "a leading tilde is the home directory" {
|
|
expect_eq(r3d_tilde("~/workspace/gpp-lang", "/Users/me"), "/Users/me/workspace/gpp-lang")
|
|
expect_eq(r3d_tilde("~", "/Users/me"), "/Users/me")
|
|
expect_eq(r3d_tilde("/opt/ludic", "/Users/me"), "/opt/ludic")
|
|
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)
|
|
}
|
|
|
|
}
|