ludic/packages/ludic.render3d/programs.ludic
Orkuncakilkaya 07fe90eb5f render3d: what the scan found dropped - shader code, create infos and paths freed after the create
The valley's reachability scan (R3D_ALLOC_SCAN) listed render3d start-up objects nothing held; all were
dropped after being handed to the driver or copied into a key: gvk_module's SPIR-V bytes, create info
and handle slot; gvk_program's key parts, .spv paths, bindings and layout create infos; the compute
program's the same; gvk_sampler's and gvk_view_of's create infos; gvk_zero_vbuf_get's 64 KB of zeros;
gvk_layout_key's result (always a copy now, freed by gvk_pipeline once its key holds it) and
r3d_program's defines. Each goes once the handle it made has been read. Compiled (steady).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 17:58:15 +03:00

103 lines
5.5 KiB
Text

# ============================================================================
# programs.ludic — GLSL from files. A shader file carries no #version line; the
# loader prepends "#version 410 core", any per-variant defines, and the shared
# noise.glsl + lighting.glsl chunks for fragment stages, then compiles it.
# ============================================================================
# Where the renderer's own files are. The shaders belong to this package and ship with
# it — in the Ludic checkout they are under packages/, and in an installed toolchain
# under $LUDIC_HOME/packages/ — so a game built outside the Ludic tree does not have to
# copy them in. The scanned CC0 materials are the other half: too large to ship with a
# toolchain and redistributable from their origin, so they are fetched into the project
# (`ludic assets`) and read from there.
# defines prepended to EVERY program (set before any is built): renderer-wide switches
function r3d_set_paths(render3d_st: mut Render3dState, root: string, assets: string) -> void { render3d_st.r3d_root = root; render3d_st.r3d_assets = assets }
# The install root, as the compiler computes it: $LUDIC_HOME, else the directory of the
# `ludic` on PATH. A game running from its own tree finds this package there.
function r3d_home() -> string {
let env = Os.env("LUDIC_HOME")
if env != null and env != "" { return env }
return `{Os.env("HOME")}/.ludic`
}
# Settle r3d_root on first use: the package as checked out beside the project, else the
# copy that ships with the toolchain.
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function r3d_find_root(render3d_st: mut Render3dState) -> void {
if render3d_st.r3d_root_found { return }
render3d_st.r3d_root_found = true
if Fs.exists(`{render3d_st.r3d_root}/shaders/lighting.glsl`) { return }
let home = r3d_home()
let alt = `{home}/packages/ludic.render3d`
if Fs.exists(`{alt}/shaders/lighting.glsl`) { render3d_st.r3d_root = alt; return }
print(`r3d: cannot find the renderer's shaders (looked in {render3d_st.r3d_root}/shaders and {alt}/shaders)`)
}
# Build a program from a vertex + fragment file pair (defines apply to both).
# R3D_PROGRAMS_LOG=<file>: append every program built (vertex, fragment, defines) - the list a
# backend that cannot compile shaders at run time (Vulkan: SPIR-V is built ahead) must cover
@alloc_ok("profiling and statistics, only under R3D_PROF / R3D_DRAWSTATS")
function r3d_program_log(render3d_st: mut Render3dState, vs: string, fs: string, defines: string) -> void {
if render3d_st.r3d_prog_log < 0 { render3d_st.r3d_prog_log = 0; if r3d_env_has(render3d_st, "R3D_PROGRAMS_LOG") { render3d_st.r3d_prog_log = 1 } }
if render3d_st.r3d_prog_log == 0 { return }
let f = file_open(r3d_env(render3d_st, "R3D_PROGRAMS_LOG"), "ab")
if f == null { return }
# one line per program: the defines' newlines become ';' so a variant is one line
let defs = Text.replace(`{render3d_st.r3d_global_defs}{defines}`, "\n", ";")
let line = `{vs}|{fs}|{defs}\n`
file_write(f, line, len(line))
file_close(f)
}
@alloc_ok("made once per resource and kept for its life (a texture, program, sampler, view, layout or memory block is created when first asked for)")
function r3d_program(render3d_st: mut Render3dState, vs: string, fs: string, defines: string) -> int {
r3d_program_log(render3d_st, vs, fs, defines)
let defs = `{render3d_st.r3d_global_defs}{defines}`
let p = gpu_program(render3d_st, vs, fs, defs)
free(defs) # the program's key is built from it, not kept
if p == 0 { print(`r3d: program failed: {vs} + {fs}`) }
return p
}
# Bind a texture to a unit and point a sampler uniform at it.
function r3d_bind_tex(render3d_st: mut Render3dState, prog: int, name: string, unit: int, kind: int, tex: int) -> void { gpu_bind_sampler(render3d_st, prog, name, unit, kind, tex) }
function r3d_bind_2d(render3d_st: mut Render3dState, prog: int, name: string, unit: int, tex: int) -> void { gpu_bind_sampler(render3d_st, prog, name, unit, GPU_TEX2D, tex) }
# A framebuffer with one colour texture (and optionally a depth texture).
property Target {
@owns(GpuFramebuffer) fbo: int = 0,
@owns(GpuTexture) color: int = 0,
@owns(GpuTexture) depth: int = 0,
w: int = 0,
h: int = 0
}
@alloc_ok("made once per resource and kept for its life (a texture, program, sampler, view, layout or memory block is created when first asked for)")
@creates(Target)
function target_new(render3d_st: mut Render3dState, w: int, h: int, ifmt: int, fmt: int, ty: int, with_depth: bool, filter: int) -> Target {
let t = new Target
t.w = w; t.h = h
t.fbo = gpu_fb_new(render3d_st)
gpu_fb_bind(render3d_st, t.fbo)
t.color = tex_target(render3d_st, w, h, ifmt, fmt, ty, filter)
gpu_fb_color(render3d_st, 0, t.color)
if with_depth {
t.depth = tex_target(render3d_st, w, h, GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, GL_NEAREST)
gpu_fb_depth(render3d_st, t.depth)
}
let st = gpu_fb_status(render3d_st)
if st != GL_FRAMEBUFFER_COMPLETE { print(`r3d: framebuffer incomplete {st} ({w}x{h})`) }
gpu_fb_bind(render3d_st, 0)
return t
}
@releases(Target)
function target_free(render3d_st: mut Render3dState, t: Target) -> void {
if t == null { return }
gpu_fb_free(render3d_st, t.fbo)
gpu_tex_free(render3d_st, t.color)
if t.depth != 0 { gpu_tex_free(render3d_st, t.depth) }
}
function target_bind(render3d_st: mut Render3dState, t: Target) -> void {
gpu_fb_bind(render3d_st, t.fbo)
gpu_viewport(render3d_st, 0, 0, t.w, t.h)
}