ludic/docs/language/vk/_section.md
Orkuncakilkaya 208cad7ca1 feat(vk): Vk.* - Vulkan 1.0-1.4 generated from the registry, loaded at run time
ludic-dev vkgen reads vk.xml into runtime/native/vk_api.ludic (constants, every struct's
<Struct>_sizeof and <Struct>_<field> offsets, one extern per command) and vk_thunks.ll.
Every size and offset was compiled against the SDK's C headers; `ludic-dev test` checks the
tracked files against the registry wherever the Vulkan SDK is installed.

vk_win.ll (vulkan-1.dll) and vk_mac.ll (libvulkan.1.dylib, MoltenVK) open the loader at run
time, so a program built with Vk.* starts on a machine without Vulkan. ludicc and ludic
build link both for any program that uses Vk.*. The seeds are regenerated for the new
compiler.

vk_probe reports what a machine's Vulkan can do; vk_compute dispatches a Slang compute
shader and reads the picture back, clean under the validation layer on an RTX 3070 Ti and
on an M4 Pro through MoltenVK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 09:52:12 +03:00

3.2 KiB
Raw Blame History

id title order
vk Vk 61

Vulkan for Ludic. Vk. binds Vulkan 1.0–1.4 and the extensions a modern renderer is built around — swapchain and HDR colour spaces, ray query and acceleration structures, opacity micromaps, mesh shaders, variable rate shading, memory budget, pipeline libraries, NVIDIA low latency, and portability enumeration for MoltenVK. Every command is a method named by its snake case (vkCreateInstance → Vk.create_instance, vkCmdDrawIndexedIndirectCount → Vk.cmd_draw_indexed_indirect_count), every VK_ constant is available as written, and every struct has its size and field offsets as constants: VkDeviceCreateInfo_sizeof, VkDeviceCreateInfo_queueCreateInfoCount. The binding is generated by ludic-dev vkgen from the Vulkan registry (vk.xml), and every size and offset is checked against the SDK's C headers.

A struct is plain memory filled by field name: bytes(Vk…_sizeof), Vk.zero(p, n), then Vk.put_i32 / Vk.put_i64 / Vk.put_ptr(p, offset, value), read back with Vk.get_i32 / get_i64 / get_ptr, and Vk.at(p, offset) for a nested struct or an inline array. Numbers follow the C types: a float is its IEEE bit pattern in an int, and uint64_t, VkDeviceSize and non-dispatchable handles are long. A negative long has to come from a long variable — an int literal passed straight to a long parameter is zero-extended.

The loader is opened at run time by Vk.open(), never linked: vulkan-1.dll on Windows, libvulkan.1.dylib (the LunarG loader and MoltenVK) on macOS — beside the executable, in the library paths, or under $VULKAN_SDK. It returns 0 on a machine without Vulkan, and the program carries on; Vk.has(name) says whether one command is there. Using Vk.* links the generated thunks and the loader; a program that does not is unchanged. See examples/rendering/vk_probe.ludic (what a machine's Vulkan can do) and vk_compute.ludic (a Slang compute shader, dispatched and read back).

program Probe {
  property Marker { on: int = 1 }
  model Anchor { Marker }
  handler Boot phase Start {
    spawn Anchor {}
    if Vk.open() == 0 { print("no Vulkan here");  quit() }
    let app = bytes(VkApplicationInfo_sizeof)
    Vk.zero(app, VkApplicationInfo_sizeof)
    Vk.put_i32(app, VkApplicationInfo_sType, VK_STRUCTURE_TYPE_APPLICATION_INFO)
    Vk.put_i32(app, VkApplicationInfo_apiVersion, (1 << 22) | (3 << 12))
    let ci = bytes(VkInstanceCreateInfo_sizeof)
    Vk.zero(ci, VkInstanceCreateInfo_sizeof)
    Vk.put_i32(ci, VkInstanceCreateInfo_sType, VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO)
    Vk.put_ptr(ci, VkInstanceCreateInfo_pApplicationInfo, app)
    let out = bytes(8)
    if Vk.create_instance(ci, null, out) == VK_SUCCESS {
      print("a Vulkan instance")
      Vk.destroy_instance(Vk.get_ptr(out, 0), null)
    }
    quit()
  }
}