Merge the R3D_DEV gate: render3d's debug switches only in headless or R3D_DEV builds

This commit is contained in:
Orkun ÇAKILKAYA 2026-09-16 15:31:54 +03:00
commit 1a2259bdca
21 changed files with 122 additions and 74 deletions

View file

@ -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`.

View file

@ -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. splash appears, and every open goes to the filesystem exactly as before.
Packing is a shipping step, and it is invisible until you ship. 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 ## Signing and distribution
`ludic bundle` ad-hoc signs the result. On Apple silicon that is not optional `ludic bundle` ad-hoc signs the result. On Apple silicon that is not optional

View file

@ -274,7 +274,7 @@ var ac_census_at: int = -2
function actor_draw() -> void { function actor_draw() -> void {
if ac_actors == null { return } if ac_actors == null { return }
ac_frame += 1 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() } if ac_census_at > 0 and ac_frame == ac_census_at { actor_census() }
for i in 0 .. len(ac_actors) { for i in 0 .. len(ac_actors) {
let a = ac_actors[i] let a = ac_actors[i]

View file

@ -51,11 +51,11 @@ var ds_last: int = -1
function ds_enabled() -> bool { function ds_enabled() -> bool {
if ds_on < 0 { if ds_on < 0 {
ds_on = 0 ds_on = 0
if Os.has_env("R3D_DRAWSTATS") { if r3d_env_has("R3D_DRAWSTATS") {
ds_on = 1 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 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_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_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 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 shown += 1
} }
var path = "build/drawstats.csv" 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") let f = file_open(path, "wb")
if f == null { print(`r3d: drawstats could not write {path}`); return } 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" 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"

View file

@ -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)
}

View file

@ -48,7 +48,7 @@ function gpu_name(kind: int) -> string {
# caller can show the player (gpu_fallback_reason). # caller can show the player (gpu_fallback_reason).
function gpu_select() -> int { function gpu_select() -> int {
if gpu_kind != 0 { return gpu_kind } 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 } if gpu_wanted == 0 { gpu_wanted = GPU_GL }
gpu_kind = GPU_GL gpu_kind = GPU_GL
if gpu_wanted == GPU_VK { if gpu_wanted == GPU_VK {
@ -318,7 +318,7 @@ function gpu_caps_fake(kind: string) -> void {
function gpu_caps_probe() -> void { function gpu_caps_probe() -> void {
if gpu_cap_probed { return } if gpu_cap_probed { return }
gpu_cap_probed = true 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" gpu_cap_windows = Os.platform() == "windows"
if not gpu_cap_windows { return } if not gpu_cap_windows { return }
if Vk.open() == 0 { return } if Vk.open() == 0 { return }
@ -793,7 +793,7 @@ function gpu_fb_at(fb: int) -> int {
return fb * GPU_FB_W return fb * GPU_FB_W
} }
function gpu_glcheck_on() -> bool { 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 return gpu_glcheck == 1
} }
function gpu_check(tag: string) -> int { if gpu_kind == GPU_VK { return 0 }; return gl_check(tag) } function gpu_check(tag: string) -> int { if gpu_kind == GPU_VK { return 0 }; return gl_check(tag) }

View file

@ -35,7 +35,7 @@ function gvk_note(msg: string) -> void {
print(msg) print(msg)
if not gvk_errlog_read { if not gvk_errlog_read {
gvk_errlog_read = true 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 } if gvk_errlog == null { return }
let f = file_open(gvk_errlog, "ab") 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. # mesh-shader grass: the extension, and its meshShader feature asked for where the device has it.
# R3D_NO_MESH=1 leaves it off. # R3D_NO_MESH=1 leaves it off.
gvk_has_mesh = false 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) let fm = bytes(VkPhysicalDeviceMeshShaderFeaturesEXT_sizeof)
Vk.zero(fm, VkPhysicalDeviceMeshShaderFeaturesEXT_sizeof) Vk.zero(fm, VkPhysicalDeviceMeshShaderFeaturesEXT_sizeof)
Vk.put_i32(fm, VkPhysicalDeviceMeshShaderFeaturesEXT_sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT) Vk.put_i32(fm, VkPhysicalDeviceMeshShaderFeaturesEXT_sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT)

View file

@ -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 # R3D_HDR=1 / 0 overrides the setting, for a desktop test
function r3d_hdr(on: bool) -> void { function r3d_hdr(on: bool) -> void {
var want = on 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 } if want == gvk_hdr_want { return }
gvk_hdr_want = want gvk_hdr_want = want
if gvk_swap != 0 { gvk_swap_stale = true } 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_frame_init() { return false }
if not gvk_screen_make(gl_w, gl_h) { 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) } if is_windowed() { return gvk_swap_make(gl_w, gl_h) }
return true return true
} }
@ -1825,7 +1825,7 @@ var gvk_n_flush: int = 0
var gvk_prof_frames: int = 0 var gvk_prof_frames: int = 0
var gvk_n_pipe_new: int = 0 # pipelines made this profile window var gvk_n_pipe_new: int = 0 # pipelines made this profile window
function gvk_prof() -> bool { 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 return gvk_prof_state == 1
} }
function gvk_prof_frame() -> void { function gvk_prof_frame() -> void {

View file

@ -43,7 +43,7 @@ var grass_mesh_on: bool = false
var grass_mesh_prog: int = 0 var grass_mesh_prog: int = 0
function r3d_mesh_grass(on: bool) -> void { function r3d_mesh_grass(on: bool) -> void {
grass_mesh_on = on 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() } 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 { 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" var defs = "#define FOLIAGE\n#define BLADE\n"
if grass_merge { if grass_merge {
defs = defs + "#define TILES\n" defs = defs + "#define TILES\n"
@ -98,9 +98,9 @@ function grass_init() -> void {
grass_s0 = fl(0.11) grass_s0 = fl(0.11)
grass_d0 = fi(45) grass_d0 = fi(45)
grass_radius = fi(1600) grass_radius = fi(1600)
if Os.has_env("R3D_NOBLADES") { grass_on = false } if r3d_env_has("R3D_NOBLADES") { grass_on = false }
if Os.has_env("R3D_GRASS_R") { grass_radius = fi(Text.to_int(Os.env("R3D_GRASS_R"))) } if r3d_env_has("R3D_GRASS_R") { grass_radius = fi(Text.to_int(r3d_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_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) # indices per 16 m cell that could exist at distance d (the count the shader computes)

View file

@ -98,7 +98,7 @@ function overlay_init(font_dir: string) -> bool {
ov_white = tex_solid(255, 255, 255, 255) ov_white = tex_solid(255, 255, 255, 255)
overlay_font(font_dir) overlay_font(font_dir)
if ov_font == 0 { print("overlay: no font atlas, text disabled") } 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 ov_ready = true
return true return true
} }

View file

@ -341,7 +341,7 @@ function post_tonemap(color_tex: int) -> void {
u_f(gpu_uniform(post_p_sharp, "u_amount"), post_sharpen) 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 # R3D_NOGRAIN=1: no film grain, so two frames of a still camera can be compared for what else moves
var grain = post_grain 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_grain"), grain)
u_f(gpu_uniform(post_p_sharp, "u_time"), r3d_time) u_f(gpu_uniform(post_p_sharp, "u_time"), r3d_time)
mesh_draw(post_fs) mesh_draw(post_fs)

View file

@ -25,7 +25,7 @@ var prof_active: int = -1 # slot whose query is currently open
var prof_scratch: words = null var prof_scratch: words = null
function prof_init() -> void { function prof_init() -> void {
prof_on = Os.has_env("R3D_PROF") prof_on = r3d_env_has("R3D_PROF")
if not prof_on { return } if not prof_on { return }
prof_names = new []pointer prof_names = new []pointer
prof_ns = new []long prof_ns = new []long

View file

@ -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 # backend that cannot compile shaders at run time (Vulkan: SPIR-V is built ahead) must cover
var r3d_prog_log: int = -1 var r3d_prog_log: int = -1
function r3d_program_log(vs: string, fs: string, defines: string) -> void { 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 } 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 } if f == null { return }
# one line per program: the defines' newlines become ';' so a variant is one line # one line per program: the defines' newlines become ';' so a variant is one line
let defs = Text.replace(`{r3d_global_defs}{defines}`, "\n", ";") let defs = Text.replace(`{r3d_global_defs}{defines}`, "\n", ";")

View file

@ -2,6 +2,7 @@
# ludic.render3d — a physically based 3D renderer on Gl.* (OpenGL 4.1 core). # 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 this one file; the game supplies scene_draw() / scene_draw_casters().
# ============================================================================ # ============================================================================
import "env.ludic"
import "fmath.ludic" import "fmath.ludic"
import "gpu.ludic" import "gpu.ludic"
import "gpu_manifest.ludic" import "gpu_manifest.ludic"

View file

@ -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 # R3D_NOPREPASS=1: light the foliage the old way, every card behind the front one included
var r3d_prepass_env: int = -1 var r3d_prepass_env: int = -1
function r3d_prepass_off() -> bool { 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 return r3d_prepass_env == 1
} }
function fog_bind(prog: int) -> void { 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_height_falloff"), r3d_fog_falloff)
u_f(gpu_uniform(prog, "u_fog_base"), r3d_fog_base) u_f(gpu_uniform(prog, "u_fog_base"), r3d_fog_base)
var cs = r3d_cloud_shadow 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_cloud_shadow"), cs)
u_f(gpu_uniform(prog, "u_time"), r3d_time) 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_trees: bool = false
var r3d_no_refl: bool = false var r3d_no_refl: bool = false
function r3d_env_flags() -> void { function r3d_env_flags() -> void {
r3d_no_shadow = Os.has_env("R3D_NOSHADOW") r3d_no_shadow = r3d_env_has("R3D_NOSHADOW")
r3d_no_trees = Os.has_env("R3D_NOTREES") r3d_no_trees = r3d_env_has("R3D_NOTREES")
r3d_no_refl = Os.has_env("R3D_NOREFL") r3d_no_refl = r3d_env_has("R3D_NOREFL")
if Os.has_env("R3D_DEBUG") { r3d_debug = true } if r3d_env_has("R3D_DEBUG") { r3d_debug = true }
if Os.has_env("R3D_DBGSHADOW") { r3d_debug_shadow = true } if r3d_env_has("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 r3d_env_has("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")) } if r3d_env_has("R3D_MSAA") { post_ms_samples = Text.to_int(r3d_env("R3D_MSAA")) }
sc_skip_blade = Os.has_env("R3D_NOBLADES") sc_skip_blade = r3d_env_has("R3D_NOBLADES")
sc_skip_card = Os.has_env("R3D_NOCARDS") sc_skip_card = r3d_env_has("R3D_NOCARDS")
sc_dbg_lod = Os.has_env("R3D_LODDBG") sc_dbg_lod = r3d_env_has("R3D_LODDBG")
if Os.has_env("R3D_ANISO") { if r3d_env_has("R3D_ANISO") {
let a = Text.to_int(Os.env("R3D_ANISO")) let a = Text.to_int(r3d_env("R3D_ANISO"))
tex_anisotropy = 1.0 tex_anisotropy = 1.0
if a >= 2 { tex_anisotropy = 2.0 } if a >= 2 { tex_anisotropy = 2.0 }
if a >= 4 { tex_anisotropy = 4.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() r3d_env_flags()
gpu_select() gpu_select()
if not gpu_open(w, h, title) { print("r3d: no OpenGL context"); return false } 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() var renderer: string = gpu_renderer_name()
print(`r3d: {gl_w}x{gl_h} on {renderer}`) print(`r3d: {gl_w}x{gl_h} on {renderer}`)
prof_init() 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 # 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. # a stale attachment or a texture freed twice shows up.
r3d_test_frame += 1 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=<n>: from frame n on, rebuild every screen-sized buffer every few # R3D_RESIZE_AT=<n>: 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. # 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 { 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) 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 # 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 # 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 { 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}`) 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}`)
} }

View file

@ -356,7 +356,7 @@ var sc_a2c: bool = true
function scatter_init() -> void { function scatter_init() -> void {
# R3D_DUMP_ATLAS: every impostor and card atlas the run bakes, to build/atlas_<n>_{color,alpha}.ppm # R3D_DUMP_ATLAS: every impostor and card atlas the run bakes, to build/atlas_<n>_{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 = 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 = 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") 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 # 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) # on by default wherever there is compute; R3D_GPU_CULL=0 keeps the CPU partition (for comparing)
var off = false 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 { if gpu_has_compute() and gpu_has_mdi() and not off {
sc_cull_prog = gpu_compute("scatter_cull", 4) sc_cull_prog = gpu_compute("scatter_cull", 4)
if sc_cull_prog > 0 { print("r3d: scatter: tree layers are culled on the GPU") } 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 # 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). # (layer_level_casts_here), and so does every actor (actor_draw_casters).
function cast_band_reaches(dmin: int, dmax: int, height: int) -> bool { 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_all == 1 or sh_split == null { return true }
if sc_cast_gen != sc_view_gen { if sc_cast_gen != sc_view_gen {
sc_cast_gen = sc_view_gen sc_cast_gen = sc_view_gen

View file

@ -277,7 +277,7 @@ function shadow_bind(prog: int) -> void {
u_fv(sh_loc(prog, "u_cascade_split"), SHADOW_CASCADES, sh_split) 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")}`) } 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) 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_i(gpu_uniform(prog, "u_force_cascade"), sh_force)
u_fv(sh_loc(prog, "u_cascade_texel"), SHADOW_CASCADES, sh_texel) u_fv(sh_loc(prog, "u_cascade_texel"), SHADOW_CASCADES, sh_texel)
} }

