From 2980f050ed9dfd90e03c7fc7321d0b6afc2d2a4a Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Wed, 16 Sep 2026 13:21:51 +0300 Subject: [PATCH] feat(render3d): gate R3D_* switches behind R3D_DEV in windowed builds Every R3D_* read goes through r3d_env_has / r3d_env (env.ludic): a headless build honours them as before, a windowed build only when R3D_DEV is set to anything but "0", so a shipped game never reaches a debug view, feature kill, file writer or hardware fake. Documented in docs/SHIPPING.md. Co-Authored-By: Claude Opus 5 --- changes/render3d-dev-gate.md | 6 ++++ docs/SHIPPING.md | 22 +++++++++++++++ packages/ludic.render3d/actor.ludic | 2 +- packages/ludic.render3d/drawstats.ludic | 8 +++--- packages/ludic.render3d/env.ludic | 19 +++++++++++++ packages/ludic.render3d/gpu.ludic | 6 ++-- packages/ludic.render3d/gpu_vk.ludic | 4 +-- packages/ludic.render3d/gpu_vk_draw.ludic | 6 ++-- packages/ludic.render3d/grass.ludic | 10 +++---- packages/ludic.render3d/overlay.ludic | 2 +- packages/ludic.render3d/post.ludic | 2 +- packages/ludic.render3d/prof.ludic | 2 +- packages/ludic.render3d/programs.ludic | 4 +-- packages/ludic.render3d/r3d.ludic | 1 + packages/ludic.render3d/render.ludic | 34 +++++++++++------------ packages/ludic.render3d/scatter.ludic | 6 ++-- packages/ludic.render3d/shadow.ludic | 2 +- packages/ludic.render3d/stream.ludic | 4 +-- packages/ludic.render3d/streamline.ludic | 18 ++++++------ packages/ludic.render3d/terrain.ludic | 30 ++++++++++---------- packages/ludic.render3d/water.ludic | 8 +++--- 21 files changed, 122 insertions(+), 74 deletions(-) create mode 100644 changes/render3d-dev-gate.md create mode 100644 packages/ludic.render3d/env.ludic diff --git a/changes/render3d-dev-gate.md b/changes/render3d-dev-gate.md new file mode 100644 index 00000000..16813323 --- /dev/null +++ b/changes/render3d-dev-gate.md @@ -0,0 +1,6 @@ +bump: minor +type: feat +**Renderer switches stay out of shipped games** — every `R3D_*` environment switch in +`ludic.render3d` (debug views, feature kills, file writers, hardware fakes, the Windows +feature overrides) now goes through one gate: a headless build honours them as before, a +windowed build only when `R3D_DEV` is set to anything but `0`. See `docs/SHIPPING.md`. diff --git a/docs/SHIPPING.md b/docs/SHIPPING.md index 08a2bac7..0de5c416 100644 --- a/docs/SHIPPING.md +++ b/docs/SHIPPING.md @@ -190,6 +190,28 @@ which is every `ludic run` - nothing mounts, no directory change happens, no splash appears, and every open goes to the filesystem exactly as before. Packing is a shipping step, and it is invisible until you ship. +### Renderer switches in a shipped game + +`ludic.render3d` has about seventy `R3D_*` environment switches - debug views +(`R3D_WIRE`, `R3D_DEBUG*`, `R3D_LODDBG`, ...), feature kills (`R3D_NOSHADOW`, +`R3D_NOTERRAIN`, ...), file writers (`R3D_DUMP_*`, `R3D_DRAWSTATS_CSV`, ...), +hardware fakes (`R3D_CAPS`, `R3D_FORCE`, `R3D_GFX`) and the Windows feature +overrides (`R3D_DLSS*`, `R3D_REFLEX`, `R3D_HDR`, `R3D_MESH_GRASS`). They are +developer tools, so a player cannot trip them by accident: + +- a **headless** build always honours them - tests, benchmarks and frame dumps + work exactly as before; +- a **windowed** build ignores every one of them unless `R3D_DEV` is set to + anything but `0`. + +```bash +R3D_DEV=1 R3D_WIRE=1 ./MyGame.app/Contents/MacOS/MyGame +``` + +A game that has its own switches should gate them the same way. Inside render3d, +read a new switch with `r3d_env_has("R3D_FOO")` / `r3d_env("R3D_FOO")`, never +`Os.has_env` / `Os.env`. + ## Signing and distribution `ludic bundle` ad-hoc signs the result. On Apple silicon that is not optional diff --git a/packages/ludic.render3d/actor.ludic b/packages/ludic.render3d/actor.ludic index 3c0c9449..b8b34d9d 100644 --- a/packages/ludic.render3d/actor.ludic +++ b/packages/ludic.render3d/actor.ludic @@ -274,7 +274,7 @@ var ac_census_at: int = -2 function actor_draw() -> void { if ac_actors == null { return } ac_frame += 1 - if ac_census_at == -2 { ac_census_at = -1; if Os.has_env("R3D_ACTOR_CENSUS") { ac_census_at = Text.to_int(Os.env("R3D_ACTOR_CENSUS")) } } + if ac_census_at == -2 { ac_census_at = -1; if r3d_env_has("R3D_ACTOR_CENSUS") { ac_census_at = Text.to_int(r3d_env("R3D_ACTOR_CENSUS")) } } if ac_census_at > 0 and ac_frame == ac_census_at { actor_census() } for i in 0 .. len(ac_actors) { let a = ac_actors[i] diff --git a/packages/ludic.render3d/drawstats.ludic b/packages/ludic.render3d/drawstats.ludic index 28d01882..e971b96b 100644 --- a/packages/ludic.render3d/drawstats.ludic +++ b/packages/ludic.render3d/drawstats.ludic @@ -51,11 +51,11 @@ var ds_last: int = -1 function ds_enabled() -> bool { if ds_on < 0 { ds_on = 0 - if Os.has_env("R3D_DRAWSTATS") { + if r3d_env_has("R3D_DRAWSTATS") { ds_on = 1 - ds_frames = Text.to_int(Os.env("R3D_DRAWSTATS")) + ds_frames = Text.to_int(r3d_env("R3D_DRAWSTATS")) if ds_frames < 2 { ds_frames = 60 } - if Os.has_env("R3D_DRAWSTATS_FROM") { ds_from = Text.to_int(Os.env("R3D_DRAWSTATS_FROM")) } + if r3d_env_has("R3D_DRAWSTATS_FROM") { ds_from = Text.to_int(r3d_env("R3D_DRAWSTATS_FROM")) } ds_pass_names = new []pointer; ds_pass_prog = new []long; ds_pass_tex = new []long ds_row_pass = new []int; ds_row_prog = new []int; ds_row_kind = new []int ds_row_draws = new []long; ds_row_inst = new []long; ds_row_idx = new []long @@ -188,7 +188,7 @@ function ds_report() -> void { shown += 1 } var path = "build/drawstats.csv" - if Os.has_env("R3D_DRAWSTATS_CSV") { path = Os.env("R3D_DRAWSTATS_CSV") } + if r3d_env_has("R3D_DRAWSTATS_CSV") { path = r3d_env("R3D_DRAWSTATS_CSV") } let f = file_open(path, "wb") if f == null { print(`r3d: drawstats could not write {path}`); return } let head = "backend,width,height,frames,pass,kind,program,draws_per_frame,instances_per_frame,indices_per_frame,pass_program_changes_per_frame,pass_texture_binds_per_frame\n" diff --git a/packages/ludic.render3d/env.ludic b/packages/ludic.render3d/env.ludic new file mode 100644 index 00000000..71fbefde --- /dev/null +++ b/packages/ludic.render3d/env.ludic @@ -0,0 +1,19 @@ +# env.ludic — the one gate every R3D_* switch goes through. +# The debug views, feature kills, file writers and hardware fakes are developer +# tools: a headless build (tests, benchmarks, frame dumps) always honours them, a +# windowed build - which is what a player runs - only when R3D_DEV is set to +# anything but "0". Read every R3D_* through r3d_env_has / r3d_env, never +# Os.has_env / Os.env. +var r3d_dev_v: int = -1 +function r3d_dev() -> bool { + if r3d_dev_v < 0 { if Os.has_env("R3D_DEV") and Os.env("R3D_DEV") != "0" { r3d_dev_v = 1 } else { r3d_dev_v = 0 } } + return r3d_dev_v == 1 +} +function r3d_env_has(name: string) -> bool { + if is_windowed() and not r3d_dev() { return false } + return Os.has_env(name) +} +function r3d_env(name: string) -> string { + if is_windowed() and not r3d_dev() { return "" } + return Os.env(name) +} diff --git a/packages/ludic.render3d/gpu.ludic b/packages/ludic.render3d/gpu.ludic index 9db0acc8..fa39ab24 100644 --- a/packages/ludic.render3d/gpu.ludic +++ b/packages/ludic.render3d/gpu.ludic @@ -48,7 +48,7 @@ function gpu_name(kind: int) -> string { # caller can show the player (gpu_fallback_reason). function gpu_select() -> int { if gpu_kind != 0 { return gpu_kind } - if Os.has_env("R3D_GFX") { gpu_wanted = gpu_kind_of(Os.env("R3D_GFX")) } + if r3d_env_has("R3D_GFX") { gpu_wanted = gpu_kind_of(r3d_env("R3D_GFX")) } if gpu_wanted == 0 { gpu_wanted = GPU_GL } gpu_kind = GPU_GL if gpu_wanted == GPU_VK { @@ -318,7 +318,7 @@ function gpu_caps_fake(kind: string) -> void { function gpu_caps_probe() -> void { if gpu_cap_probed { return } gpu_cap_probed = true - if Os.has_env("R3D_CAPS") { gpu_caps_fake(Os.env("R3D_CAPS")); return } + if r3d_env_has("R3D_CAPS") { gpu_caps_fake(r3d_env("R3D_CAPS")); return } gpu_cap_windows = Os.platform() == "windows" if not gpu_cap_windows { return } if Vk.open() == 0 { return } @@ -793,7 +793,7 @@ function gpu_fb_at(fb: int) -> int { return fb * GPU_FB_W } function gpu_glcheck_on() -> bool { - if gpu_glcheck < 0 { gpu_glcheck = 0; if Os.has_env("R3D_GLCHECK") { gpu_glcheck = 1 } } + if gpu_glcheck < 0 { gpu_glcheck = 0; if r3d_env_has("R3D_GLCHECK") { gpu_glcheck = 1 } } return gpu_glcheck == 1 } function gpu_check(tag: string) -> int { if gpu_kind == GPU_VK { return 0 }; return gl_check(tag) } diff --git a/packages/ludic.render3d/gpu_vk.ludic b/packages/ludic.render3d/gpu_vk.ludic index 30a56421..8f6371f6 100644 --- a/packages/ludic.render3d/gpu_vk.ludic +++ b/packages/ludic.render3d/gpu_vk.ludic @@ -35,7 +35,7 @@ function gvk_note(msg: string) -> void { print(msg) if not gvk_errlog_read { gvk_errlog_read = true - if Os.has_env("R3D_VK_ERRLOG") { gvk_errlog = Os.env("R3D_VK_ERRLOG") } + if r3d_env_has("R3D_VK_ERRLOG") { gvk_errlog = r3d_env("R3D_VK_ERRLOG") } } if gvk_errlog == null { return } let f = file_open(gvk_errlog, "ab") @@ -205,7 +205,7 @@ function gvk_init() -> bool { # mesh-shader grass: the extension, and its meshShader feature asked for where the device has it. # R3D_NO_MESH=1 leaves it off. gvk_has_mesh = false - if gvk_ext_in(dexts, nde, VK_EXT_MESH_SHADER_EXTENSION_NAME) and not Os.has_env("R3D_NO_MESH") { + if gvk_ext_in(dexts, nde, VK_EXT_MESH_SHADER_EXTENSION_NAME) and not r3d_env_has("R3D_NO_MESH") { let fm = bytes(VkPhysicalDeviceMeshShaderFeaturesEXT_sizeof) Vk.zero(fm, VkPhysicalDeviceMeshShaderFeaturesEXT_sizeof) Vk.put_i32(fm, VkPhysicalDeviceMeshShaderFeaturesEXT_sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT) diff --git a/packages/ludic.render3d/gpu_vk_draw.ludic b/packages/ludic.render3d/gpu_vk_draw.ludic index 1abe2cda..05a1dc87 100644 --- a/packages/ludic.render3d/gpu_vk_draw.ludic +++ b/packages/ludic.render3d/gpu_vk_draw.ludic @@ -965,7 +965,7 @@ var gvk_has_hdr_meta: bool = false # the device took VK_EXT_hdr_metadata # R3D_HDR=1 / 0 overrides the setting, for a desktop test function r3d_hdr(on: bool) -> void { var want = on - if Os.has_env("R3D_HDR") { want = Text.to_int(Os.env("R3D_HDR")) != 0 } + if r3d_env_has("R3D_HDR") { want = Text.to_int(r3d_env("R3D_HDR")) != 0 } if want == gvk_hdr_want { return } gvk_hdr_want = want if gvk_swap != 0 { gvk_swap_stale = true } @@ -1420,7 +1420,7 @@ function gvk_open(w: int, h: int, title: string) -> bool { } if not gvk_frame_init() { return false } if not gvk_screen_make(gl_w, gl_h) { return false } - if Os.has_env("R3D_VK_PROBE") { gvk_compute_probe() } + if r3d_env_has("R3D_VK_PROBE") { gvk_compute_probe() } if is_windowed() { return gvk_swap_make(gl_w, gl_h) } return true } @@ -1825,7 +1825,7 @@ var gvk_n_flush: int = 0 var gvk_prof_frames: int = 0 var gvk_n_pipe_new: int = 0 # pipelines made this profile window function gvk_prof() -> bool { - if gvk_prof_state < 0 { gvk_prof_state = 0; if Os.has_env("R3D_VK_PROF") { gvk_prof_state = 1 } } + if gvk_prof_state < 0 { gvk_prof_state = 0; if r3d_env_has("R3D_VK_PROF") { gvk_prof_state = 1 } } return gvk_prof_state == 1 } function gvk_prof_frame() -> void { diff --git a/packages/ludic.render3d/grass.ludic b/packages/ludic.render3d/grass.ludic index 29cc745f..c07f1d18 100644 --- a/packages/ludic.render3d/grass.ludic +++ b/packages/ludic.render3d/grass.ludic @@ -43,7 +43,7 @@ var grass_mesh_on: bool = false var grass_mesh_prog: int = 0 function r3d_mesh_grass(on: bool) -> void { grass_mesh_on = on - if Os.has_env("R3D_MESH_GRASS") { grass_mesh_on = Text.to_int(Os.env("R3D_MESH_GRASS")) != 0 } + if r3d_env_has("R3D_MESH_GRASS") { grass_mesh_on = Text.to_int(r3d_env("R3D_MESH_GRASS")) != 0 } } function grass_mesh_live() -> bool { return grass_mesh_on and grass_merge and grass_mesh_prog != 0 and gpu_has_mesh() } @@ -83,7 +83,7 @@ function grass_blade_mesh(rows: int) -> Mesh { } function grass_init() -> void { - grass_merge = gpu_has_mdi() and not Os.has_env("R3D_GRASS_TILES") + grass_merge = gpu_has_mdi() and not r3d_env_has("R3D_GRASS_TILES") var defs = "#define FOLIAGE\n#define BLADE\n" if grass_merge { defs = defs + "#define TILES\n" @@ -98,9 +98,9 @@ function grass_init() -> void { grass_s0 = fl(0.11) grass_d0 = fi(45) grass_radius = fi(1600) - if Os.has_env("R3D_NOBLADES") { grass_on = false } - if Os.has_env("R3D_GRASS_R") { grass_radius = fi(Text.to_int(Os.env("R3D_GRASS_R"))) } - if Os.has_env("R3D_GRASS_DBG") { grass_dbg = Text.to_int(Os.env("R3D_GRASS_DBG")) } + if r3d_env_has("R3D_NOBLADES") { grass_on = false } + if r3d_env_has("R3D_GRASS_R") { grass_radius = fi(Text.to_int(r3d_env("R3D_GRASS_R"))) } + if r3d_env_has("R3D_GRASS_DBG") { grass_dbg = Text.to_int(r3d_env("R3D_GRASS_DBG")) } } # indices per 16 m cell that could exist at distance d (the count the shader computes) diff --git a/packages/ludic.render3d/overlay.ludic b/packages/ludic.render3d/overlay.ludic index f5e4d9a3..fbf08938 100644 --- a/packages/ludic.render3d/overlay.ludic +++ b/packages/ludic.render3d/overlay.ludic @@ -98,7 +98,7 @@ function overlay_init(font_dir: string) -> bool { ov_white = tex_solid(255, 255, 255, 255) overlay_font(font_dir) if ov_font == 0 { print("overlay: no font atlas, text disabled") } - ov_dbg = Os.has_env("R3D_FONTDBG") + ov_dbg = r3d_env_has("R3D_FONTDBG") ov_ready = true return true } diff --git a/packages/ludic.render3d/post.ludic b/packages/ludic.render3d/post.ludic index 8b6aa6d1..92587bf1 100644 --- a/packages/ludic.render3d/post.ludic +++ b/packages/ludic.render3d/post.ludic @@ -341,7 +341,7 @@ function post_tonemap(color_tex: int) -> void { u_f(gpu_uniform(post_p_sharp, "u_amount"), post_sharpen) # R3D_NOGRAIN=1: no film grain, so two frames of a still camera can be compared for what else moves var grain = post_grain - if Os.has_env("R3D_NOGRAIN") { grain = F_ZERO } + if r3d_env_has("R3D_NOGRAIN") { grain = F_ZERO } u_f(gpu_uniform(post_p_sharp, "u_grain"), grain) u_f(gpu_uniform(post_p_sharp, "u_time"), r3d_time) mesh_draw(post_fs) diff --git a/packages/ludic.render3d/prof.ludic b/packages/ludic.render3d/prof.ludic index 8d8f101c..50b04c79 100644 --- a/packages/ludic.render3d/prof.ludic +++ b/packages/ludic.render3d/prof.ludic @@ -25,7 +25,7 @@ var prof_active: int = -1 # slot whose query is currently open var prof_scratch: words = null function prof_init() -> void { - prof_on = Os.has_env("R3D_PROF") + prof_on = r3d_env_has("R3D_PROF") if not prof_on { return } prof_names = new []pointer prof_ns = new []long diff --git a/packages/ludic.render3d/programs.ludic b/packages/ludic.render3d/programs.ludic index 57d19efa..d3ffe198 100644 --- a/packages/ludic.render3d/programs.ludic +++ b/packages/ludic.render3d/programs.ludic @@ -61,9 +61,9 @@ function r3d_shader_src(name: string, defines: string, is_frag: bool) -> string # backend that cannot compile shaders at run time (Vulkan: SPIR-V is built ahead) must cover var r3d_prog_log: int = -1 function r3d_program_log(vs: string, fs: string, defines: string) -> void { - if r3d_prog_log < 0 { r3d_prog_log = 0; if Os.has_env("R3D_PROGRAMS_LOG") { r3d_prog_log = 1 } } + if r3d_prog_log < 0 { r3d_prog_log = 0; if r3d_env_has("R3D_PROGRAMS_LOG") { r3d_prog_log = 1 } } if r3d_prog_log == 0 { return } - let f = file_open(Os.env("R3D_PROGRAMS_LOG"), "ab") + let f = file_open(r3d_env("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(`{r3d_global_defs}{defines}`, "\n", ";") diff --git a/packages/ludic.render3d/r3d.ludic b/packages/ludic.render3d/r3d.ludic index 104bc5a2..1d9e1cce 100644 --- a/packages/ludic.render3d/r3d.ludic +++ b/packages/ludic.render3d/r3d.ludic @@ -2,6 +2,7 @@ # ludic.render3d — a physically based 3D renderer on Gl.* (OpenGL 4.1 core). # Import this one file; the game supplies scene_draw() / scene_draw_casters(). # ============================================================================ +import "env.ludic" import "fmath.ludic" import "gpu.ludic" import "gpu_manifest.ludic" diff --git a/packages/ludic.render3d/render.ludic b/packages/ludic.render3d/render.ludic index 82013944..c468bb87 100644 --- a/packages/ludic.render3d/render.ludic +++ b/packages/ludic.render3d/render.ludic @@ -30,7 +30,7 @@ var r3d_clip_y: int = 0xCF000000 # -2^31: no clipping # R3D_NOPREPASS=1: light the foliage the old way, every card behind the front one included var r3d_prepass_env: int = -1 function r3d_prepass_off() -> bool { - if r3d_prepass_env < 0 { r3d_prepass_env = 0; if Os.has_env("R3D_NOPREPASS") { r3d_prepass_env = 1 } } + if r3d_prepass_env < 0 { r3d_prepass_env = 0; if r3d_env_has("R3D_NOPREPASS") { r3d_prepass_env = 1 } } return r3d_prepass_env == 1 } function fog_bind(prog: int) -> void { @@ -40,7 +40,7 @@ function fog_bind(prog: int) -> void { u_f(gpu_uniform(prog, "u_fog_height_falloff"), r3d_fog_falloff) u_f(gpu_uniform(prog, "u_fog_base"), r3d_fog_base) var cs = r3d_cloud_shadow - if Os.has_env("R3D_NOCLOUD") { cs = F_ZERO } + if r3d_env_has("R3D_NOCLOUD") { cs = F_ZERO } u_f(gpu_uniform(prog, "u_cloud_shadow"), cs) u_f(gpu_uniform(prog, "u_time"), r3d_time) } @@ -52,18 +52,18 @@ var r3d_no_shadow: bool = false var r3d_no_trees: bool = false var r3d_no_refl: bool = false function r3d_env_flags() -> void { - r3d_no_shadow = Os.has_env("R3D_NOSHADOW") - r3d_no_trees = Os.has_env("R3D_NOTREES") - r3d_no_refl = Os.has_env("R3D_NOREFL") - if Os.has_env("R3D_DEBUG") { r3d_debug = true } - if Os.has_env("R3D_DBGSHADOW") { r3d_debug_shadow = true } - if Os.has_env("R3D_NOGI") { post_gi_strength = F_ZERO; post_ao_strength = F_ZERO; post_no_gi = true } - if Os.has_env("R3D_MSAA") { post_ms_samples = Text.to_int(Os.env("R3D_MSAA")) } - sc_skip_blade = Os.has_env("R3D_NOBLADES") - sc_skip_card = Os.has_env("R3D_NOCARDS") - sc_dbg_lod = Os.has_env("R3D_LODDBG") - if Os.has_env("R3D_ANISO") { - let a = Text.to_int(Os.env("R3D_ANISO")) + r3d_no_shadow = r3d_env_has("R3D_NOSHADOW") + r3d_no_trees = r3d_env_has("R3D_NOTREES") + r3d_no_refl = r3d_env_has("R3D_NOREFL") + if r3d_env_has("R3D_DEBUG") { r3d_debug = true } + if r3d_env_has("R3D_DBGSHADOW") { r3d_debug_shadow = true } + if r3d_env_has("R3D_NOGI") { post_gi_strength = F_ZERO; post_ao_strength = F_ZERO; post_no_gi = true } + if r3d_env_has("R3D_MSAA") { post_ms_samples = Text.to_int(r3d_env("R3D_MSAA")) } + sc_skip_blade = r3d_env_has("R3D_NOBLADES") + sc_skip_card = r3d_env_has("R3D_NOCARDS") + sc_dbg_lod = r3d_env_has("R3D_LODDBG") + if r3d_env_has("R3D_ANISO") { + let a = Text.to_int(r3d_env("R3D_ANISO")) tex_anisotropy = 1.0 if a >= 2 { tex_anisotropy = 2.0 } if a >= 4 { tex_anisotropy = 4.0 } @@ -84,7 +84,7 @@ function r3d_open(w: int, h: int, title: string) -> bool { r3d_env_flags() gpu_select() if not gpu_open(w, h, title) { print("r3d: no OpenGL context"); return false } - if Os.has_env("R3D_NOVSYNC") { gpu_vsync(0) } + if r3d_env_has("R3D_NOVSYNC") { gpu_vsync(0) } var renderer: string = gpu_renderer_name() print(`r3d: {gl_w}x{gl_h} on {renderer}`) prof_init() @@ -167,7 +167,7 @@ function r3d_frame(time: int) -> void { # or a fullscreen change does. Headless has no window to resize, and this path is where # a stale attachment or a texture freed twice shows up. r3d_test_frame += 1 - if r3d_test_resize == 0 and Os.has_env("R3D_RESIZE_AT") { r3d_test_resize = Text.to_int(Os.env("R3D_RESIZE_AT")) } + if r3d_test_resize == 0 and r3d_env_has("R3D_RESIZE_AT") { r3d_test_resize = Text.to_int(r3d_env("R3D_RESIZE_AT")) } # R3D_RESIZE_AT=: from frame n on, rebuild every screen-sized buffer every few # frames at a different size, as dragging a window edge or entering fullscreen does. if r3d_test_resize > 0 and r3d_test_frame >= r3d_test_resize and r3d_test_frame % 4 == 0 { @@ -188,7 +188,7 @@ function r3d_frame(time: int) -> void { cam_begin_frame(post_frame, gl_w, gl_h) # R3D_CAM_LOG=1: the camera each frame, in millimetres and thousandths of a radian - to tell a # camera that moves while the hiker stands still from a picture that shakes on its own - if r3d_cam_log < 0 { r3d_cam_log = 0; if Os.has_env("R3D_CAM_LOG") { r3d_cam_log = 1 } } + if r3d_cam_log < 0 { r3d_cam_log = 0; if r3d_env_has("R3D_CAM_LOG") { r3d_cam_log = 1 } } if r3d_cam_log == 1 { print(`cam {r3d_test_frame} pos {f_to_int(f_mul(cam_pos[0], fi(1000)))} {f_to_int(f_mul(cam_pos[1], fi(1000)))} {f_to_int(f_mul(cam_pos[2], fi(1000)))} yaw {f_to_int(f_mul(cam_yaw, fi(1000)))} pitch {f_to_int(f_mul(cam_pitch, fi(1000)))} jitter {f_to_int(f_mul(gsl_jitter_x, fi(1000000)))} {f_to_int(f_mul(gsl_jitter_y, fi(1000000)))} render {post_w}x{post_h} reset {gsl_reset} evalok {gsl_eval_ok} fresh {gsl_fresh}`) } diff --git a/packages/ludic.render3d/scatter.ludic b/packages/ludic.render3d/scatter.ludic index 3f007e73..ed908ff9 100644 --- a/packages/ludic.render3d/scatter.ludic +++ b/packages/ludic.render3d/scatter.ludic @@ -356,7 +356,7 @@ var sc_a2c: bool = true function scatter_init() -> void { # R3D_DUMP_ATLAS: every impostor and card atlas the run bakes, to build/atlas__{color,alpha}.ppm - sc_debug_dump = Os.has_env("R3D_DUMP_ATLAS") + sc_debug_dump = r3d_env_has("R3D_DUMP_ATLAS") sc_prog = r3d_program("model.vert", "model.frag", "") sc_prog_fol = r3d_program("model.vert", "model.frag", "#define FOLIAGE\n#define WIND\n#define ALPHA_TEST\n") sc_prog_fol_depth = r3d_program("model.vert", "depth.frag", "#define FOLIAGE\n#define WIND\n#define ALPHA_TEST\n") @@ -646,7 +646,7 @@ function layer_gpu_prepare(l: Layer) -> bool { # Os.env is null when the variable is unset, and a compare reads through it: ask first # on by default wherever there is compute; R3D_GPU_CULL=0 keeps the CPU partition (for comparing) var off = false - if Os.has_env("R3D_GPU_CULL") { off = Os.env("R3D_GPU_CULL") == "0" } + if r3d_env_has("R3D_GPU_CULL") { off = r3d_env("R3D_GPU_CULL") == "0" } if gpu_has_compute() and gpu_has_mdi() and not off { sc_cull_prog = gpu_compute("scatter_cull", 4) if sc_cull_prog > 0 { print("r3d: scatter: tree layers are culled on the GPU") } @@ -968,7 +968,7 @@ function layer_level_casts_here(l: Layer, k: int) -> bool { # tall, put a shadow on anything the cascade being rendered covers? The flowers' levels ask it # (layer_level_casts_here), and so does every actor (actor_draw_casters). function cast_band_reaches(dmin: int, dmax: int, height: int) -> bool { - if sc_cast_all < 0 { sc_cast_all = 0; if Os.has_env("R3D_CAST_ALL") { sc_cast_all = 1 } } + if sc_cast_all < 0 { sc_cast_all = 0; if r3d_env_has("R3D_CAST_ALL") { sc_cast_all = 1 } } if sc_cast_all == 1 or sh_split == null { return true } if sc_cast_gen != sc_view_gen { sc_cast_gen = sc_view_gen diff --git a/packages/ludic.render3d/shadow.ludic b/packages/ludic.render3d/shadow.ludic index 379ee27b..e2b91288 100644 --- a/packages/ludic.render3d/shadow.ludic +++ b/packages/ludic.render3d/shadow.ludic @@ -277,7 +277,7 @@ function shadow_bind(prog: int) -> void { u_fv(sh_loc(prog, "u_cascade_split"), SHADOW_CASCADES, sh_split) if r3d_debug_shadow and not sh_printed3 { sh_printed3 = true; print(`range {f_fx(sh_range[0])} {f_fx(sh_range[1])} {f_fx(sh_range[2])} {f_fx(sh_range[3])} texel*1000 {f_fx(f_mul(sh_texel[0], fi(1000)))} {f_fx(f_mul(sh_texel[1], fi(1000)))} {f_fx(f_mul(sh_texel[2], fi(1000)))} {f_fx(f_mul(sh_texel[3], fi(1000)))} locs {gpu_uniform(prog, "u_cascade_range")} {gpu_uniform(prog, "u_cascade_texel")}`) } u_fv(sh_loc(prog, "u_cascade_range"), SHADOW_CASCADES, sh_range) - if Os.has_env("R3D_FORCE") { sh_force = Text.to_int(Os.env("R3D_FORCE")) } + if r3d_env_has("R3D_FORCE") { sh_force = Text.to_int(r3d_env("R3D_FORCE")) } u_i(gpu_uniform(prog, "u_force_cascade"), sh_force) u_fv(sh_loc(prog, "u_cascade_texel"), SHADOW_CASCADES, sh_texel) } diff --git a/packages/ludic.render3d/stream.ludic b/packages/ludic.render3d/stream.ludic index 61399a9d..487248af 100644 --- a/packages/ludic.render3d/stream.ludic +++ b/packages/ludic.render3d/stream.ludic @@ -54,8 +54,8 @@ var stream_walks: int = 0 # streams that walked their whole ring this f function stream_new(layer: Layer, size: int, reach: int, b0: int, b1: int, b2: int, b3: int) -> Stream { if not stream_cap_read { stream_cap_read = true - if Os.has_env("R3D_STREAM_CAP") { STREAM_MAX_CHUNKS = Text.to_int(Os.env("R3D_STREAM_CAP")) } - stream_no_evict = Os.has_env("R3D_NOEVICT") + if r3d_env_has("R3D_STREAM_CAP") { STREAM_MAX_CHUNKS = Text.to_int(r3d_env("R3D_STREAM_CAP")) } + stream_no_evict = r3d_env_has("R3D_NOEVICT") } let s = new Stream s.layer = layer; s.size = size; s.reach = reach diff --git a/packages/ludic.render3d/streamline.ludic b/packages/ludic.render3d/streamline.ludic index e1d87be8..e0d96230 100644 --- a/packages/ludic.render3d/streamline.ludic +++ b/packages/ludic.render3d/streamline.ludic @@ -76,7 +76,7 @@ function gsl_fn(feature: int, name: string) -> pointer { # Before Vk.open(): ask the loader for the interposer. R3D_NO_STREAMLINE=1 keeps plain Vulkan. function gsl_boot() -> void { if Os.platform() != "windows" { return } - if Os.has_env("R3D_NO_STREAMLINE") { return } + if r3d_env_has("R3D_NO_STREAMLINE") { return } Vk.sl_prefer(1) } @@ -91,10 +91,10 @@ function gsl_init() -> void { gsl_header(pref, 0x1ca1, 0x0965, 0xbf8e, 0x432b, 0x8da1, 0x6716, 0xd879, 0xfb14, 1) # logLevel eOff; R3D_SL_LOG= writes Streamline's own verbose log (sl.log) there var level = 0 - if Os.has_env("R3D_SL_LOG") { + if r3d_env_has("R3D_SL_LOG") { level = 2 - let dir: pointer = Os.env("R3D_SL_LOG") - let n = Text.length(Os.env("R3D_SL_LOG")) + let dir: pointer = r3d_env("R3D_SL_LOG") + let n = Text.length(r3d_env("R3D_SL_LOG")) gsl_logdir = gsl_struct(n * 2 + 8) # pathToLogsAndData is a wide string for i in 0 .. n { Vk.put_i32(gsl_logdir, i * 2, dir[i] & 255) } Vk.put_ptr(pref, 56, gsl_logdir) @@ -159,7 +159,7 @@ var gsl_f_marker: pointer = null function r3d_reflex(mode: int) -> void { gsl_reflex_mode = mode - if Os.has_env("R3D_REFLEX") { gsl_reflex_mode = Text.to_int(Os.env("R3D_REFLEX")) } + if r3d_env_has("R3D_REFLEX") { gsl_reflex_mode = Text.to_int(r3d_env("R3D_REFLEX")) } } function r3d_reflex_live() -> bool { return gsl_on and gsl_reflex_ok and gsl_reflex_mode > 0 } @@ -248,7 +248,7 @@ var gsl_eval_ok: bool = true # the last evaluate worked: only then is th # R3D_DLSS=0..4 overrides the setting, for a headless take function r3d_dlss(mode: int) -> void { var m = mode - if Os.has_env("R3D_DLSS") { m = Text.to_int(Os.env("R3D_DLSS")) } + if r3d_env_has("R3D_DLSS") { m = Text.to_int(r3d_env("R3D_DLSS")) } if m != gsl_dlss_mode { gsl_reset = true } gsl_dlss_mode = m } @@ -279,7 +279,7 @@ function gsl_fill_options(w: int, h: int) -> void { # K's 2.8 - slower than no DLSS at all (33 fps against 41; with K, 60). R3D_DLSS_PRESET= # (sl::DLSSPreset: 11 K, 12 L, 13 M) sets every mode's, to measure them against each other. var preset = 11 - if Os.has_env("R3D_DLSS_PRESET") { preset = Text.to_int(Os.env("R3D_DLSS_PRESET")) } + if r3d_env_has("R3D_DLSS_PRESET") { preset = Text.to_int(r3d_env("R3D_DLSS_PRESET")) } if preset > 0 { for f in 0 .. 6 { Vk.put_i32(gsl_opts, 60 + f * 4, preset) } } # dlaa, quality, balanced, performance, ultra performance, ultra quality } @@ -417,8 +417,8 @@ function gsl_constants() -> void { var jx = f_neg(gsl_jpx) var jy = f_neg(gsl_jpy) # R3D_DLSS_JX / R3D_DLSS_JY = -1 flip a sign, to check the convention against the picture - if Os.has_env("R3D_DLSS_JX") and Text.to_int(Os.env("R3D_DLSS_JX")) < 0 { jx = f_neg(jx) } - if Os.has_env("R3D_DLSS_JY") and Text.to_int(Os.env("R3D_DLSS_JY")) < 0 { jy = f_neg(jy) } + if r3d_env_has("R3D_DLSS_JX") and Text.to_int(r3d_env("R3D_DLSS_JX")) < 0 { jx = f_neg(jx) } + if r3d_env_has("R3D_DLSS_JY") and Text.to_int(r3d_env("R3D_DLSS_JY")) < 0 { jy = f_neg(jy) } Vk.put_i32(k, 352, jx) Vk.put_i32(k, 356, jy) Vk.put_i32(k, 360, F_ONE) # mvecScale diff --git a/packages/ludic.render3d/terrain.ludic b/packages/ludic.render3d/terrain.ludic index 9d7da8c7..9c6dac9b 100644 --- a/packages/ludic.render3d/terrain.ludic +++ b/packages/ludic.render3d/terrain.ludic @@ -384,7 +384,7 @@ function terrain_load_textures() -> void { ter_tex[12] = tex_load(a + "aerial_grass_rock_disp_2k.png", false) ter_tex[13] = tex_load(a + "cliff_side_diff_2k.png", true) ter_tex[14] = tex_load(a + "cliff_side_nor_gl_2k.png", false) - if Os.has_env("R3D_TEXDBG") { for i in 0 .. 15 { print(`ter_tex[{string(i)}] = {string(ter_tex[i])}`) } } + if r3d_env_has("R3D_TEXDBG") { for i in 0 .. 15 { print(`ter_tex[{string(i)}] = {string(ter_tex[i])}`) } } } function terrain_init() -> void { @@ -404,16 +404,16 @@ function terrain_init_step(i: int) -> void { function terrain_init_finish() -> void { cdlod_init() - ter_wire = Os.has_env("R3D_WIRE") - ter_force_far = Os.has_env("R3D_TFARONLY") - ter_no_split = Os.has_env("R3D_NOSPLIT") - ter_force_near = Os.has_env("R3D_TNEARONLY") - ter_skip = Os.has_env("R3D_NOTERRAIN") + ter_wire = r3d_env_has("R3D_WIRE") + ter_force_far = r3d_env_has("R3D_TFARONLY") + ter_no_split = r3d_env_has("R3D_NOSPLIT") + ter_force_near = r3d_env_has("R3D_TNEARONLY") + ter_skip = r3d_env_has("R3D_NOTERRAIN") var defs = "" if r3d_debug_shadow { defs = "#define DEBUG_SHADOW\n" } - if Os.has_env("R3D_DEBUG_MAT") { defs = "#define DEBUG_MAT\n" } - if Os.has_env("R3D_DEBUG_NRM") { defs = "#define DEBUG_NRM\n" } - if Os.has_env("R3D_DEBUG_ALB") { defs = "#define DEBUG_ALB\n" } + if r3d_env_has("R3D_DEBUG_MAT") { defs = "#define DEBUG_MAT\n" } + if r3d_env_has("R3D_DEBUG_NRM") { defs = "#define DEBUG_NRM\n" } + if r3d_env_has("R3D_DEBUG_ALB") { defs = "#define DEBUG_ALB\n" } # Elimination profiling. Measure these by FRAME TIME (prof_ft_report), not by the # per-pass GPU timers: this driver's timer queries attribute a pass's fragment work # almost arbitrarily, and will happily report a pass at a tenth of its cost. @@ -423,20 +423,20 @@ function terrain_init_finish() -> void { # R3D_NOTERRAIN skips the ground entirely (what it costs); R3D_TNEARONLY / R3D_TFARONLY # draw every patch with one tier's program (what each tier costs over a whole frame); # R3D_NOSPLIT goes back to the single program that holds both tiers. - if Os.has_env("R3D_TFAST") { defs = defs + "#define TFAST_" + Os.env("R3D_TFAST") + "\n" } + if r3d_env_has("R3D_TFAST") { defs = defs + "#define TFAST_" + r3d_env("R3D_TFAST") + "\n" } # Vulkan reads the cascades inside the ground's own shader and draws no separate sun pass # (terrain.frag, SUN_INLINE): one rasterisation of the patches instead of two, in the frame and in # the reflection. R3D_SUN_PASS=1 keeps the pass there too, for comparing. - ter_sun_inline = gpu_kind == GPU_VK and not Os.has_env("R3D_SUN_PASS") + ter_sun_inline = gpu_kind == GPU_VK and not r3d_env_has("R3D_SUN_PASS") if ter_sun_inline { defs = defs + "#define SUN_INLINE\n" } ter_prog = r3d_program("terrain.vert", "terrain.frag", defs) ter_prog_far = r3d_program("terrain.vert", "terrain.frag", defs + "#define FAR_ONLY\n") ter_prog_near = r3d_program("terrain.vert", "terrain.frag", defs + "#define NEAR_ONLY\n") ter_sun_prog = r3d_program("terrain.vert", "tersun.frag", "") ter_far_split = fi(200) - if Os.has_env("R3D_TFAR") { ter_far_split = fi(Text.to_int(Os.env("R3D_TFAR"))) } + if r3d_env_has("R3D_TFAR") { ter_far_split = fi(Text.to_int(r3d_env("R3D_TFAR"))) } ter_far_band = fi(60) - if Os.has_env("R3D_TBAND") { ter_far_band = fi(Text.to_int(Os.env("R3D_TBAND"))) } + if r3d_env_has("R3D_TBAND") { ter_far_band = fi(Text.to_int(r3d_env("R3D_TBAND"))) } ter_snow_line = fi(880) gpu_check("terrain init") } @@ -563,7 +563,7 @@ function terrain_sun_pass(w: int, h: int, depth: int) -> Target { gpu_mesh_bind(cd_mesh) cdlod_select(CD_LEVELS - 1, 0, 0) ter_sun_pass = false - if Os.has_env("R3D_DUMP_SUN") and not ter_sun_dumped and not ter_reflect { ter_sun_dumped = true; tex_dump(t.color, w, h, "build/dbg_sun.ppm") } + if r3d_env_has("R3D_DUMP_SUN") and not ter_sun_dumped and not ter_reflect { ter_sun_dumped = true; tex_dump(t.color, w, h, "build/dbg_sun.ppm") } return t } @@ -645,7 +645,7 @@ function cdlod_init() -> void { cd_mesh = m cd_range = words(CD_LEVELS) var r = fi(48) - if Os.has_env("R3D_CD_R0") { r = fi(Text.to_int(Os.env("R3D_CD_R0"))) } + if r3d_env_has("R3D_CD_R0") { r = fi(Text.to_int(r3d_env("R3D_CD_R0"))) } for l in 0 .. CD_LEVELS { cd_range[l] = r; r = f_mul(r, F_TWO) } cdlod_bounds() } diff --git a/packages/ludic.render3d/water.ludic b/packages/ludic.render3d/water.ludic index 922908e4..75b89496 100644 --- a/packages/ludic.render3d/water.ludic +++ b/packages/ludic.render3d/water.ludic @@ -37,7 +37,7 @@ var wb_primary: int = -1 # the body the reflection pass mirrors, o function water_reflection_pass() -> void { if wb_primary < 0 { return } # no body reflects: nothing to mirror if water_refl == null { - if Os.has_env("R3D_REFLDIV") { water_refl_div = Text.to_int(Os.env("R3D_REFLDIV")) } + if r3d_env_has("R3D_REFLDIV") { water_refl_div = Text.to_int(r3d_env("R3D_REFLDIV")) } if water_refl != null { target_free(water_refl) } # sized from the scene target, not the window: with a render scale below 1 the frame # this reflection is composited into is smaller than the drawable, and a reflection @@ -89,7 +89,7 @@ function water_reflection_pass() -> void { sc_skip_blade = sb sc_freeze = false gpu_cull_face(GL_BACK) - if Os.has_env("R3D_DUMP_REFL") and not water_dumped { water_dumped = true; tex_dump(water_refl.color, water_refl.w, water_refl.h, "build/dbg_refl.ppm") } + if r3d_env_has("R3D_DUMP_REFL") and not water_dumped { water_dumped = true; tex_dump(water_refl.color, water_refl.w, water_refl.h, "build/dbg_refl.ppm") } # restore r3d_clip_y = 0xCF000000 m4_copy(cam_view, water_saved) @@ -118,10 +118,10 @@ var wb_dbg: int = -1 var wb_build_us: long = 0 # how long the last water_cells_build took (R3D_REFL_DBG prints it) function water_reflect_visible() -> bool { if wb_primary < 0 { return false } - if wb_refl_always < 0 { wb_refl_always = 0; if Os.has_env("R3D_REFL_ALWAYS") { wb_refl_always = 1 } } + if wb_refl_always < 0 { wb_refl_always = 0; if r3d_env_has("R3D_REFL_ALWAYS") { wb_refl_always = 1 } } if wb_refl_always == 1 or ter_heights == null { return true } if wb_ncells < 0 { let t0 = gl_now_us(); water_cells_build(); wb_build_us = gl_now_us() - t0 } - if wb_dbg < 0 { wb_dbg = 0; if Os.has_env("R3D_REFL_DBG") { wb_dbg = 1 } } + if wb_dbg < 0 { wb_dbg = 0; if r3d_env_has("R3D_REFL_DBG") { wb_dbg = 1 } } if wb_dbg == 1 { # R3D_REFL_DBG: once, what the test is made of and how much of it is in view wb_dbg = 2