ludic/packages/ludic.render3d/gpu_manifest.ludic
Orkuncakilkaya 9f233104a3 render3d: start-up declared, ov_nine and the caster test on buffers made once - 362 frame sites to 122
The renderer starts itself from the first frame (gpu_select), so the device, its tables, the
manifest and programs, grass, shadows, sky, terrain textures and the actor pool read as frame
allocations: each is @alloc_ok as start-up, with gvk_fail (a failure) and the actor census and
texture dump (debug switches). ov_nine's four corner/uv arrays are one floats(16) made in
gvk_startup_state; ac_in_light's four points are made there too. Compiled (steady, and ludic deps
over main 4316ff97).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:21:51 +03:00

116 lines
5.1 KiB
Text

# ============================================================================
# gpu_manifest.ludic — the shader variants a pipeline API draws with.
#
# OpenGL compiles each program's GLSL when the game starts; Vulkan cannot, so every
# program render3d builds is compiled ahead of time by `ludic-dev shaders` into
# shaders/spv/<id>.vert.spv and .frag.spv, with shaders/spv/manifest.txt saying what a
# backend needs to draw with it:
# P <id> <vertex> <fragment> <defines> a program (defines with ';' for newlines)
# B <id> <stage> <binding> <size> a stage's uniform block
# 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
# 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.
# ============================================================================
property GpuVariant {
id: string,
vs: string,
fs: string,
defs: string, # the defines as variants.list writes them: ';' for newlines
vblock: int = -1, # the vertex stage's uniform block size in bytes (-1: none)
fblock: int = -1,
u_name: []string,
u_stage: []int, # 0 vertex, 1 fragment
u_off: []int,
u_type: []string,
u_count: []int,
u_stride: []int,
t_name: []string,
t_bind: []int,
i_name: []string,
i_loc: []int,
i_type: []string
}
@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_variant_new(id: string, vs: string, fs: string, defs: string) -> GpuVariant {
let v = new GpuVariant
v.id = id; v.vs = vs; v.fs = fs; v.defs = defs
v.u_name = new []string; v.u_stage = new []int; v.u_off = new []int; v.u_type = new []string
v.u_count = new []int; v.u_stride = new []int
v.t_name = new []string; v.t_bind = new []int
v.i_name = new []string; v.i_loc = new []int; v.i_type = new []string
return v
}
# Read a manifest; returns how many programs it holds (0: none, or no file).
@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 }
let lines = Text.split(text, "\n")
var cur: GpuVariant = null
for li in 0 .. len(lines) {
let ln = lines[li]
let lp: pointer = ln
if len(lp) < 2 or lp[0] == '#' { continue }
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)
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])
}
}
return len(render3d_st.gpu_variants)
}
# the variant for a program as r3d_program names it (defines with real newlines)
function gpu_variant_find(render3d_st: Render3dState, vs: string, fs: string, defines: string) -> GpuVariant {
return gpu_variant_find_key(render3d_st, vs, fs, Text.replace(defines, "\n", ";"))
}
# ... 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]
if v.vs == vs and v.fs == fs and v.defs == defs { return v }
}
return null
}
# a uniform's index in the variant's table for a stage (0 vertex, 1 fragment), or -1;
# a program sets a uniform in whichever stages declare it, so a backend asks both
function gpu_variant_uniform(v: GpuVariant, stage: int, name: string) -> int {
for i in 0 .. len(v.u_name) { if v.u_stage[i] == stage and v.u_name[i] == name { return i } }
return -1
}
# a sampler's binding, or -1 when neither stage declares it
function gpu_variant_sampler(v: GpuVariant, name: string) -> int {
for i in 0 .. len(v.t_name) { if v.t_name[i] == name { return v.t_bind[i] } }
return -1
}