View file

@ -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 { function stream_new(layer: Layer, size: int, reach: int, b0: int, b1: int, b2: int, b3: int) -> Stream {
if not stream_cap_read { if not stream_cap_read {
stream_cap_read = true stream_cap_read = true
if Os.has_env("R3D_STREAM_CAP") { STREAM_MAX_CHUNKS = Text.to_int(Os.env("R3D_STREAM_CAP")) } if r3d_env_has("R3D_STREAM_CAP") { STREAM_MAX_CHUNKS = Text.to_int(r3d_env("R3D_STREAM_CAP")) }
stream_no_evict = Os.has_env("R3D_NOEVICT") stream_no_evict = r3d_env_has("R3D_NOEVICT")
} }
let s = new Stream let s = new Stream
s.layer = layer; s.size = size; s.reach = reach s.layer = layer; s.size = size; s.reach = reach

View file

@ -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. # Before Vk.open(): ask the loader for the interposer. R3D_NO_STREAMLINE=1 keeps plain Vulkan.
function gsl_boot() -> void { function gsl_boot() -> void {
if Os.platform() != "windows" { return } 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) Vk.sl_prefer(1)
} }
@ -91,10 +91,10 @@ function gsl_init() -> void {
gsl_header(pref, 0x1ca1, 0x0965, 0xbf8e, 0x432b, 0x8da1, 0x6716, 0xd879, 0xfb14, 1) gsl_header(pref, 0x1ca1, 0x0965, 0xbf8e, 0x432b, 0x8da1, 0x6716, 0xd879, 0xfb14, 1)
# logLevel eOff; R3D_SL_LOG=<directory> writes Streamline's own verbose log (sl.log) there # logLevel eOff; R3D_SL_LOG=<directory> writes Streamline's own verbose log (sl.log) there
var level = 0 var level = 0
if Os.has_env("R3D_SL_LOG") { if r3d_env_has("R3D_SL_LOG") {
level = 2 level = 2
let dir: pointer = Os.env("R3D_SL_LOG") let dir: pointer = r3d_env("R3D_SL_LOG")
let n = Text.length(Os.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 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) } for i in 0 .. n { Vk.put_i32(gsl_logdir, i * 2, dir[i] & 255) }
Vk.put_ptr(pref, 56, gsl_logdir) Vk.put_ptr(pref, 56, gsl_logdir)
@ -159,7 +159,7 @@ var gsl_f_marker: pointer = null
function r3d_reflex(mode: int) -> void { function r3d_reflex(mode: int) -> void {
gsl_reflex_mode = mode 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 } 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 # R3D_DLSS=0..4 overrides the setting, for a headless take
function r3d_dlss(mode: int) -> void { function r3d_dlss(mode: int) -> void {
var m = mode 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 } if m != gsl_dlss_mode { gsl_reset = true }
gsl_dlss_mode = m 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=<n> # K's 2.8 - slower than no DLSS at all (33 fps against 41; with K, 60). R3D_DLSS_PRESET=<n>
# (sl::DLSSPreset: 11 K, 12 L, 13 M) sets every mode's, to measure them against each other. # (sl::DLSSPreset: 11 K, 12 L, 13 M) sets every mode's, to measure them against each other.
var preset = 11 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 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 jx = f_neg(gsl_jpx)
var jy = f_neg(gsl_jpy) var jy = f_neg(gsl_jpy)
# R3D_DLSS_JX / R3D_DLSS_JY = -1 flip a sign, to check the convention against the picture # 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 r3d_env_has("R3D_DLSS_JX") and Text.to_int(r3d_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_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, 352, jx)
Vk.put_i32(k, 356, jy) Vk.put_i32(k, 356, jy)
Vk.put_i32(k, 360, F_ONE) # mvecScale Vk.put_i32(k, 360, F_ONE) # mvecScale

View file

@ -393,7 +393,7 @@ function terrain_load_textures() -> void {
ter_tex[12] = tex_load(a + "aerial_grass_rock_disp_2k.png", false) 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[13] = tex_load(a + "cliff_side_diff_2k.png", true)
ter_tex[14] = tex_load(a + "cliff_side_nor_gl_2k.png", false) 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 { function terrain_init() -> void {
@ -413,16 +413,16 @@ function terrain_init_step(i: int) -> void {
function terrain_init_finish() -> void { function terrain_init_finish() -> void {
cdlod_init() cdlod_init()
ter_wire = Os.has_env("R3D_WIRE") ter_wire = r3d_env_has("R3D_WIRE")
ter_force_far = Os.has_env("R3D_TFARONLY") ter_force_far = r3d_env_has("R3D_TFARONLY")
ter_no_split = Os.has_env("R3D_NOSPLIT") ter_no_split = r3d_env_has("R3D_NOSPLIT")
ter_force_near = Os.has_env("R3D_TNEARONLY") ter_force_near = r3d_env_has("R3D_TNEARONLY")
ter_skip = Os.has_env("R3D_NOTERRAIN") ter_skip = r3d_env_has("R3D_NOTERRAIN")
var defs = "" var defs = ""
if r3d_debug_shadow { defs = "#define DEBUG_SHADOW\n" } if r3d_debug_shadow { defs = "#define DEBUG_SHADOW\n" }
if Os.has_env("R3D_DEBUG_MAT") { defs = "#define DEBUG_MAT\n" } if r3d_env_has("R3D_DEBUG_MAT") { defs = "#define DEBUG_MAT\n" }
if Os.has_env("R3D_DEBUG_NRM") { defs = "#define DEBUG_NRM\n" } if r3d_env_has("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_ALB") { defs = "#define DEBUG_ALB\n" }
# Elimination profiling. Measure these by FRAME TIME (prof_ft_report), not by the # 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 # 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. # almost arbitrarily, and will happily report a pass at a tenth of its cost.
@ -432,20 +432,20 @@ function terrain_init_finish() -> void {
# R3D_NOTERRAIN skips the ground entirely (what it costs); R3D_TNEARONLY / R3D_TFARONLY # 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); # 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. # 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 # 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 # (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. # 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" } if ter_sun_inline { defs = defs + "#define SUN_INLINE\n" }
ter_prog = r3d_program("terrain.vert", "terrain.frag", defs) 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_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_prog_near = r3d_program("terrain.vert", "terrain.frag", defs + "#define NEAR_ONLY\n")
ter_sun_prog = r3d_program("terrain.vert", "tersun.frag", "") ter_sun_prog = r3d_program("terrain.vert", "tersun.frag", "")
ter_far_split = fi(200) 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) 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) ter_snow_line = fi(880)
gpu_check("terrain init") gpu_check("terrain init")
} }
@ -573,7 +573,7 @@ function terrain_sun_pass(w: int, h: int, depth: int) -> Target {
gpu_mesh_bind(cd_mesh) gpu_mesh_bind(cd_mesh)
cdlod_select(CD_LEVELS - 1, 0, 0) cdlod_select(CD_LEVELS - 1, 0, 0)
ter_sun_pass = false 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 return t
} }
@ -655,7 +655,7 @@ function cdlod_init() -> void {
cd_mesh = m cd_mesh = m
cd_range = words(CD_LEVELS) cd_range = words(CD_LEVELS)
var r = fi(48) 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) } for l in 0 .. CD_LEVELS { cd_range[l] = r; r = f_mul(r, F_TWO) }
cdlod_bounds() cdlod_bounds()
} }

View file

@ -37,7 +37,7 @@ var wb_primary: int = -1 # the body the reflection pass mirrors, o
function water_reflection_pass() -> void { function water_reflection_pass() -> void {
if wb_primary < 0 { return } # no body reflects: nothing to mirror if wb_primary < 0 { return } # no body reflects: nothing to mirror
if water_refl == null { 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) } if water_refl != null { target_free(water_refl) }
# sized from the scene target, not the window: with a render scale below 1 the frame # 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 # 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_skip_blade = sb
sc_freeze = false sc_freeze = false
gpu_cull_face(GL_BACK) 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 # restore
r3d_clip_y = 0xCF000000 r3d_clip_y = 0xCF000000
m4_copy(cam_view, water_saved) 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) var wb_build_us: long = 0 # how long the last water_cells_build took (R3D_REFL_DBG prints it)
function water_reflect_visible() -> bool { function water_reflect_visible() -> bool {
if wb_primary < 0 { return false } 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_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_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 { if wb_dbg == 1 {
# R3D_REFL_DBG: once, what the test is made of and how much of it is in view # R3D_REFL_DBG: once, what the test is made of and how much of it is in view
wb_dbg = 2 wb_dbg = 2