diff --git a/packages/ludic.render3d/scatter.ludic b/packages/ludic.render3d/scatter.ludic index 667b7254..39a41359 100644 --- a/packages/ludic.render3d/scatter.ludic +++ b/packages/ludic.render3d/scatter.ludic @@ -831,7 +831,7 @@ function layer_partition_lods(render3d_st: mut Render3dState, l: Layer, src: flo } l.n_near = counts[0] l.n_far = counts[n] - if render3d_st.sc_dbg_lod and total > 1000 { print(`lod partition: total {total} dropped {counts[n + 1]} far {counts[n]} l0 {counts[0]} l1 {counts[1]} l2 {counts[2]} l3 {counts[3]} dist0 {fixed(l.lod_dist[0])} dist3 {fixed(l.lod_dist[n - 1])} cull {fixed(l.cull)} cam {fixed(render3d_st.cam_pos[0])} {fixed(render3d_st.cam_pos[2])} first {fixed(src[0])} {fixed(src[2])}`) } + if render3d_st.sc_dbg_lod and total > 1000 { sc_debug_lods(render3d_st, l, counts, n, total, src) } if l.n_far > 0 { gpu_buffer_upload(render3d_st, l.imp_buf, l.n_far * INST_FLOATS * 4, mem_off(tmp, start[n] * INST_FLOATS * 4), GPU_DYNAMIC) } @@ -894,11 +894,7 @@ function layer_update(render3d_st: mut Render3dState, l: Layer) -> void { } l.n_near = nn l.n_far = nf - if l.imp != null and nn > 0 and render3d_st.sc_debug_dump { print(`near full-mesh instances: {nn} (first at {fixed(tmp[0])} {fixed(tmp[1])} {fixed(tmp[2])})`) } - if render3d_st.sc_debug_dump and l.imp != null { - print(`layer: near {nn} far {nf}`) - for k in 0 .. nn { let q = k * INST_FLOATS; print(` near {fixed(tmp[q])} {fixed(tmp[q + 1])} {fixed(tmp[q + 2])} s {fixed(tmp[q + 3])}`) } - } + if render3d_st.sc_debug_dump { sc_debug_layer(l, tmp, nn, nf) } # Every instance, unculled and unsplit, for the shadow pass. What the camera draws is # allowed to change with distance; what casts must not, or shadows blink in and out as # you walk. This is the whole set, drawn one way, into every cascade. @@ -1354,3 +1350,20 @@ function scatter_clear_all(render3d_st: mut Render3dState) -> void { render3d_st.sc_layers = new []Layer render3d_st.sc_view_gen += 1 } + +# R3D_DUMP_ATLAS / the LOD debug switch: what a layer holds, printed (text made only then) +@alloc_ok("debug output, only under R3D_DUMP_ATLAS and the LOD debug switch") +function sc_debug_lods(render3d_st: Render3dState, l: Layer, counts: words, n: int, total: int, src: floats) -> void { + print(`lod partition: total {total} dropped {counts[n + 1]} far {counts[n]} l0 {counts[0]} l1 {counts[1]} l2 {counts[2]} l3 {counts[3]} dist0 {fixed(l.lod_dist[0])} dist3 {fixed(l.lod_dist[n - 1])} cull {fixed(l.cull)} cam {fixed(render3d_st.cam_pos[0])} {fixed(render3d_st.cam_pos[2])} first {fixed(src[0])} {fixed(src[2])}`) +} +@alloc_ok("debug output, only under R3D_DUMP_ATLAS and the LOD debug switch") +function sc_debug_layer(l: Layer, tmp: floats, nn: int, nf: int) -> void { + if l.imp != null and nn > 0 { print(`near full-mesh instances: {nn} (first at {fixed(tmp[0])} {fixed(tmp[1])} {fixed(tmp[2])})`) } + if l.imp != null { + print(`layer: near {nn} far {nf}`) + for k in 0 .. nn { + let q = k * INST_FLOATS + print(` near {fixed(tmp[q])} {fixed(tmp[q + 1])} {fixed(tmp[q + 2])} s {fixed(tmp[q + 3])}`) + } + } +} diff --git a/packages/ludic.render3d/shadow.ludic b/packages/ludic.render3d/shadow.ludic index be7eabd8..41faebe0 100644 --- a/packages/ludic.render3d/shadow.ludic +++ b/packages/ludic.render3d/shadow.ludic @@ -147,7 +147,7 @@ function shadow_pass(render3d_st: mut Render3dState) -> void { prof_cpu_mark(render3d_st, "shadow fit") near = render3d_st.sh_split[c] gpu_fb_depth_layer(render3d_st, render3d_st.sh_tex, c) - if render3d_st.r3d_debug and c == 0 { let st = gpu_fb_status(render3d_st); print(`shadow fbo status {st}`) } + if render3d_st.r3d_debug and c == 0 { shadow_debug_fbo(render3d_st) } gpu_clear(render3d_st, GL_DEPTH_BUFFER_BIT) let vp = shadow_cascade_vp(render3d_st, c) # shadows off (a video setting): the cascades stay cleared, so everything reads lit @@ -159,30 +159,7 @@ function shadow_pass(render3d_st: mut Render3dState) -> void { gpu_depth_bias(render3d_st, 0.0, 0.0) gpu_fb_bind(render3d_st, 0) if render3d_st.r3d_debug_shadow { shadow_dump(render3d_st) } - if render3d_st.r3d_debug_shadow and not render3d_st.sh_printed2 { - render3d_st.sh_printed2 = true - let q = floats(3) - if render3d_st.sh_probe_x != 0.0 { - let vp = shadow_cascade_vp(render3d_st, 2) - m4_xform_point(q, vp, render3d_st.sh_probe_x, render3d_st.sh_probe_y, render3d_st.sh_probe_z) - print(`probe base ndc {fixed(q[0])} {fixed(q[1])} {fixed(q[2])}`) - m4_xform_point(q, vp, render3d_st.sh_probe_x, render3d_st.sh_probe_y + 15.0, render3d_st.sh_probe_z) - print(`probe top ndc {fixed(q[0])} {fixed(q[1])} {fixed(q[2])} -> map texel {int((q[0] * 0.5 + 0.5) * float(render3d_st.shadow_res))} {int((q[1] * 0.5 + 0.5) * float(render3d_st.shadow_res))}`) - # where the top's shadow lands on the ground: walk down the sun ray - let gx = render3d_st.sh_probe_x + 0.0 - render3d_st.sun_dir[0] * (15.0 / render3d_st.sun_dir[1]) - let gz = render3d_st.sh_probe_z - render3d_st.sun_dir[2] * (15.0 / render3d_st.sun_dir[1]) - m4_xform_point(q, vp, gx, terrain_height(render3d_st, gx, gz), gz) - print(`shadow-of-top ground ndc {fixed(q[0])} {fixed(q[1])} {fixed(q[2])} at {fixed(gx)} {fixed(gz)}`) - } - for c in 0 .. SHADOW_CASCADES { - let vp = shadow_cascade_vp(render3d_st, c) - # a point 5 m ahead of the camera on the ground - let px = render3d_st.cam_pos[0] + render3d_st.cam_fwd[0] * 5.0; let pz = render3d_st.cam_pos[2] + render3d_st.cam_fwd[2] * 5.0 - let w = m4_xform_point(q, vp, px, terrain_height(render3d_st, px, pz), pz) - print(`cascade {c}: ndc {fixed(q[0])} {fixed(q[1])} {fixed(q[2])} w {fixed(w)} m0 {fixed(vp[0])} m5 {fixed(vp[5])} m14 {fixed(vp[14])}`) - } - free(q) - } + if render3d_st.r3d_debug_shadow and not render3d_st.sh_printed2 { shadow_debug_probe(render3d_st) } } # Debug: cascade depths as grey PPMs (build/dbg_shadow_.ppm) @@ -254,12 +231,55 @@ function shadow_bind(render3d_st: mut Render3dState, prog: int) -> void { u_f2(render3d_st, gpu_uniform(render3d_st, prog, "u_ts_origin"), render3d_st.ter_ox, render3d_st.ter_oz) var loc = gpu_uniform(render3d_st, prog, "u_cascade_vp[0]") if loc < 0 { loc = gpu_uniform(render3d_st, prog, "u_cascade_vp") } - if render3d_st.r3d_debug_shadow and not render3d_st.sh_printed { render3d_st.sh_printed = true; print(`cascade vp loc {loc} / {gpu_uniform(render3d_st, prog, "u_cascade_vp")} split loc {gpu_uniform(render3d_st, prog, "u_cascade_split")} shadow loc {gpu_uniform(render3d_st, prog, "u_shadow")}`) } + if render3d_st.r3d_debug_shadow and not render3d_st.sh_printed { shadow_debug_locs(render3d_st, prog, loc) } u_mat4n(render3d_st, loc, SHADOW_CASCADES, render3d_st.sh_vp) u_fv(render3d_st, sh_loc(render3d_st, prog, "u_cascade_split[0]", "u_cascade_split"), SHADOW_CASCADES, render3d_st.sh_split) - if render3d_st.r3d_debug_shadow and not render3d_st.sh_printed3 { render3d_st.sh_printed3 = true; print(`range {fixed(render3d_st.sh_range[0])} {fixed(render3d_st.sh_range[1])} {fixed(render3d_st.sh_range[2])} {fixed(render3d_st.sh_range[3])} texel*1000 {fixed(render3d_st.sh_texel[0] * 1000.0)} {fixed(render3d_st.sh_texel[1] * 1000.0)} {fixed(render3d_st.sh_texel[2] * 1000.0)} {fixed(render3d_st.sh_texel[3] * 1000.0)} locs {gpu_uniform(render3d_st, prog, "u_cascade_range")} {gpu_uniform(render3d_st, prog, "u_cascade_texel")}`) } + if render3d_st.r3d_debug_shadow and not render3d_st.sh_printed3 { shadow_debug_range(render3d_st, prog) } u_fv(render3d_st, sh_loc(render3d_st, prog, "u_cascade_range[0]", "u_cascade_range"), SHADOW_CASCADES, render3d_st.sh_range) if r3d_env_has(render3d_st, "R3D_FORCE") { render3d_st.sh_force = Text.to_int(r3d_env(render3d_st, "R3D_FORCE")) } u_i(render3d_st, gpu_uniform(render3d_st, prog, "u_force_cascade"), render3d_st.sh_force) u_fv(render3d_st, sh_loc(render3d_st, prog, "u_cascade_texel[0]", "u_cascade_texel"), SHADOW_CASCADES, render3d_st.sh_texel) } + +# R3D_DEBUG / the shadow debug switch: what the passes found, said once each (text made only then) +@alloc_ok("debug output, only under R3D_DEBUG and the shadow debug switch") +function shadow_debug_fbo(render3d_st: mut Render3dState) -> void { + let st = gpu_fb_status(render3d_st) + print(`shadow fbo status {st}`) +} +@alloc_ok("debug output, only under R3D_DEBUG and the shadow debug switch") +function shadow_debug_probe(render3d_st: mut Render3dState) -> void { + render3d_st.sh_printed2 = true + let q = floats(3) + if render3d_st.sh_probe_x != 0.0 { + let vp = shadow_cascade_vp(render3d_st, 2) + m4_xform_point(q, vp, render3d_st.sh_probe_x, render3d_st.sh_probe_y, render3d_st.sh_probe_z) + print(`probe base ndc {fixed(q[0])} {fixed(q[1])} {fixed(q[2])}`) + m4_xform_point(q, vp, render3d_st.sh_probe_x, render3d_st.sh_probe_y + 15.0, render3d_st.sh_probe_z) + print(`probe top ndc {fixed(q[0])} {fixed(q[1])} {fixed(q[2])} -> map texel {int((q[0] * 0.5 + 0.5) * float(render3d_st.shadow_res))} {int((q[1] * 0.5 + 0.5) * float(render3d_st.shadow_res))}`) + # where the top's shadow lands on the ground: walk down the sun ray + let gx = render3d_st.sh_probe_x + 0.0 - render3d_st.sun_dir[0] * (15.0 / render3d_st.sun_dir[1]) + let gz = render3d_st.sh_probe_z - render3d_st.sun_dir[2] * (15.0 / render3d_st.sun_dir[1]) + m4_xform_point(q, vp, gx, terrain_height(render3d_st, gx, gz), gz) + print(`shadow-of-top ground ndc {fixed(q[0])} {fixed(q[1])} {fixed(q[2])} at {fixed(gx)} {fixed(gz)}`) + } + for c in 0 .. SHADOW_CASCADES { + let vp = shadow_cascade_vp(render3d_st, c) + # a point 5 m ahead of the camera on the ground + let px = render3d_st.cam_pos[0] + render3d_st.cam_fwd[0] * 5.0; let pz = render3d_st.cam_pos[2] + render3d_st.cam_fwd[2] * 5.0 + let w = m4_xform_point(q, vp, px, terrain_height(render3d_st, px, pz), pz) + print(`cascade {c}: ndc {fixed(q[0])} {fixed(q[1])} {fixed(q[2])} w {fixed(w)} m0 {fixed(vp[0])} m5 {fixed(vp[5])} m14 {fixed(vp[14])}`) + } + free(q) + +} +@alloc_ok("debug output, only under R3D_DEBUG and the shadow debug switch") +function shadow_debug_locs(render3d_st: mut Render3dState, prog: int, loc: int) -> void { + render3d_st.sh_printed = true + print(`cascade vp loc {loc} / {gpu_uniform(render3d_st, prog, "u_cascade_vp")} split loc {gpu_uniform(render3d_st, prog, "u_cascade_split")} shadow loc {gpu_uniform(render3d_st, prog, "u_shadow")}`) +} +@alloc_ok("debug output, only under R3D_DEBUG and the shadow debug switch") +function shadow_debug_range(render3d_st: mut Render3dState, prog: int) -> void { + render3d_st.sh_printed3 = true + print(`range {fixed(render3d_st.sh_range[0])} {fixed(render3d_st.sh_range[1])} {fixed(render3d_st.sh_range[2])} {fixed(render3d_st.sh_range[3])} texel*1000 {fixed(render3d_st.sh_texel[0] * 1000.0)} {fixed(render3d_st.sh_texel[1] * 1000.0)} {fixed(render3d_st.sh_texel[2] * 1000.0)} {fixed(render3d_st.sh_texel[3] * 1000.0)} locs {gpu_uniform(render3d_st, prog, "u_cascade_range")} {gpu_uniform(render3d_st, prog, "u_cascade_texel")}`) +}