feat(render3d): the meadow's blades are culled on the GPU - under 1.5 ms of a MoltenVK frame for all the grass

- grass_cull.comp decides each candidate blade once a frame (place, ground, density, water,
  slope, frustum, colour field) and writes survivors into three bands by distance, one indirect
  draw each; grass_inst.vert only bends and places the vertices. OpenGL keeps grass.vert's
  per-vertex path; R3D_GRASS_GPU=0 compares
- compute programs take sampled textures after their buffers (gpu_compute_tex / gpu_dispatch_tex),
  and every dispatch now records a compute-to-draw memory barrier
- the blades as a sward: 4 m cells, bands with five, three and one-quad blades, spacing doubling
  every 18 m to 70 m, never narrower than a pixel; lit facing the sun and leaning to the sky,
  shadow looked up above the ground (it read the terrain as its caster), a colour ramp that
  leaves only the sheath dark, clumps, dry patches and a tussock shade worked out per blade
- scatter layers flagged grass are skipped while the blades draw (and under R3D_NOGRASS);
  R3D_BLADES, R3D_NOBLADES win over the game's setting; R3D_GRASS_S0/D0 for measuring

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-27 15:38:43 +03:00
parent f815f3e3cf
commit cc384efee0
34 changed files with 798 additions and 212 deletions

View file

@ -58,6 +58,7 @@ function r3d_env_flags(render3d_st: mut Render3dState) -> void {
render3d_st.sc_skip_blade = r3d_env_has(render3d_st, "R3D_NOBLADES")
render3d_st.sc_skip_grass = r3d_env_has(render3d_st, "R3D_NOGRASS")
render3d_st.grass_force = r3d_env_has(render3d_st, "R3D_BLADES")
render3d_st.grass_env_off = r3d_env_has(render3d_st, "R3D_NOBLADES")
render3d_st.sc_skip_card = r3d_env_has(render3d_st, "R3D_NOCARDS")
render3d_st.sc_dbg_lod = r3d_env_has(render3d_st, "R3D_LODDBG")
if r3d_env_has(render3d_st, "R3D_ANISO") {
@ -208,6 +209,7 @@ function r3d_frame(render3d_st: mut Render3dState, time: float) -> void {
prof_cpu_mark(render3d_st, "shadow rebake")
stream_update_all(render3d_st)
prof_cpu_mark(render3d_st, "streaming")
gg_cull_frame(render3d_st) # before any pass: a dispatch inside one would split it
if not render3d_st.r3d_no_shadow { prof_begin(render3d_st, "shadow"); shadow_pass(render3d_st); prof_end(render3d_st) }
prof_cpu_mark(render3d_st, "shadow pass")
if render3d_st.water_on and not render3d_st.r3d_no_refl and water_reflect_visible(render3d_st) { prof_begin(render3d_st, "water reflection"); water_reflection_pass(render3d_st); prof_end(render3d_st) }