# ============================================================================ # 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 } # Build a program from a vertex + fragment file pair (defines apply to both). # R3D_PROGRAMS_LOG=: 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) }