render3d: a renderer that cannot draw says so and stops - LUDIC_HOME's leading ~ expanded (an unexpanded one left a run without shaders, logging 27 missing variants and drawing on), the SPIR-V manifest ends with E <count> and a cut or miscounted one is refused, and every such failure is a fault: r3d_open / r3d_load_step fail on it, r3d_fault() / r3d_fault_exit(code) for the game; tests/manifest_test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 22:12:39 +03:00
parent aac58ec6fa
commit c3fc896dea
13 changed files with 227 additions and 54 deletions

View file

@ -10,6 +10,7 @@
# U <id> <stage> <name> <offset> <type> <count> <stride>
# T <id> <name> <binding> a sampler, one binding across both stages
# I <id> <name> <location> <type> a vertex input
# E <count> the end: how many P lines came before it
# This reads it, and answers the questions a backend asks while drawing: which variant a
# program is, where a uniform lives in its stage's block, which binding a sampler has.
# ============================================================================
@ -46,47 +47,74 @@ function gpu_variant_new(id: string, vs: string, fs: string, defs: string) -> Gp
return v
}
# Read a manifest; returns how many programs it holds (0: none, or no file).
# Read a manifest; returns how many programs it holds (0: none, no file, or refused - and then
# the renderer is at fault, so nothing starts on a partial list)
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gpu_manifest_load(render3d_st: mut Render3dState, path: string) -> int {
render3d_st.gpu_variants = new []GpuVariant
let text = Fs.read_text(path)
if text == null { return 0 }
if text == null { gpu_fault_set(render3d_st, `no SPIR-V manifest at {path}`); return 0 }
let vs = gpu_manifest_parse(text)
if vs == null { gpu_fault_set(render3d_st, `the SPIR-V manifest at {path} is cut short or not a manifest (no E line, or its count is not the programs')`); return 0 }
render3d_st.gpu_variants = vs
return len(vs)
}
# A manifest's programs, or null when it is refused: `ludic-dev shaders` ends one with `E <count>`,
# so a file cut off anywhere (a torn copy, a short read) is told apart from one with fewer programs.
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gpu_manifest_parse(text: string) -> []GpuVariant {
let out = new []GpuVariant
let lines = Text.split(text, "\n")
var cur: GpuVariant = null
var ended = -1
for li in 0 .. len(lines) {
let ln = lines[li]
let lp: pointer = ln
if len(lp) < 2 or lp[0] == '#' { continue }
if ended >= 0 { return null } # nothing may follow the end line
let f = Text.split(ln, " ")
if lp[0] == 'P' and len(f) >= 4 {
# the defines are the rest of the line, spaces and all
var defs = ""
var k = 4
while k < len(f) {
if k > 4 { defs = defs + " " }
defs = defs + f[k]
k += 1
}
cur = gpu_variant_new(f[1], f[2], f[3], defs)
push(render3d_st.gpu_variants, cur)
if lp[0] == 'E' and len(f) == 2 {
ended = Text.to_int(f[1])
continue
}
if cur == null { continue }
if lp[0] == 'B' and len(f) >= 5 {
if f[2] == "vert" { cur.vblock = Text.to_int(f[4]) } else { cur.fblock = Text.to_int(f[4]) }
} else if lp[0] == 'U' and len(f) >= 8 {
var stage = 1
if f[2] == "vert" { stage = 0 }
push(cur.u_stage, stage); push(cur.u_name, f[3]); push(cur.u_off, Text.to_int(f[4]))
push(cur.u_type, f[5]); push(cur.u_count, Text.to_int(f[6])); push(cur.u_stride, Text.to_int(f[7]))
} else if lp[0] == 'T' and len(f) >= 4 {
push(cur.t_name, f[2]); push(cur.t_bind, Text.to_int(f[3]))
} else if lp[0] == 'I' and len(f) >= 5 {
push(cur.i_name, f[2]); push(cur.i_loc, Text.to_int(f[3])); push(cur.i_type, f[4])
if lp[0] == 'P' and len(f) >= 4 {
cur = gpu_variant_new(f[1], f[2], f[3], gpu_manifest_defs(f))
push(out, cur)
continue
}
if cur != null { gpu_manifest_row(cur, lp[0], f) }
}
if ended < 0 or ended != len(out) { return null }
return out
}
# a P line's defines: the rest of the line, spaces and all
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gpu_manifest_defs(f: []string) -> string {
var defs = ""
var k = 4
while k < len(f) {
if k > 4 { defs = defs + " " }
defs = defs + f[k]
k += 1
}
return defs
}
# one B / U / T / I line into the variant it follows
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gpu_manifest_row(cur: GpuVariant, kind: int, f: []string) -> void {
if kind == 'B' and len(f) >= 5 {
if f[2] == "vert" { cur.vblock = Text.to_int(f[4]) } else { cur.fblock = Text.to_int(f[4]) }
} else if kind == 'U' and len(f) >= 8 {
var stage = 1
if f[2] == "vert" { stage = 0 }
push(cur.u_stage, stage); push(cur.u_name, f[3]); push(cur.u_off, Text.to_int(f[4]))
push(cur.u_type, f[5]); push(cur.u_count, Text.to_int(f[6])); push(cur.u_stride, Text.to_int(f[7]))
} else if kind == 'T' and len(f) >= 4 {
push(cur.t_name, f[2]); push(cur.t_bind, Text.to_int(f[3]))
} else if kind == 'I' and len(f) >= 5 {
push(cur.i_name, f[2]); push(cur.i_loc, Text.to_int(f[3])); push(cur.i_type, f[4])
}
return len(render3d_st.gpu_variants)
}
# the variant for a program as r3d_program names it (defines with real newlines)
@ -95,9 +123,13 @@ function gpu_variant_find(render3d_st: Render3dState, vs: string, fs: string, de
}
# ... or as variants.list writes it (';' for newlines)
function gpu_variant_find_key(render3d_st: Render3dState, vs: string, fs: string, defs: string) -> GpuVariant {
if render3d_st.gpu_variants == null { return null }
for i in 0 .. len(render3d_st.gpu_variants) {
let v = render3d_st.gpu_variants[i]
return gpu_variant_in(render3d_st.gpu_variants, vs, fs, defs)
}
# ... in a list of variants (a parsed manifest), or null
function gpu_variant_in(vars: []GpuVariant, vs: string, fs: string, defs: string) -> GpuVariant {
if vars == null { return null }
for i in 0 .. len(vars) {
let v = vars[i]
if v.vs == vs and v.fs == fs and v.defs == defs { return v }
}
return null