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 d9a792db..e41094fd 100644 Binary files a/packages/ludic.render3d/shaders/spv/7ed7de52.vert.spv and b/packages/ludic.render3d/shaders/spv/7ed7de52.vert.spv differ 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 00000000..f83e77c0 Binary files /dev/null and b/packages/ludic.render3d/shaders/spv/7f096322.frag.spv differ 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 00000000..277a763b Binary files /dev/null and b/packages/ludic.render3d/shaders/spv/7f096322.vert.spv differ diff --git a/packages/ludic.render3d/shaders/spv/manifest.txt b/packages/ludic.render3d/shaders/spv/manifest.txt index 4deb3b32..cc2c4f9f 100644 --- a/packages/ludic.render3d/shaders/spv/manifest.txt +++ b/packages/ludic.render3d/shaders/spv/manifest.txt @@ -647,6 +647,77 @@ T 7ed7de52 u_prefilter 8 T 7ed7de52 u_shadow 9 T 7ed7de52 u_tershadow 10 T 7ed7de52 u_ts_height 11 +P 7f096322 grass.vert model.frag #define FOLIAGE;#define BLADE;#define TILES; +B 7f096322 vert 0 4400 +U 7f096322 vert u_view 0 mat4 1 0 +U 7f096322 vert u_proj 64 mat4 1 0 +U 7f096322 vert u_vp 128 mat4 1 0 +U 7f096322 vert u_cam_pos 192 vec3 1 0 +U 7f096322 vert u_time 204 float 1 0 +U 7f096322 vert u_ts_origin 208 vec2 1 0 +U 7f096322 vert u_ts_half 216 float 1 0 +U 7f096322 vert u_ortho_on 220 float 1 0 +U 7f096322 vert u_lake_level 224 float 1 0 +U 7f096322 vert u_sea_level 228 float 1 0 +U 7f096322 vert u_lake 240 vec4 1 0 +U 7f096322 vert u_snow_line 256 float 1 0 +U 7f096322 vert u_wind 260 float 1 0 +U 7f096322 vert u_tile 264 vec2 1 0 +U 7f096322 vert u_tile_cells 272 int 1 0 +U 7f096322 vert u_per_cell 276 int 1 0 +U 7f096322 vert u_tiles 288 vec4 256 16 +U 7f096322 vert u_s0 4384 float 1 0 +U 7f096322 vert u_d0 4388 float 1 0 +U 7f096322 vert u_radius 4392 float 1 0 +U 7f096322 vert u_dbg 4396 int 1 0 +I 7f096322 a_pos 0 vec3 +I 7f096322 a_uv 2 vec2 +B 7f096322 frag 1 892 +U 7f096322 frag u_cascade_vp 0 mat4 5 64 +U 7f096322 frag u_cascade_split 320 float 5 16 +U 7f096322 frag u_cascade_range 400 float 5 16 +U 7f096322 frag u_cascade_texel 480 float 5 16 +U 7f096322 frag u_sun_dir 560 vec3 1 0 +U 7f096322 frag u_sun_color 576 vec3 1 0 +U 7f096322 frag u_cam_pos 592 vec3 1 0 +U 7f096322 frag u_prefilter_levels 604 float 1 0 +U 7f096322 frag u_fog_density 608 float 1 0 +U 7f096322 frag u_fog_height_falloff 612 float 1 0 +U 7f096322 frag u_fog_base 616 float 1 0 +U 7f096322 frag u_clip_y 620 float 1 0 +U 7f096322 frag u_spec_scale 624 float 1 0 +U 7f096322 frag u_sky_rot 632 vec2 1 0 +U 7f096322 frag u_ibl_scale 640 vec3 1 0 +U 7f096322 frag u_daylight 652 float 1 0 +U 7f096322 frag u_fire_pos 656 vec3 1 0 +U 7f096322 frag u_fire_color 672 vec3 1 0 +U 7f096322 frag u_hand_pos 688 vec3 1 0 +U 7f096322 frag u_hand_color 704 vec3 1 0 +U 7f096322 frag u_hand_dir 720 vec3 1 0 +U 7f096322 frag u_hand_cone 732 float 1 0 +U 7f096322 frag u_ts_origin 736 vec2 1 0 +U 7f096322 frag u_ts_half 744 float 1 0 +U 7f096322 frag u_ts_on 748 float 1 0 +U 7f096322 frag u_force_cascade 752 int 1 0 +U 7f096322 frag u_cloud_shadow 756 float 1 0 +U 7f096322 frag u_time 760 float 1 0 +U 7f096322 frag u_model_h 764 float 1 0 +U 7f096322 frag u_view 768 mat4 1 0 +U 7f096322 frag u_tint 832 vec3 1 0 +U 7f096322 frag u_rough_scale 844 float 1 0 +U 7f096322 frag u_emissive 848 float 1 0 +U 7f096322 frag u_blade_base 864 vec3 1 0 +U 7f096322 frag u_blade_tip 880 vec3 1 0 +T 7f096322 u_arm 2 +T 7f096322 u_brdf 3 +T 7f096322 u_diff 4 +T 7f096322 u_irradiance 5 +T 7f096322 u_nrm 6 +T 7f096322 u_ortho 7 +T 7f096322 u_prefilter 8 +T 7f096322 u_shadow 9 +T 7f096322 u_tershadow 10 +T 7f096322 u_ts_height 11 P 3733fc38 impostor.vert impostor.frag B 3733fc38 vert 0 232 U 3733fc38 vert u_view 0 mat4 1 0 diff --git a/packages/ludic.render3d/shaders/variants.list b/packages/ludic.render3d/shaders/variants.list index c135d04d..64674722 100644 --- a/packages/ludic.render3d/shaders/variants.list +++ b/packages/ludic.render3d/shaders/variants.list @@ -17,6 +17,7 @@ fullscreen.vert|ternormal.frag| fullscreen.vert|tershadow.frag|#define NOISE_ONLY; fullscreen.vert|tonemap.frag| grass.vert|model.frag|#define FOLIAGE;#define BLADE; +grass.vert|model.frag|#define FOLIAGE;#define BLADE;#define TILES; impostor.vert|impostor.frag| impostor.vert|impostor.frag|#define SHADOW_PASS; model.vert|bake.frag|