feat(render3d): mesh-shader grass

The chunked grass path draws each chunk as one mesh-shader dispatch when the
setting asks and the card has VK_EXT_mesh_shader: work group y is a tile, x a
batch of 16 of its blades, and a blade the placement, density, frustum, water
or slope tests reject emits nothing - the instanced path still runs its eight
vertices to a degenerate position. grass.mesh generates the same blades as
grass.vert (grass_blade_mesh(4)'s rows, the same hashes, sway and lighting
normal), capped at the instanced path's 65535 a tile.

Vulkan: VK_EXT_mesh_shader with meshShader, and maintenance4 (glslang's mesh
stages declare LocalSizeId); vkCmdDrawMeshTasksEXT looked up per device, as the
Streamline interposer exports none; a *.mesh program's pipeline takes the mesh
stage and no vertex input, its bindings the mesh stage bit. gpu_has_mesh,
gpu_draw_mesh_tasks; r3d_mesh_grass and R3D_MESH_GRASS / R3D_NO_MESH.
bin/ludic-dev rebuilt: the committed binary predated the shader tool's mesh
support and compiled grass.mesh as a vertex stage.

PC (RTX 3070 Ti): the camp matches the chunked path; validation only the
no-window present-id message.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-15 20:20:42 +03:00
parent 6525c11b3c
commit 750d13779d
10 changed files with 334 additions and 13 deletions

View file

@ -394,7 +394,7 @@ function gpu_caps_probe() -> void {
# DLSS super resolution and Reflex run through NVIDIA Streamline (streamline.ludic), and HDR output is an
# HDR10 swapchain (gpu_vk_draw.ludic); whether this
# machine can use one is the caps' question, not this one.
function gpu_feature_implemented(f: int) -> bool { return f == GF_VULKAN or f == GF_DLSS or f == GF_REFLEX or f == GF_HDR }
function gpu_feature_implemented(f: int) -> bool { return f == GF_VULKAN or f == GF_DLSS or f == GF_REFLEX or f == GF_HDR or f == GF_MESH_GRASS }
# ---- vertex data --------------------------------------------------------------------
# A Mesh is built through these and records what it is made of - which buffer feeds which
@ -524,6 +524,9 @@ function gpu_buffer_free(buf: int) -> void {
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 }
# mesh shaders (VK_EXT_mesh_shader): a *.mesh program drawn with gpu_draw_mesh_tasks
function gpu_has_mesh() -> bool { return gpu_kind == GPU_VK and gvk_has_mesh }
function gpu_draw_mesh_tasks(x: int, y: int, z: int) -> void { if gpu_kind == GPU_VK { gvk_draw_mesh_tasks_now(x, y, z) } }
function gpu_compute(name: string, n_bufs: int) -> int {
if gpu_kind != GPU_VK { return 0 }
return gvk_compute_new(name, n_bufs)