ludic/packages/ludic.render3d/env.ludic
Orkuncakilkaya b9f9cb8495 render3d: r3d_fog_wall(dist) - fog that closes to the sky's horizon colour from 0.3 dist to dist, and nothing drawn past dist + 8 m
Every lit program blends toward the horizon colour (the prefiltered sky with the sun's inscatter,
so it carries the day's grade); the sky's horizon band is pulled to the same colour, wider the
closer the wall. The far plane, scatter and stream cull reach and the shadow cascades are clamped
to the wall; the reflection pass shares the shaders and the cull. 0 is off: every branch is gated
on u_fog_wall > 0 and r3d_reach hands back the far it was given. R3D_FOG_WALL=<m> for a shot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:07:40 +03:00

955 lines
42 KiB
Text

# 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.
export state Render3dState {
ac_lit: AcProg = null
ac_lit_cut: AcProg = null
ac_sh: AcProg = null
ac_sh_cut: AcProg = null
ac_out: AcProg = null
ac_out_cut: AcProg = null
@max(2048) ac_actors: []Actor = null
ac_next_id: int = 0
@max(2048) ac_spare: []Actor = null # released actors, taken again by actor_new (actor_release)
ac_frame: int = 0
ac_oq: []OutlineReq = null
ac_oq_n: int = 0 # live entries; the array is kept and reused
ac_oq_closed: bool = true
ac_census_at: int = -2
ac_proxy: Mesh = null # the capsule a figure casts in the far cascades (actor.ludic)
ac_proxy_s: floats = null # its scale and its world matrix, made with it
ac_proxy_m: floats = null
ac_lp0: floats = floats(3)
ac_lpx: floats = floats(3)
ac_lpy: floats = floats(3)
ac_lpz: floats = floats(3)
ac_cc_actors: int = 0
ac_cc_draws: int = 0
ac_cc_skinned: int = 0
ac_cc_cut: int = 0
cam_pos: floats = null # x, y, z
cam_yaw: float = 0.0 # radians, 0 = looking down -z
cam_pitch: float = 0.0
cam_fov: float = 0.0 # vertical, radians
cam_near: float = 0.0
cam_far: float = 0.0
cam_aspect: float = 0.0
cam_view: floats = null
cam_proj: floats = null
cam_vp: floats = null
cam_inv_vp: floats = null
cam_inv_proj: floats = null
cam_vp_clean: floats = null # view-projection, kept for depth reconstruction
cam_inv_vp_clean: floats = null
cam_fwd: floats = null
cam_right: floats = null
cam_planes: floats = floats(16)
day_on: bool = false
day_hours: float = 0.0 # float bits, 0 .. 24
day_light: float = 0.0 # 0 night .. 1 full day (float bits)
day_ibl: floats = null # rgb scale on the sky's light
day_sun_base: floats = null # the HDRI's sun radiance, kept
day_az0: float = 0.0 # the sun's azimuth at the reference hour (radians, yaw convention)
day_yaw0: float = 0.0 # the sky yaw the scene was tuned at
day_hour0: float = 0.0 # the hour the photograph was taken (10.5)
day_dir: float = 0.0 # +1 / -1: which way the sun travels in yaw
day_az: float = 0.0
day_el: float = 0.0
day_gen: int = 0 # bumps when the light moved enough to rebake the terrain shadow
day_baked_az: float = 0.0
day_baked_el: float = 0.0
day_sky_baked: float = 0.0 # the sky yaw the convolutions were baked at
fire_pos: floats = null
fire_color: floats = null
day_moon: bool = false
day_moon_phase: float = 0.5 # 0.5: full, which is where the game used to be
day_moon_illum: float = 1.0 # the lit fraction, derived from the phase
day_moon_dir: floats = null # toward the moon, whether or not it is up
hand_pos: floats = null
hand_color: floats = null
hand_dir: floats = null
hand_cone: float = 0.0
hand_reach: float = 0.0 # metres the hand light reaches (float bits)
day_overcast: float = 0.0 # 0 clear .. 1 a low grey sky (float bits): dims the sun and the sky's light
day_flash: float = 0.0 # a lightning flash this frame (0..1): the sun brightens for it
day_fog_mul: float = 1.0 # multiplies the base fog density (rain and snow thicken the air)
day_fog_base: float = 0.0
ds_on: int = -1 # -1: not read yet
ds_counting: bool = false
ds_from: int = 90
ds_frames: int = 60
ds_frame_no: int = 0
ds_outside: pointer = "(outside passes)"
ds_pass_names: []pointer = null
ds_pass_prog: []long = null # program changes inside the pass
ds_pass_tex: []long = null # texture binds inside the pass
ds_pass_i: int = 0
ds_row_pass: []int = null
ds_row_prog: []int = null
ds_row_kind: []int = null
ds_row_draws: []long = null
ds_row_inst: []long = null
ds_row_idx: []long = null
ds_last: int = -1
r3d_dev_v: int = -1
gltf_dir: string = null
gltf_bin: pointer = null
gltf_doc: Val = null
gltf_count: int = 0
gltf_ctype: int = 0
gltf_comps: int = 0
gltf_tex_paths: []pointer = null
gltf_tex_ids: words = null
gltf_tex_refs: words = null # how many primitives use each cached texture (model_release)
gltf_tex_n: int = 0
gltf_white: int = 0
gltf_flat: int = 0
gltf_mat_names: []string = null
gltf_mat_diff: words = null
gltf_mat_nrm: words = null
gltf_mat_arm: words = null
gltf_mat_n: int = 0
gltf_kept: []Model = null # gltf_cached's: a still model by dir, file and node, loaded once
gltf_kept_dir: []string = null
gltf_kept_file: []string = null
gltf_kept_node: []string = null
gltf_cutout: bool = false # the material being loaded is alpha-blended (a cut-out atlas)
gpu_kind: int = 0 # the backend running; 0 until gpu_select
gpu_wanted: int = 0 # what was asked for (setting or R3D_GFX)
gpu_fallback_reason: string = null # why the wanted backend is not the one running
gpu_s_depth_test: int = -1
gpu_s_depth_func: int = -1
gpu_s_depth_write: int = -1
gpu_s_blend: int = -1
gpu_s_blend_src: int = -1
gpu_s_blend_dst: int = -1
gpu_s_cull: int = -1
gpu_s_cull_face: int = -1
gpu_s_color_write: int = -1
gpu_s_a2c: int = -1
gpu_s_bias: int = -1
gpu_s_bias_f: float = 0.0 # float bits, for a backend that bakes the bias into a pipeline
gpu_s_bias_u: float = 0.0
gpu_s_scissor: int = -1
gpu_prog_ids: []int = null
gpu_prog_keys: []string = null
gpu_prog_cur: int = 0
gpu_u_tmp: words = words(4)
gpu_cap_probed: bool = false
gpu_cap_windows: bool = false # the advanced features exist on this platform at all
gpu_cap_vulkan: bool = false # a Vulkan loader, an instance and a device
gpu_cap_floor: bool = false # Vulkan 1.3 with everything the renderer's floor needs
gpu_cap_rt: bool = false # ray query + acceleration structures
gpu_cap_mesh: bool = false # VK_EXT_mesh_shader
gpu_cap_nvidia: bool = false
gpu_cap_rtx: int = 0 # the RTX generation (20, 30, 40, 50); 0 = not RTX
gpu_cap_reflex: bool = false # VK_NV_low_latency2
gpu_cap_hdr: bool = false # the instance offers HDR colour spaces
gpu_cap_device: string = ""
gpu_tx: words = null
gpu_tx_cap: int = 0
gpu_bound_2d: int = 0
gpu_bound_array: int = 0
gpu_unit_2d: words = null
gpu_unit_cur: int = 0
gpu_fb: words = null
gpu_fb_cap: int = 0
gpu_fb_cur: int = 0
gpu_rb_samples: int = 0
gpu_drawbufs: words = null
gpu_variants: []GpuVariant = null
gvk_ready: bool = false
gvk_inst: pointer = null
gvk_pd: pointer = null
gvk_dev: pointer = null
gvk_queue: pointer = null
gvk_family: int = -1
gvk_mp: bytes = null # VkPhysicalDeviceMemoryProperties
gvk_device_name: string = ""
gvk_sc_dead: int = 0 # textures released since the kept-set cache was emptied
gvk_read_px: bytes = null # gvk_read_screen's read-back, gvk_read_cap bytes, made again only at a new size
gvk_read_cap: int = 0
gvk_renderer_name: string = "" # "<device> (Vulkan)", made once with the device
gvk_why: string = "" # why the device did not come up, for the fallback notice
gvk_max_aniso: int = 0x3F800000 # the device's anisotropy limit as float bits; 1.0 when it has none
gvk_want_surface: bool = false # set before gvk_init when the renderer will present to a window
gvk_has_surface: bool = false # the instance and device can make a surface and a swapchain
gvk_errlog: string = null
gvk_errlog_read: bool = false
gvk_has_mdi: bool = false # multiDrawIndirect + drawIndirectFirstInstance
gvk_has_dic: bool = false # drawIndirectCount
gvk_has_mesh: bool = false # VK_EXT_mesh_shader with its meshShader feature on
gvk_mesh_fn: pointer = null # vkCmdDrawMeshTasksEXT: the device's own, the interposer exports none
gvk_n_allocs: int = 0
gvk_blk_mem: []long = null
gvk_blk_kind: []int = null # memory type * 2, + 1 for images
gvk_blk_size: []int = null
gvk_blk_map: []pointer = null
@max(4096) gvk_fr_blk: []int = null # free ranges: block, offset, length (0 = an unused slot)
@max(4096) gvk_fr_off: []int = null
@max(4096) gvk_fr_len: []int = null
gvk_al_blk: []int = null # per allocation id: its block, or -1 for its own memory
gvk_al_mem: []long = null # its own memory when it has one
gvk_al_off: []int = null
gvk_al_len: []int = null
gvk_al_map: []pointer = null
@max(4096) gvk_al_spare: []int = null # ids to reuse
gvk_pool: long = 0
gvk_fence: bytes = null
gvk_frame_fence: bytes = null # the presented frame still on the GPU (one frame in flight)
gvk_tmp_buf: pointer = null # the per-call scratch ring (gvk_tmp)
gvk_tmp_off: int = 0
gvk_seen: words = null # gvk_draw's vertex buffers already bound, reused
gvk_frame_pending: bool = false
gvk_frame_pending_cb: pointer = null
gvk_frame_pending_no: int = -1 # which frame it is (gvk_frame_no when it was submitted)
gvk_dpools: []long = null # a descriptor pool per frame slot (frame_no & 1)
gvk_ring_end: int = 0 # the end of this frame's half of the ring
@max(4096) gvk_retired_frame: []int = null # the last frame that read each retired buffer
gvk_labels: bool = false # R3D_VK_LABELS: each profiled pass is a debug label (Metal System Trace)
gvk_inflight: int = -1
gvk_nopool: bool = false
gvk_bc_said: int = 0 # attachments refused for being compressed, said so far
gvk_map_slot: []pointer = null # gvk_mem_new's mapped-pointer slot, made once
gvk_lay_sig: words = null # gvk_layout_id's scratch: one layout as numbers
gvk_lay_seen: words = null
gvk_lay_flat: []int = null # every layout known, end to end; gvk_lay_off / _len find each
gvk_lay_off: []int = null
gvk_lay_len: []int = null
@max(4096) gvk_tex_spare: []int = null # ids of freed textures, handed out again by gvk_tex_new
@max(4096) gvk_buf_spare: []int = null # the same for buffers (gvk_buf_delete)
gvk_has_bc: bool = false # textureCompressionBC: .dds textures are uploaded compressed
# R3D_VK_PROF: Vulkan objects made and destroyed, reported every 120 frames
gvk_mk_img: int = 0
gvk_mk_view: int = 0
gvk_mk_buf: int = 0
gvk_mk_mem: int = 0
gvk_mk_smp: int = 0
gvk_mk_set: int = 0
gvk_mk_cmd: int = 0
gvk_mk_dpool: int = 0
gvk_mk_x_img: int = 0
gvk_mk_x_view: int = 0
gvk_mk_x_buf: int = 0
gvk_mk_x_mem: int = 0
gvk_label_depth: int = 0 # -1 until asked: 1 leaves the presented frame in flight
gvk_has_hqr: bool = false
gvk_ts_period: float = 0.0 # float bits: nanoseconds per timestamp tick
gvk_qpool: long = 0
gvk_q_active: int = -1
gvk_prog_var: []GpuVariant = null
gvk_prog_vs: []long = null
gvk_prog_fs: []long = null
gvk_prog_dsl: []long = null
gvk_prog_layout: []long = null
gvk_spv_dir: string = ""
gvk_spv_len: int = 0
gvk_prog_mesh: []int = null
gvk_cp_pipe: []long = null
gvk_cp_layout: []long = null
gvk_cp_dsl: []long = null
gvk_cp_nbuf: []int = null
gvk_cp_ntex: []int = null
gvk_pipe_keys: []string = null
gvk_pipe: []long = null
gvk_zero_vbuf: int = 0
gvk_skip_said: words = null # per program: its skipped draws have been reported
gvk_ublk_v: []bytes = null # per program: the vertex stage's block as last set
gvk_ublk_f: []bytes = null
gvk_prog_tex: []words = null # per program: the texture bound to each manifest sampler
gvk_prog_unit: []words = null # per program: the unit + 1 each sampler was pointed at with u_i
gvk_prog_dirty: []int = null # per program: its blocks or textures changed since its last set
gvk_set_last: []long = null # per program: the descriptor set its last draw used
gvk_set_frame: []int = null # per program: the frame that set belongs to
gvk_set_tex: []words = null # per program: the textures that set was filled with
gvk_ring_buf: int = 0 # a gvk_buf handle
gvk_ring_off: int = 0
gvk_ring_align: int = 256
gvk_dpool: long = 0
gvk_white: int = 0 # a 1x1 white texture for a sampler nothing was bound to
gvk_msaa_max: int = 0 # samples the scene may use (gvk_frame_init reads the device's limits)
gvk_kpool: long = 0
gvk_sc_prog: []int = null # per cached set: its program
gvk_sc_koff: []int = null # per cached set: where its key starts in gvk_sc_keys
gvk_sc_set: []long = null
gvk_sc_next: []int = null # the program's next cached set, -1 at the end
gvk_sc_keys: []long = null # per texture of a key: handle * 65536 + generation, sampler
gvk_sc_head: words = null # per program (< 4096): its most recently made set, -1 if none
@max(130) gvk_sc_tmp: []long = null # this draw's key while it is looked up
gvk_set_offs: bytes = null # this draw's dynamic offsets, in binding order
gvk_set_ndyn: int = 0
@max(4096) gvk_ub_frame: []int = null # per program: the frame its blocks were last copied into the ring
@max(4096) gvk_ub_offv: []int = null
@max(4096) gvk_ub_offf: []int = null
gvk_sc_made: int = 0 # sets made since start (R3D_VK_PROF)
gvk_default_smp: long = 0 # linear, clamped: for a texture slot nothing was bound to
gvk_cb: pointer = null
gvk_in_pass: bool = false
gvk_fb_cur: int = 0
gvk_fb_read: int = 0
gvk_fb_draw: int = 0
gvk_pass_w: int = 0
gvk_pass_h: int = 0
gvk_pass_ncolor: int = 0
gvk_pass_cfmt: int = 0
gvk_pass_dfmt: int = 0
gvk_pass_samples: int = 1 # the attachments' sample count, which every pipeline in the pass must match
gvk_pass_col: words = null # the pass's colour attachments: texture, layer + 1
gvk_pass_dep: words = null # its depth attachment: texture, layer + 1
gvk_clear_bits: int = 0 # GL_COLOR_BUFFER_BIT / GL_DEPTH_BUFFER_BIT waiting for the pass
gvk_clear_rgba: floats = null # float bits
gvk_vp: words = null # x, y, w, h
gvk_sc: words = null # scissor on, x, y (OpenGL rows), w, h
gvk_screen_color: int = 0
gvk_screen_depth: int = 0
gvk_screen_w: int = 0
gvk_screen_h: int = 0
gvk_layer_views: []int = null # generation * 4096 + layer, beside gvk_layer_view_tex
gvk_layer_view: []long = null
gvk_layer_view_tex: []int = null # the texture each cached view is of, so a release drops them
gvk_fb_ncolor: words = null # per framebuffer: colour slots drawn (draw buffers; 0 = none)
gvk_hdr_want: bool = false # r3d_hdr: the setting
gvk_hdr_on: bool = false # the swapchain is HDR10 now
gvk_has_colorspace: bool = false # the instance took VK_EXT_swapchain_colorspace
gvk_has_hdr_meta: bool = false # the device took VK_EXT_hdr_metadata
r3d_hdr_peak: float = 0.0 # 0 until r3d_hdr_calibrate: 1000
r3d_hdr_paper: float = 0.0 # 200
r3d_hdr_black: float = 0.0 # 0
gvk_fb_counter: int = 0
gvk_prog_counter: int = 0
gvk_wireframe: int = 0
gvk_state: GvkState = null
gvk_ind_buf: int = 0
gvk_ind_off: int = 0
gvk_ind_n: int = 0
gvk_ind_cbuf: int = 0
gvk_ind_coff: int = 0
gvk_mesh_x: int = 0
gvk_mesh_y: int = 0
gvk_mesh_z: int = 0
gvk_surface: long = 0
gvk_swap: long = 0
gvk_swap_n: int = 0
gvk_swap_images: bytes = null
gvk_swap_fmt: int = 0
gvk_swap_w: int = 0
gvk_swap_h: int = 0
gvk_vsync: bool = true
gvk_acq_fence: bytes = null
gvk_swap_stale: bool = false
gvk_size_buf: words = null
gvk_prof_state: int = -1
gvk_us_pipe: long = 0
gvk_us_set: long = 0
gvk_us_draw: long = 0
gvk_n_draws: int = 0
gvk_n_asked: int = 0 # draws that reached gvk_draw_now: every one the renderer asked for
gvk_n_flush: int = 0
gvk_prof_frames: int = 0
gvk_n_pipe_new: int = 0 # pipelines made this profile window
gvk_pc_prog: []int = null
gvk_pc_layout: []int = null
gvk_pc_state: []int = null
gvk_pc_bias: []int = null
gvk_pc_pass: []int = null
gvk_pc_pipe: []long = null
gvk_pc_last: words = null
gvk_tex_image: []long = null
gvk_tex_view: []long = null
gvk_tex_mem: []long = null
gvk_tex_levels: []int = null
gvk_tex_layers: []int = null
gvk_tex_vkfmt: []int = null
gvk_tex_dims_w: []int = null
gvk_tex_dims_h: []int = null
gvk_tex_glfmt: []int = null # the format the renderer asked for (gpu.ludic's vocabulary)
gvk_tex_array: []int = null # 1 for an array image
gvk_tex_gen: []int = null # bumped whenever the image behind a handle is replaced
gvk_tex_smp_sig: []int = null # the sampling parameters the cached sampler was made for
gvk_tex_smp: []long = null
gvk_tex_samples: []int = null # 1, or the sample count of a multisampled renderbuffer
gvk_storage_samples: int = 1 # what the next gvk_tex_storage makes: gpu_rb_storage sets it around its call
gvk_unpack_swap: bool = false # GL_UNPACK_SWAP_BYTES: 16-bit PNG samples arrive big-endian
gvk_st_buf: long = 0
gvk_st_mem: long = 0
gvk_smp_keys: []int = null # 7 numbers per sampler: filters, wraps, compare, anisotropy, bias
gvk_smp: []long = null
gvk_buf: []long = null
gvk_buf_mem: []long = null
gvk_buf_size: []int = null
gvk_buf_map: []pointer = null # host-visible buffers stay mapped for their whole life
gvk_buf_used: []int = null # the frame (gvk_frame_no) a draw last bound the buffer in
gvk_bsp_buf: []long = null # per handle, a second buffer a re-filled one swaps with (gvk_buf_reserve)
gvk_bsp_mem: []long = null
gvk_bsp_size: []int = null
gvk_bsp_map: []pointer = null
gvk_bsp_used: []int = null
gvk_frame_no: int = 1
@max(4096) gvk_retired_buf: []long = null
@max(4096) gvk_retired_mem: []long = null
@max(16384) gvk_buf_gpu: []int = null
grass_prog: int = 0
grass_mesh: Mesh = null
grass_on: bool = true
grass_wind: float = 0.0
grass_s0: float = 0.0 # float bits: blade spacing at the camera (m)
grass_d0: float = 0.0 # the distance at which the spacing has doubled (m)
grass_radius: float = 0.0 # no blades past this (m)
grass_draws: int = 0
grass_dbg: int = 0
grass_merge: bool = false
grass_rec: words = null # VkDrawIndexedIndirectCommand records, 5 words each
grass_tv: floats = null # per record: corner x, corner z, indices per cell, 0 (float bits)
grass_chunk_tv: floats = null
grass_n: int = 0
grass_cmds: int = 0
grass_band_start: words = null # per band: its first record
grass_band_cells: words = null # ... and its cells per tile side
grass_band_n: int = 0
gg_on: bool = false # the blades are culled on the GPU (grass_gpu.ludic)
gg_reset: int = 0
gg_cull: int = 0
gg_prog: int = 0
gg_tiles_buf: []int = null
gg_tv: floats = null
gg_out: int = 0
gg_cmds: int = 0
gg_mesh: []Mesh = null
gg_n: int = 0
gg_max: int = 0
gg_rb: words = null # the per-frame dispatch arguments, made once (grass_gpu.ludic)
gg_cb: words = null
gg_bufs: words = null
gg_texs: words = null
gg_pr: words = null
grass_band_mesh: []Mesh = null # per band: the blade it draws (rows fall with distance)
grass_mesh3: Mesh = null # three rows: the middle distance
grass_mesh2: Mesh = null # two rows, one quad: far enough that the arch is under a pixel
grass_mesh_on: bool = false
grass_mesh_prog: int = 0
r3d_draw_cb: fn() = null
r3d_casters_cb: fn(floats) = null
r3d_fill_cb: fn(Stream, int, int, int) = null
ov_prog: int = 0
ov_mesh: Mesh = null
ov_vbo: int = 0
ov_buf: pointer = null
ov_n: int = 0
ov_tex: int = 0
ov_mode: int = 2 # what the next quads read: 0 the image texture, 1 the font, 2 a flat colour
ov_voff: float = 0.0 # float bits: 4 x ov_mode, added to v (overlay.frag)
ov_white: int = 0
ov_font: int = 0
ov_font_adv: floats = null # float bits, em units, one per glyph in the atlas
ov_font_n: int = 0 # glyphs in the atlas
ov_font_map: words = null
ov_font_hi_cp: words = null # code points >= OV_MAP_N, ascending
ov_font_hi_g: words = null
ov_font_hi_n: int = 0
ov_font_space: int = 0 # the glyph for ' ', and for what has none: '?'
ov_font_qmark: int = 0
ov_font_cols: int = 16
ov_font_rows: int = 6
ov_font_cell: int = 128 # px per cell in the atlas
ov_font_em: int = 100 # px per em in the atlas
ov_pad_x: float = 0.0 # float bits, em
ov_base_y: float = 0.0
ov_ready: bool = false
ov_open: bool = false
ov_dbg: bool = false
ov_ranges: words = null
ov_nr: int = 0
ov_range_start: int = 0
ov_clip_x: int = 0 # the current clip rectangle in screen pixels (top-left origin)
ov_clip_y: int = 0
ov_clip_w: int = 0
ov_clip_h: int = 0
ov_prog_sdr: int = 0
ov_prog_hdr: int = 0
ov_hdr_scale: float = 0.0 # float bits; 0 until ov_begin sets 1
ov_u8_len: int = 1
post_hdr: Target = null
post_ms_fbo: int = 0 # 4x multisampled scene target, resolved into post_hdr
post_ms_samples: int = 1 # temporal AA carries the edges; R3D_MSAA=n to compare
post_bloom: []Target = null
post_p_down: int = 0
post_p_up: int = 0
post_p_tone: int = 0
post_fs: Mesh = null
post_exposure: float = 0.0
post_bloom_strength: int = 0
post_vignette: float = 0.0
post_saturation: float = 0.0
post_contrast: float = 0.0
post_w: int = 0
post_h: int = 0
post_key: float = 0.0 # target mean luminance after exposure (float bits)
post_lum: words = null
post_mips: int = 0
post_adapt: float = 0.0 # smoothed exposure (float bits)
post_exposure_max: float = 20.0 # 20: the ceiling auto-exposure may reach (night lowers it)
post_wb_r: float = 1.02 # 1.02
post_wb_g: float = 1.0 # 1.0
post_wb_b: float = 0.97 # 0.97
post_lift_r: float = 0.004 # 0.004
post_lift_g: float = 0.004 # 0.004
post_lift_b: float = 0.012 # 0.012
post_gain_r: float = 0.99 # 0.99
post_gain_g: float = 0.995 # 0.995
post_gain_b: float = 1.0 # 1.0
post_ao: Target = null
post_ao_blur: Target = null
post_p_ao: int = 0
post_p_ao_blur: int = 0
post_ao_radius: float = 0.0
post_ao_intensity: float = 0.0
post_ao_strength: float = 0.0
post_gi_strength: float = 0.4 # 0.4
post_no_gi: bool = false
post_ldr: Target = null
post_depth_copy: Target = null
post_vol: Target = null
post_p_vol: int = 0
post_vol_density: float = 0.002 # 0.002
post_vol_falloff: float = 0.005 # 0.005
post_vol_mist: float = 0.0 # the day sets these two
post_prev: Target = null # last frame's scene colour, for the SSGI bounce only
post_scene: Target = null # this frame's scene colour before the water, for refraction
post_frame: int = 0
post_color_w: int = 0 # its size: the display's when DLSS upscaled it
post_color_h: int = 0
post_color: int = 0 # the HDR colour the rest of post reads # the resolved depth, copied so passes can read it while drawing into the frame
post_p_sharp: int = 0
post_p_dof: int = 0
post_dof: Target = null
post_p_tone_hdr: int = 0 # the tonemap's HDR10 variant, made the first time HDR is on
post_ldr_hdr: bool = false # post_ldr was made for HDR10 output (10-bit)
post_sharpen: float = 0.0
post_grain: int = 0
post_adapt_t: []Target = null
post_adapt_i: int = 0
post_p_adapt: int = 0
post_adapt_reset: bool = true
prof_on: bool = false
prof_names: []pointer = null
prof_ids: words = null # PROF_SLOTS * PROF_RING query objects
prof_ns: []long = null # accumulated nanoseconds per slot
prof_hits: []long = null # samples accumulated per slot
prof_n: int = 0 # slots in use
prof_frame: int = 0
prof_active: int = -1 # slot whose query is currently open
prof_scratch: words = null
prof_gen: []int = null
prof_gen_cur: int = 0
prof_ft: []long = null # real frame times, microseconds
prof_last_us: long = 0
prof_chunk_us: []long = null
prof_chunk_kind: []int = null
prof_chunk_band: []int = null
prof_chunk_n: []int = null
prof_gen_us_cur: long = 0
prof_gen_us: []long = null # generation microseconds per frame
prof_layer_us_cur: long = 0 # rebuilding and uploading instance buffers
prof_bake_us_cur: long = 0 # the height-field shadow rebake
prof_layer_us: []long = null
prof_bake_us: []long = null
prof_dt: []long = null # frame time, aligned with the arrays above
prof_up_cur: long = 0 # instance bytes uploaded this frame
prof_up: []long = null
prof_pre_swap: long = 0
prof_cpu_us: []long = null
prof_sum_dt: long = 0 # sums over the frames past the first eight, for the mean split
prof_sum_cpu: long = 0
prof_sum_game: long = 0
prof_sum_n: int = 0
prof_mark_last: long = 0
prof_mark_best: long = 0
prof_mark_name: pointer = null
prof_mark_us: []long = null
prof_mark_who: []pointer = null
prof_mk_names: []pointer = null
prof_mk_us: []long = null
prof_mk_n: int = 0
prof_game_us_cur: long = 0
prof_game_us: []long = null
r3d_root: string = "packages/ludic.render3d" # where shaders/ lives
r3d_assets: string = "assets/polyhaven" # where the CC0 assets live
r3d_root_found: bool = false
r3d_prog_log: int = -1
q_scratch: floats = floats(16)
q_sy: floats = null # views of q_scratch's second, third and fourth
q_sz: floats = null
q_st: floats = null
r3d_sky_prog: int = 0
r3d_fog_wall: float = 0.0 # r3d_fog_wall: > 0, nothing past it is seen or drawn
r3d_fog_density: float = 0.0
r3d_fog_falloff: float = 0.0
r3d_fog_inscatter: float = 0.02 # 0.02, what the shader used to hard-code
r3d_fog_desat: float = 1.35 # 1.35
r3d_sky_relight: float = 0.0
r3d_time: float = 0.0
r3d_ready: bool = false
r3d_plate: bool = false
r3d_small: bool = false # set before r3d_open: a small UI window (a launcher) - the ring, the memory blocks and the descriptor pools sized for it
r3d_sky_path: string = null
r3d_debug: bool = false
r3d_debug_shadow: bool = false
r3d_clip_y: float = -2147483600.0 # -2^31: no clipping
r3d_prepass_env: int = -1
r3d_test_frame: int = 0
r3d_test_resize: int = 0
r3d_no_shadow: bool = false
r3d_no_trees: bool = false
r3d_no_refl: bool = false
r3d_cam_log: int = -1
sc_prog: int = 0
sc_prog_fol: int = 0
sc_prog_wind: int = 0
sc_prog_blade: int = 0
sc_prog_flower: int = 0
sc_prog_card: int = 0
sc_prog_card_shadow: int = 0
sc_prog_card_cheap: int = 0
sc_blade_base: floats = null
sc_blade_tip: floats = null
sc_blade_tint: floats = null
sc_prog_shadow: int = 0
sc_prog_shadow_wind: int = 0
sc_prog_shadow_fol: int = 0 # foliage meshes: alpha-tested casters
sc_prog_fol_depth: int = 0
sc_prog_fol_eq: int = 0
sc_prepass: bool = false # the main pass is drawing over what the prepass laid down
sc_imp_prog: int = 0
sc_imp_prog_shadow: int = 0
sc_bake_prog: int = 0
sc_bake_card_prog: int = 0
sc_bake_flower_prog: int = 0
sc_card: Mesh = null
sc_ident_buf: int = 0
sc_layers: []Layer = null
sc_debug_dump: bool = false
sc_printed: bool = false
sc_dump_n: int = 0
sc_view_gen: int = 1
sc_view_pos: floats = null
sc_view_fwd: floats = null
sc_cull_prog: int = 0
sc_cull_tried: bool = false
sc_ind_base: int = -1 # >= 0: layer_draw_model / layer_draw_depth draw records from here
sc_ind_n: int = 1 # records each of those draws covers: one per level of a material
sc_ind_stride: int = 4 # records between one material's first and the next's (3 for the shadow LOD)
sc_freeze: bool = false # a secondary pass (reflection) reuses the partition
sc_dbg_level: int = -1
sc_dbg_lod: bool = false
sc_dbg_tint: floats = null
sc_cast_all: int = -1
sc_cast_gen: int = -1
sc_cast_dy: float = 0.0
sc_cast_k: float = 0.0
sc_skip_blade: bool = false
sc_skip_grass: bool = false
grass_force: bool = false # R3D_BLADES: the blades on whatever the game set
grass_env_off: bool = false # R3D_NOBLADES: the blades off whatever the game set
sc_skip_card: bool = false
cb_state: int = 12345
shadow_res: int = 2048
shadow_refused: int = 0 # the last size the graphics card had no memory for (0: none)
sh_tex: int = 0
sh_fbo: int = 0
sh_vp: floats = null # the cascades' view-projections, 16 floats each
sh_vp_v: [][]float = null # a view of each
sh_split: floats = null # view-space far distance of each cascade
sh_range: floats = null # 4 light-frustum depth extents (metres)
sh_texel: floats = null # 4 shadow texel sizes (metres)
sh_tmp_proj: floats = null
sh_tmp_vp: floats = null
sh_tmp_inv: floats = null
sh_tmp_view: floats = null
sh_corner: floats = null
sh_cascade: int = 0 # the cascade being rendered (for casters that skip far ones)
sh_printed2: bool = false
sh_printed3: bool = false
sh_force: int = -1 # R3D_FORCE=<c> pins every pixel to cascade c (debug)
sh_printed: bool = false
sky_tex: int = 0
sky_w: int = 0
sky_h: int = 0
sky_irradiance: int = 0
sky_prefilter: int = 0 # GL_TEXTURE_2D_ARRAY
sky_brdf: int = 0
sun_dir: floats = null # toward the sun (float bits)
sun_color: floats = null # radiance (float bits)
sky_fullscreen: Mesh = null
sky_yaw: float = 0.0 # radians: the HDRI is turned by this about y
sky_rot_s: float = 0.0
sky_rot_c: float = 0.0
sun_hdri: floats = null # the sun direction as found in the file
sky_baked: bool = false # the IBL set exists, baked at sky_baked_yaw
sky_baked_yaw: float = 0.0
sky_p_irr: int = 0
sky_p_pre: int = 0
sky_p_brdf: int = 0
sky_prefilter_w: int = 512
STREAM_MAX_CHUNKS: int = 4096
stream_all: []Stream = null
stream_cap_read: bool = false
stream_no_evict: bool = false # R3D_NOEVICT: the old behaviour, for comparison
stream_walk_no: int = 0 # counts ring walks; a chunk's age is measured in these
stream_evictions: int = 0
stream_us_gen: long = 0 # generating new chunks (stream_fill)
stream_us_gather: long = 0 # copying cached chunks into the layer buffer
stream_us_walk: long = 0 # the ring walk itself
stream_walks: int = 0 # streams that walked their whole ring this frame
stream_debug_n: int = 0
stream_scratch: floats = null
gvk_pipe_bufs: words = null # a pipeline's vertex buffers, while it is made
gsl_up: floats = floats(3)
sh_fit_cview: floats = floats(3) # shadow_fit's scratch
sh_fit_corners: floats = floats(24)
sh_fit_center: floats = floats(3)
sh_fit_eye: floats = floats(3)
sh_fit_up: floats = floats(3)
sh_fit_out: floats = floats(16)
water_eye: floats = floats(3) # water_reflection_pass's scratch
water_mirror: floats = floats(16)
water_mv: floats = floats(16)
sc_lod_counts: words = words(64) # layer_partition_lods' scratch (SC_LOD_ROOM)
sc_lod_start: words = words(64)
sc_lod_fill: words = words(64)
sc_gpu_rec: words = words(29 * 5) # layer_gpu_prepare's (SC_RECS * 5) and layer_gpu_cull's
sc_gpu_zeros: words = words(5)
sc_gpu_pr: words = words(36)
sc_gpu_bufs: words = words(4) # the camera's up for DLSS's constants
ov_nine_buf: floats = floats(16) # ov_nine's corners and uvs
gvk_ac: pointer = null # the VkAllocationCallbacks every create and destroy is given (R3D_ALLOC_VK=1)
gvk_prime_b: words = null # buffers made since the frame began, read once so MoltenVK makes their
@max(16384) gvk_prime_h: []long = null # Metal buffers now (gvk_prime), and their handles
gvk_prime_n: int = 0
gvk_prime_dst: long = 0 # the 64 bytes those reads land in
gvk_prime_mem: long = 0
stream_loose: Chunk = null # the record for a chunk no stream's cache can keep this walk
stream_deadline: long = 0
gsl_on: bool = false # slInit succeeded: the interposer is the loader, the plugins are in
gsl_dlss_ok: bool = false # what this adapter can run, from slIsFeatureSupported
gsl_rr_ok: bool = false
gsl_fg_ok: bool = false
gsl_reflex_ok: bool = false
gsl_pcl_ok: bool = false
gsl_feats: bytes = null # kept alive: Preferences points at it
gsl_logdir: bytes = null # and at this
gsl_vp: bytes = null # sl::ViewportHandle 0, the one view
gsl_tok_buf: bytes = null
gsl_idx_buf: bytes = null
gsl_token: pointer = null # this frame's sl::FrameToken, owned by Streamline
gsl_frame_n: int = 0
gsl_said: bool = false # one failure message, not one a frame
gsl_fresh: bool = false # a token was taken since the last frame started (a present took it)
gsl_reflex_mode: int = 0 # 0 off, 1 low latency, 2 low latency + boost
gsl_reflex_applied: int = -1
gsl_f_sleep: pointer = null
gsl_f_marker: pointer = null
gsl_dlss_mode: int = 0 # 0 off, 1 DLAA, 2 quality, 3 balanced, 4 performance
gsl_opts: bytes = null
gsl_opt_mode: int = -1
gsl_opt_w: int = 0
gsl_opt_h: int = 0
gsl_rw: int = 0 # the render size DLSS asked for
gsl_rh: int = 0
gsl_set_mode: int = -1
gsl_set_w: int = 0
gsl_set_h: int = 0
gsl_mv: Target = null
gsl_out: Target = null
gsl_consts: bytes = null
gsl_tags: bytes = null
gsl_res: bytes = null
gsl_inputs: bytes = null
gsl_prev_vp: floats = null
gsl_reset: bool = true
gsl_jitter_x: float = 0.0 # float bits, NDC offsets the projection carries this frame
gsl_jitter_y: float = 0.0
gsl_jpx: float = 0.0 # float bits, the same in pixels
gsl_jpy: float = 0.0
gsl_eval_ok: bool = true # the last evaluate worked: only then is the next frame jittered
TERRAIN_HALF: int = 4096 # world half-size in metres (8 km square)
cd_mesh: Mesh = null
cd_range: floats = null # float bits: how far each level is drawn
cd_min: []floats = null # per level: min height of each patch (float bits)
cd_max: []floats = null
cd_draws: int = 0
cd_far_draws: int = 0
cd_near_draws: int = 0
ter_force_far: bool = false
ter_no_split: bool = false
ter_force_near: bool = false
ter_skip: bool = false
ter_height_tex: int = 0
ter_normal_tex: int = 0 # the baked surface normal, x and z in RG16F (y rebuilt when read)
ter_heights: floats = null # CPU copy, float bits, TERRAIN_RES^2
ter_reflect: bool = false # drawing the reflection: the mid mesh is plenty
ter_prog: int = 0
ter_prog_far: int = 0
ter_prog_near: int = 0 # the detailed tier alone (NEAR_ONLY)
ter_sun_prog: int = 0 # tersun.frag: sun visibility into a screen buffer
ter_sun_inline: bool = false # the ground's shader reads the cascades itself (Vulkan); no sun pass
ter_sun_tgt: Target = null # that buffer, at the frame's size
ter_sun_refl: Target = null # and at the reflection's, which is smaller
ter_sun_dumped: bool = false
ter_sun_checked: bool = false
ter_sun_done: bool = false # the caller already ran the pass (so it can time it)
ter_sun_pass: bool = false # selection is drawing the visibility pass
ter_sun_tex: int = 0
ter_prog_cur: int = 0 # the program currently bound during selection
ter_smooth: bool = false # generate the analytic test ground instead of a survey
ter_far_split: float = 0.0 # metres: beyond this the terrain takes its cheap far path (R3D_TFAR)
ter_far_band: float = 0.0 # half-width of the near/far blend (R3D_TBAND)
ter_snow_line: float = 0.0
ter_tex: words = null # 11 material textures (see terrain_bind)
ter_ox: float = 0.0 # world x/z of the terrain centre (float bits)
ter_oz: float = 0.0
ter_dem_tex: int = 0 # a real height map (16-bit), or 0 for the procedural valley
ter_dem_min: float = 0.0
ter_dem_max: float = 0.0
ter_dem_base: float = 0.0
ter_ortho_tex: int = 0 # a photograph of the same window, draped with distance
ter_carpet: int = 0 # the distant-grass carpet (carpet_bake), 0 = none
ter_shadow_tex: int = 0 # height-field sun shadow: RG32F (lowest lit height, occluder distance)
ter_shadow_yaw: float = 1000000000.0 # the sky yaw it was baked for
ter_shadow_gen: int = -1 # the daylight generation it was baked for
ter_shadow_prog: int = 0
ter_lake_level: float = 0.0 # a lake carved into the height map (float bits; ex = 0 → none)
ter_lake_cx: float = 0.0
ter_lake_cz: float = 0.0
ter_lake_ex: float = 0.0
ter_lake_ez: float = 0.0
ter_lake_carve: bool = true
ter_sea_level: float = 0.0
ter_sea_set: bool = false
ter_isle_cx: float = 0.0
ter_isle_cz: float = 0.0
ter_isle_r: float = 0.0
ter_isle_fall: float = 0.0
ter_isle_mode: int = 0
ter_ortho_px: pointer = null # the photograph on the CPU (RGB8, TERRAIN_RES^2) for placement rules
ter_ortho_w: int = 0
ter_ortho_c: int = 3
ter_h_scale: float = 0.0
ter_o_scale: float = 0.0
ter_bw: floats = floats(8)
ter_wire: bool = false
ter_printed: bool = false
ter_scr: floats = floats(16)
tex_w: int = 0 # the last decoded image
tex_h: int = 0
tex_channels: int = 0
tex_depth: int = 0 # bits per sample (8 or 16)
tex_file_len: int = 0
tex_anisotropy: fixed = 16.0
tex_size_ws: []int = new []int
tex_size_hs: []int = new []int
hdr_max_lum: float = 0.0 # float bits of the brightest texel (sun finding)
hdr_max_x: int = 0
hdr_max_y: int = 0
hdr_sun_r: float = 0.0 # irradiance (float bits) of everything above the IBL clip: the sun
hdr_sun_g: float = 0.0
hdr_sun_b: float = 0.0
hdr_clip: float = 0.0 # float bits; texels above this (per channel) feed the sun, not the IBL
tex_dump_alpha: bool = false
water_mesh: Mesh = null
water_prog: int = 0
water_level: float = 0.0
water_cx: float = 0.0
water_cz: float = 0.0
water_ex: float = 0.0
water_ez: float = 0.0
water_on: bool = false
water_refl: Target = null # the world mirrored in the surface, half resolution
water_refl_div: int = 2 # R3D_REFLDIV overrides: 2 = half res, 4 = quarter
water_saved: floats = floats(16 * 4 + 3) # the real camera's matrices, restored after the pass
water_saved_vp: floats = null # views of its second and third matrices
water_saved_ivp: floats = null
water_dumped: bool = false
wb_n: int = 0
wb_level: floats = null
wb_cx: floats = null
wb_cz: floats = null
wb_ex: floats = null
wb_ez: floats = null
wb_reflect: words = null
wb_primary: int = -1 # the body the reflection pass mirrors, or -1
wb_cells: floats = null # x0, z0, x1, z1 of each rectangle with water showing
wb_ncells: int = -1 # -1: not built for the current mirrored body
wb_blocks: words = null # x0, z0, x1, z1, first cell, cells past the last - of each block
wb_nblocks: int = 0
wb_refl_always: int = -1
wb_dbg: int = -1
wb_build_us: long = 0 # how long the last water_cells_build took (R3D_REFL_DBG prints it)
grass_blade_tex: int = 0
grass_blade_cols: int = 8
grass_push_x: float = 0.0
grass_push_z: float = 0.0
grass_push_r: float = 0.0
post_auto: bool = true
post_contact: float = 1.25 # 1.25: how hard a thing is darkened where it meets the ground
post_vol_steps: float = 24.0 # 24
post_vol_far: float = 800.0 # 800 m
post_vol_g: float = 0.6 # 0.6: air throws light forward
post_vol_mist_h: float = 40.0 # 40 m
post_fxaa: float = 1.0
post_dof_focus: float = 10.0 # 10 m
post_dof_aperture: float = 0.0 # 0 = no lens, and no pass
post_dof_max: float = 12.0 # 12 px
r3d_global_defs: string = ""
r3d_fog_scale: float = 1.0 # float bits: a setting's multiplier over the density the day sets
r3d_fog_base: float = 0.0 # the height fog is measured from (float bits); 0 = y = 0
r3d_ground_alb_r: float = 0.3
r3d_ground_alb_g: float = 0.34
r3d_ground_alb_b: float = 0.14
r3d_ground_hi_r: float = 0.289
r3d_ground_hi_g: float = 0.28
r3d_ground_hi_b: float = 0.26
r3d_ground_hi_y: float = 480.0 # 480
r3d_ground_hi_w: float = 150.0 # 150
r3d_dem_path: string = null
r3d_dem_min: float = 0.0
r3d_dem_max: float = 0.0
r3d_dem_base: float = 0.0
r3d_dem_ox: float = 0.0
r3d_dem_oz: float = 0.0
r3d_ortho_path: string = null
r3d_debug_max: bool = false
r3d_cloud_shadow: float = 0.5 # 0.5
sc_a2c: bool = true
sc_bake_flower: bool = false
sc_dbg_blade: int = 0
sc_skip_flower: bool = false
sh_enabled: bool = true
sh_skip_terrain: bool = false
sh_probe_x: float = 0.0
sh_probe_y: float = 0.0
sh_probe_z: float = 0.0
sky_sun_boost: float = 2.3 # 2.3: the photograph's thin cloud dims its sun; a crisper day wants more
sky_start_yaw: float = 0.0
STREAM_BUDGET_US: int = 2500 # microseconds of generation per frame; a setting may move it
stream_budget_left: int = 0
ter_wet: float = 0.0
ter_dem_blur: float = 0.0 # gaussian texels applied to the survey (0 for lidar; ~3 for 30 m data)
wt_wade_x: float = 0.0
wt_wade_z: float = 0.0
wt_wade_s: float = 0.0
}
function r3d_dev(render3d_st: mut Render3dState) -> bool {
if render3d_st.r3d_dev_v < 0 { if Os.has_env("R3D_DEV") and Os.env("R3D_DEV") != "0" { render3d_st.r3d_dev_v = 1 } else { render3d_st.r3d_dev_v = 0 } }
return render3d_st.r3d_dev_v == 1
}
function r3d_env_has(render3d_st: mut Render3dState, name: string) -> bool {
if is_windowed() and not r3d_dev(render3d_st) { return false }
return Os.has_env(name)
}
function r3d_env(render3d_st: mut Render3dState, name: string) -> string {
if is_windowed() and not r3d_dev(render3d_st) { return "" }
return Os.env(name)
}