From cdfffa62bbd6c98d77ab4aa71438c6dc087365d1 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Tue, 15 Sep 2026 16:58:53 +0300 Subject: [PATCH] render3d: grass in chunks of 256 tiles a draw, flower casters only into the cascades they reach Grass (Vulkan with multi-draw indirect): every visible tile is a record in one buffer, uploaded once a frame, and each band draws its records 256 at a time. A record's firstInstance is its place in the chunk times 65536; grass.vert's TILES variant reads that place's corner and indices per cell from u_tiles. R3D_GRASS_TILES=1 keeps a draw per tile. OpenGL is unchanged. Casters: a LOD level with no impostor is drawn into a shadow cascade only when its distance band, widened by six times its height, the camera's height over the ground and the frustum's corner reach, can touch that cascade's receivers. The flowers' mesh levels (6 - 30 m) leave the three outer cascades. R3D_CAST_ALL=1 draws every level everywhere. OpenGL frames byte-identical at all five viewpoints; alpha-tested shadow draws at a 460 -> 244. gpu_has_mdi() guards both this and the GPU-culled trees' multi-record draws. Camp: Mac Vulkan 2645 -> 2191 (grass) -> 2034 draws; PC 2657 -> 2046, self-tests 59/59, validation 0. Co-Authored-By: Claude Opus 5 --- packages/ludic.render3d/gpu.ludic | 2 + packages/ludic.render3d/grass.ludic | 70 ++++++++++++++++- packages/ludic.render3d/scatter.ludic | 47 +++++++++++- packages/ludic.render3d/shaders/grass.vert | 20 ++++- .../shaders/spv/7ed7de52.vert.spv | Bin 23560 -> 23664 bytes .../shaders/spv/7f096322.frag.spv | Bin 0 -> 45536 bytes .../shaders/spv/7f096322.vert.spv | Bin 0 -> 23996 bytes .../ludic.render3d/shaders/spv/manifest.txt | 71 ++++++++++++++++++ packages/ludic.render3d/shaders/variants.list | 1 + 9 files changed, 202 insertions(+), 9 deletions(-) create mode 100644 packages/ludic.render3d/shaders/spv/7f096322.frag.spv create mode 100644 packages/ludic.render3d/shaders/spv/7f096322.vert.spv diff --git a/packages/ludic.render3d/gpu.ludic b/packages/ludic.render3d/gpu.ludic index bf707f47..740278d2 100644 --- a/packages/ludic.render3d/gpu.ludic +++ b/packages/ludic.render3d/gpu.ludic @@ -516,6 +516,8 @@ function gpu_buffer_free(buf: int) -> void { # a caller keeps its CPU path. A compute program's binding 0 is its parameter block (params, # copied at the dispatch); bindings 1.. are `bufs`, gpu buffers. function gpu_has_compute() -> bool { return gpu_kind == GPU_VK } +# several records in one indirect draw, each with its own firstInstance +function gpu_has_mdi() -> bool { return gpu_kind == GPU_VK and gvk_has_mdi } function gpu_compute(name: string, n_bufs: int) -> int { if gpu_kind != GPU_VK { return 0 } return gvk_compute_new(name, n_bufs) diff --git a/packages/ludic.render3d/grass.ludic b/packages/ludic.render3d/grass.ludic index 23c96571..86efe7d6 100644 --- a/packages/ludic.render3d/grass.ludic +++ b/packages/ludic.render3d/grass.ludic @@ -20,6 +20,21 @@ var grass_d0: int = 0 # the distance at which the spacing has doubled var grass_radius: int = 0 # no blades past this (m) var grass_draws: int = 0 var grass_dbg: int = 0 +# Vulkan with multi-draw indirect: every visible tile is a record in one buffer, uploaded once a +# frame, and each band draws its records GRASS_CHUNK at a time - one draw for up to 256 tiles. A +# record's firstInstance is its place in its chunk times 65536; grass.vert's TILES variant reads that +# place's corner and indices per cell from u_tiles. R3D_GRASS_TILES=1 keeps a draw per tile. +const GRASS_CHUNK: int = 256 +const GRASS_RECS: int = 8192 +var grass_merge: bool = false +var grass_rec: words = null # VkDrawIndexedIndirectCommand records, 5 words each +var grass_tv: words = null # per record: corner x, corner z, indices per cell, 0 (float bits) +var grass_chunk_tv: words = null +var grass_n: int = 0 +var grass_cmds: int = 0 +var grass_band_start: words = null # per band: its first record +var grass_band_cells: words = null # ... and its 16 m cells per tile side +var grass_band_n: int = 0 # a blade: `rows` rows of 2 vertices (x across, y along, z bend), attribute 2 = uv function grass_blade_mesh(rows: int) -> Mesh { @@ -57,7 +72,15 @@ function grass_blade_mesh(rows: int) -> Mesh { } function grass_init() -> void { - grass_prog = r3d_program("grass.vert", "model.frag", "#define FOLIAGE\n#define BLADE\n") + grass_merge = gpu_has_mdi() and not Os.has_env("R3D_GRASS_TILES") + var defs = "#define FOLIAGE\n#define BLADE\n" + if grass_merge { + defs = defs + "#define TILES\n" + grass_rec = words(GRASS_RECS * 5); grass_tv = words(GRASS_RECS * 4); grass_chunk_tv = words(GRASS_CHUNK * 4) + grass_band_start = words(4); grass_band_cells = words(4) + grass_cmds = gpu_buffer_new() + } + grass_prog = r3d_program("grass.vert", "model.frag", defs) grass_mesh = grass_blade_mesh(4) grass_wind = fl(2.4) grass_s0 = fl(0.11) @@ -79,7 +102,11 @@ function grass_count_at(d: int) -> int { function grass_tiles(size: int, d_min: int, d_max: int) -> void { let p = grass_prog let cells = size / GRASS_CELL - u_i(gpu_uniform(p, "u_tile_cells"), cells) + if grass_merge { + grass_band_start[grass_band_n] = grass_n; grass_band_cells[grass_band_n] = cells; grass_band_n += 1 + } else { + u_i(gpu_uniform(p, "u_tile_cells"), cells) + } let sz = fi(size) let half = f_mul(sz, F_HALF) let reach = f_add(d_max, f_mul(half, fl(1.5))) @@ -102,7 +129,18 @@ function grass_tiles(size: int, d_min: int, d_max: int) -> void { let cy = terrain_height(cx, cz) if cam_sphere_visible(cx, cy, cz, f_add(corner_r, fi(6))) { let per = grass_count_at(dnear) - if per > 0 { + if per > 0 and grass_merge { + if grass_n < GRASS_RECS { + var inst = per * cells * cells + if inst > 65535 { inst = 65535 } + let r = grass_n * 5 + grass_rec[r] = grass_mesh.count; grass_rec[r + 1] = inst; grass_rec[r + 2] = 0; grass_rec[r + 3] = 0 + grass_rec[r + 4] = ((grass_n - grass_band_start[grass_band_n - 1]) % GRASS_CHUNK) * 65536 + let t = grass_n * 4 + grass_tv[t] = ox; grass_tv[t + 1] = oz; grass_tv[t + 2] = fi(per); grass_tv[t + 3] = F_ZERO + grass_n += 1 + } + } else if per > 0 { u_f2(gpu_uniform(p, "u_tile"), ox, oz) u_i(gpu_uniform(p, "u_per_cell"), per) mesh_draw_instanced(grass_mesh, per * cells * cells) @@ -152,9 +190,35 @@ function grass_draw() -> void { u_f(gpu_uniform(p, "u_spec_scale"), fl(0.15)) gpu_cull(false) grass_draws = 0 + grass_n = 0 + grass_band_n = 0 gpu_mesh_bind(grass_mesh) grass_tiles(16, F_ZERO, fi(300)) grass_tiles(64, fi(300), fi(1200)) grass_tiles(256, fi(1200), grass_radius) + if grass_merge { grass_flush() } gpu_cull(true) } + +# the records gathered this frame: uploaded once, then each band GRASS_CHUNK records a draw +function grass_flush() -> void { + if grass_n == 0 { return } + let p = grass_prog + gpu_buffer_upload(grass_cmds, grass_n * 20, grass_rec, GPU_DYNAMIC) + for b in 0 .. grass_band_n { + let s = grass_band_start[b] + var e = grass_n + if b + 1 < grass_band_n { e = grass_band_start[b + 1] } + if e > s { u_i(gpu_uniform(p, "u_tile_cells"), grass_band_cells[b]) } + var k = s + while k < e { + var m = e - k + if m > GRASS_CHUNK { m = GRASS_CHUNK } + for q in 0 .. m * 4 { grass_chunk_tv[q] = grass_tv[k * 4 + q] } + u_fv(gpu_uniform(p, "u_tiles"), m * 4, grass_chunk_tv) + gpu_draw_mesh_indirect(grass_mesh, grass_cmds, k * 20, m, 0, 0) + grass_draws += 1 + k += m + } + } +} diff --git a/packages/ludic.render3d/scatter.ludic b/packages/ludic.render3d/scatter.ludic index 53e55161..7f911ddf 100644 --- a/packages/ludic.render3d/scatter.ludic +++ b/packages/ludic.render3d/scatter.ludic @@ -647,7 +647,7 @@ function layer_gpu_prepare(l: Layer) -> bool { # on by default wherever there is compute; R3D_GPU_CULL=0 keeps the CPU partition (for comparing) var off = false if Os.has_env("R3D_GPU_CULL") { off = Os.env("R3D_GPU_CULL") == "0" } - if gpu_has_compute() and not off { + if gpu_has_compute() and gpu_has_mdi() and not off { sc_cull_prog = gpu_compute("scatter_cull", 4) if sc_cull_prog > 0 { print("r3d: scatter: tree layers are culled on the GPU") } } @@ -945,6 +945,43 @@ var sc_dbg_blade: int = 0 var sc_dbg_level: int = -1 var sc_dbg_lod: bool = false var sc_dbg_tint: words = null +# Can level k's casters (its instances lie between the previous level's distance and its own) put a +# shadow on anything the cascade being rendered covers? A receiver in that slice of view depth +# [near, far] stands between near - dy and far * K metres away on the ground: dy is the camera's height +# over the ground, K how far the frustum's corners reach past its depth. A prop's shadow falls at most +# about six times its height past it (the sun near ten degrees). A level outside that range cannot +# touch a pixel of the cascade, so leaving it out changes no shadow - unlike the old per-class skips +# at fixed distances, which dropped casters that did cast (see scatter_draw_casters). The flowers' +# mesh levels (6 - 30 m) stop being drawn into the three outer cascades. R3D_CAST_ALL=1 draws every +# level into every cascade, for comparing. +var sc_cast_all: int = -1 +var sc_cast_gen: int = -1 +var sc_cast_dy: int = 0 +var sc_cast_k: int = 0 +function layer_level_casts_here(l: Layer, k: 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 == 1 or sh_split == null or l.lod_dist == null { return true } + if sc_cast_gen != sc_view_gen { + sc_cast_gen = sc_view_gen + sc_cast_dy = f_add(f_abs(f_sub(cam_pos[1], terrain_height(cam_pos[0], cam_pos[2]))), fi(5)) + let half = f_mul(cam_fov, F_HALF) + let t = f_div(f_sin(half), f_cos(half)) + let ta = f_mul(t, cam_aspect) + sc_cast_k = f_mul(f_sqrt(f_add(F_ONE, f_add(f_mul(t, t), f_mul(ta, ta)))), fl(1.1)) + } + let c = sh_cascade + var near = cam_near + if c > 0 { near = sh_split[c - 1] } + let far = sh_split[c] + var dmin = F_ZERO + if k > 0 { dmin = l.lod_dist[k - 1] } + let dmax = l.lod_dist[k] # 0: open, out to the layer's cull distance + let reach = f_max(f_min(f_mul(l.lods[k].height, fi(6)), fi(40)), fi(8)) + if dmax != 0 and f_ls(f_add(f_add(dmax, reach), sc_cast_dy), near) { return false } + if f_gt(f_sub(dmin, reach), f_mul(far, sc_cast_k)) { return false } + return true +} + # `full` casts the layer's entire instance list out of sh_buf instead of the near # partition out of l.buf. A layer with no impostor (the tree crowns' branch cards) has # no cheap stand-in to cast from, so without this its shadow simply began at the near @@ -958,8 +995,12 @@ function layer_draw_near(l: Layer, shadow: bool, light_vp: words, full: bool) -> return } if l.n_lods > 1 { - # a LOD chain: every level from its own bucket (casters are what is drawn) - for k in 0 .. l.n_lods { sc_dbg_level = k; layer_draw_model(l, l.lods[k], l.lod_buf[k], l.n_lod[k], l.lod_card[k] == 1, shadow, light_vp) } + # a LOD chain: every level from its own bucket (casters are what is drawn), and a caster level + # only into the cascades it can put a shadow in + for k in 0 .. l.n_lods { + if shadow and not layer_level_casts_here(l, k) { continue } + sc_dbg_level = k; layer_draw_model(l, l.lods[k], l.lod_buf[k], l.n_lod[k], l.lod_card[k] == 1, shadow, light_vp) + } sc_dbg_level = -1 return } diff --git a/packages/ludic.render3d/shaders/grass.vert b/packages/ludic.render3d/shaders/grass.vert index dbf8d966..aedf8d1a 100644 --- a/packages/ludic.render3d/shaders/grass.vert +++ b/packages/ludic.render3d/shaders/grass.vert @@ -27,6 +27,9 @@ uniform float u_wind; uniform vec2 u_tile; // world xz of this tile's corner uniform int u_tile_cells; // 16 m cells per tile side uniform int u_per_cell; // indices drawn per cell in this tile +#ifdef TILES +uniform vec4 u_tiles[256]; // per tile of the draw: corner x, corner z, indices per cell +#endif uniform float u_s0; // blade spacing at the camera (m) uniform float u_d0; // distance at which the spacing has doubled (m) uniform float u_radius; // no blades past this @@ -66,10 +69,21 @@ float heightSmooth(sampler2D tex, vec2 uv) { void cull() { gl_Position = vec4(0.0, 0.0, 2.0, 1.0); v_wpos = vec3(0.0); v_nrm = vec3(0.0, 1.0, 0.0); v_uv = vec2(0.0); v_seed = 0.0; v_rot = vec2(0.0, 1.0); v_hull = 1.0; } void main() { +#ifdef TILES + // Vulkan: one draw covers a chunk of tiles. Each record's firstInstance is its place in the chunk + // times 65536 (grass.ludic), and u_tiles holds that place's corner and indices per cell. + int ti = gl_InstanceIndex / 65536; + int i = gl_InstanceIndex - ti * 65536; + vec2 tile = u_tiles[ti].xy; + int per_cell = int(u_tiles[ti].z + 0.5); +#else int i = gl_InstanceID; - int c = i / u_per_cell; - int j = i - c * u_per_cell; - vec2 cell = u_tile + vec2(float(c % u_tile_cells), float(c / u_tile_cells)) * CELL; + vec2 tile = u_tile; + int per_cell = u_per_cell; +#endif + int c = i / per_cell; + int j = i - c * per_cell; + vec2 cell = tile + vec2(float(c % u_tile_cells), float(c / u_tile_cells)) * CELL; vec2 cid = floor(cell / CELL + 0.5); ivec2 ci = ivec2(cid); float fj = float(j); diff --git a/packages/ludic.render3d/shaders/spv/7ed7de52.vert.spv b/packages/ludic.render3d/shaders/spv/7ed7de52.vert.spv index d9a792db06f1451e07661f76fa530d2c19d09f9a..e41094fda6e2640a39cdeec03c58c161b589a1f7 100644 GIT binary patch literal 23664 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

)WE~ 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 a2EpxLA9G%eV1vO6Q=7Bnmg|>)`}F_a<~GX! literal 23560 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 diff --git a/packages/ludic.render3d/shaders/spv/7f096322.frag.spv b/packages/ludic.render3d/shaders/spv/7f096322.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..f83e77c00b5279c33bd4288b5d96c2c33c91f03f GIT binary patch literal 45536 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 MM_=#F+PpXaKhQQ-cmMzZ literal 0 HcmV?d00001 diff --git a/packages/ludic.render3d/shaders/spv/7f096322.vert.spv b/packages/ludic.render3d/shaders/spv/7f096322.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..277a763b717d45e9894d701062bad6a4e8e5312e GIT binary patch literal 23996 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