feat(vk): mesh-shader plumbing - *.mesh variants compile, a pointer call for commands the interposer lacks

ludic-dev shaders: a variant whose first file is *.mesh compiles that stage with
glslang -S mesh for Vulkan 1.3, without the vertex stage's depth-remap wrapper
or invariant gl_Position (a mesh shader writes an array of positions and
remaps depth itself). Its SPIR-V keeps the .vert name, so the manifest and the
loader are unchanged. The 51 existing programs build identical SPIR-V.

runtime: lsl_call_piii(fn, ptr, i32, i32, i32) calls a command-buffer command
through a pointer. NVIDIA Streamline's interposer exports no
vkCmdDrawMeshTasksEXT, so it is to be looked up per device with
vkGetDeviceProcAddr. Both runtimes assemble.

Nothing uses either yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-15 20:00:26 +03:00
parent 762a74624c
commit 0224af64ab
3 changed files with 25 additions and 2 deletions

View file

@ -182,3 +182,11 @@ entry:
%r = call i32 %fn(i32 %a0, ptr %a1)
ret i32 %r
}
; a command-buffer command with three counts, through a pointer (vkCmdDrawMeshTasksEXT: the Streamline
; interposer exports no such command, so it is looked up per device with vkGetDeviceProcAddr)
define void @lsl_call_piii(ptr %fn, ptr %a0, i32 %a1, i32 %a2, i32 %a3) {
entry:
call void %fn(ptr %a0, i32 %a1, i32 %a2, i32 %a3)
ret void
}

View file

@ -323,3 +323,11 @@ entry:
%r = call i32 %fn(i32 %a0, ptr %a1)
ret i32 %r
}
; a command-buffer command with three counts, through a pointer (vkCmdDrawMeshTasksEXT: the Streamline
; interposer exports no such command, so it is looked up per device with vkGetDeviceProcAddr)
define void @lsl_call_piii(ptr %fn, ptr %a0, i32 %a1, i32 %a2, i32 %a3) {
entry:
call void %fn(ptr %a0, i32 %a1, i32 %a2, i32 %a3)
ret void
}

View file

@ -232,7 +232,12 @@ function cmd_shaders() -> int {
let depth_remap = "\n#undef main\nvoid main() {\n ludic_gl_main();\n gl_Position.z = (gl_Position.z + gl_Position.w) * 0.5;\n}\n"
# invariant: the foliage's depth prepass and its lit pass (depth EQUAL) are different variants,
# and without it the compiler may compute their positions a hair apart and fail the test
let vsrc = "#version 460\n" + defines + "invariant gl_Position;\n#define main ludic_gl_main\n" + shd_file(parts[0]) + depth_remap
var vsrc = "#version 460\n" + defines + "invariant gl_Position;\n#define main ludic_gl_main\n" + shd_file(parts[0]) + depth_remap
# A mesh shader stands in for the vertex stage (a variant whose first file is *.mesh): it writes an
# array of positions, so it cannot be wrapped for the depth remap or take invariant gl_Position -
# it remaps depth itself. Its SPIR-V keeps the .vert name, so the manifest and loader are unchanged.
let is_mesh = Text.ends_with(parts[0], ".mesh")
if is_mesh { vsrc = "#version 460\n#extension GL_EXT_mesh_shader : require\n" + defines + shd_file(parts[0]) }
let fsrc = "#version 460\n" + defines + shd_noise + shd_lighting + shd_file(parts[1])
# one binding table for both stages, and one location table for the varyings between them
shd_names = new []pointer
@ -264,8 +269,10 @@ function cmd_shaders() -> int {
let spv = `{outdir}/{id}.{stage}.spv`
var extra = ""
if stage == "frag" { extra = " --shift-UBO-binding frag 1" }
var sflag = stage
if stage == "vert" and is_mesh { sflag = "mesh"; extra = extra + " --target-env vulkan1.3" }
let logf = `{tmp}/{id}.{stage}.log`
if not shq(`{shd_q(shd_bin + "/glslangValidator")} -V -R --auto-map-bindings --auto-map-locations{extra} -S {stage} {shd_q(glsl)} -o {shd_q(spv)} > {shd_q(logf)} 2>&1`) {
if not shq(`{shd_q(shd_bin + "/glslangValidator")} -V -R --auto-map-bindings --auto-map-locations{extra} -S {sflag} {shd_q(glsl)} -o {shd_q(spv)} > {shd_q(logf)} 2>&1`) {
err(`shaders: {parts[0]} + {parts[1]} [{defs}] ({stage}) did not compile:\n{capture("grep ERROR " + shd_q(logf) + " | head -5")}\n`)
return 1
}