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>
This commit is contained in:
parent
043d8d81a2
commit
208cad7ca1
18 changed files with 52468 additions and 40972 deletions
21
runtime/native/vk.ludic
Normal file
21
runtime/native/vk.ludic
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# ============================================================================
|
||||
# vk.ludic — Vk.*: Vulkan for Ludic.
|
||||
#
|
||||
# Every command of Vulkan 1.0-1.4 and of the extensions the render3d Vulkan renderer
|
||||
# is built around is bound in vk_api.ludic (generated by `ludic-dev vkgen` from the
|
||||
# Vulkan registry), with every constant and every struct's size and field offsets.
|
||||
# Vk.<snake_name>(...) calls one: Vk.create_instance(info, null, out). Structs are
|
||||
# plain memory filled by field name:
|
||||
# let ci = bytes(VkInstanceCreateInfo_sizeof)
|
||||
# Vk.zero(ci, VkInstanceCreateInfo_sizeof)
|
||||
# Vk.put_i32(ci, VkInstanceCreateInfo_sType, VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO)
|
||||
#
|
||||
# Numbers: a C float is float bits in an int; 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() (vk_win.ll / vk_mac.ll), never
|
||||
# linked: 0 means this machine has no Vulkan, and the program carries on without it.
|
||||
# ============================================================================
|
||||
|
||||
import "vk_api.ludic"
|
||||
5979
runtime/native/vk_api.ludic
Normal file
5979
runtime/native/vk_api.ludic
Normal file
File diff suppressed because it is too large
Load diff
110
runtime/native/vk_mac.ll
Normal file
110
runtime/native/vk_mac.ll
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
; ============================================================================
|
||||
; vk_mac.ll — the Vulkan loader, for macOS, written in LLVM IR.
|
||||
;
|
||||
; macOS has no Vulkan of its own: the LunarG loader and MoltenVK (Vulkan over
|
||||
; Metal) come with the Vulkan SDK or with a game that bundles them. So the loader
|
||||
; is looked for at run time - beside the executable, in the usual library
|
||||
; directories, then under $VULKAN_SDK - and a Mac without it gets 0 from vk_open
|
||||
; and stays on OpenGL. The contract is vk_win.ll's:
|
||||
;
|
||||
; lvk_open() -> 1 | 0 lvk_sym(name) -> ptr lvk_has(name) -> 1 | 0
|
||||
;
|
||||
; MoltenVK is a portability driver: an instance must ask for
|
||||
; VK_KHR_portability_enumeration (and the ENUMERATE_PORTABILITY flag) to see it.
|
||||
; ============================================================================
|
||||
|
||||
declare ptr @dlopen(ptr, i32)
|
||||
declare ptr @dlsym(ptr, ptr)
|
||||
declare ptr @getenv(ptr)
|
||||
declare i32 @snprintf(ptr, i64, ptr, ...)
|
||||
declare i32 @lvk_bind()
|
||||
|
||||
@.lvk_c0 = private unnamed_addr constant [18 x i8] c"libvulkan.1.dylib\00"
|
||||
@.lvk_c1 = private unnamed_addr constant [35 x i8] c"@executable_path/libvulkan.1.dylib\00"
|
||||
@.lvk_c2 = private unnamed_addr constant [33 x i8] c"/usr/local/lib/libvulkan.1.dylib\00"
|
||||
@.lvk_c3 = private unnamed_addr constant [36 x i8] c"/opt/homebrew/lib/libvulkan.1.dylib\00"
|
||||
@.lvk_env = private unnamed_addr constant [11 x i8] c"VULKAN_SDK\00"
|
||||
@.lvk_fmt = private unnamed_addr constant [25 x i8] c"%s/lib/libvulkan.1.dylib\00"
|
||||
@lvk_lib = internal global ptr null
|
||||
|
||||
; RTLD_NOW (2) | RTLD_LOCAL (4)
|
||||
define internal ptr @lvk_try(ptr %path) {
|
||||
entry:
|
||||
%h = call ptr @dlopen(ptr %path, i32 6)
|
||||
ret ptr %h
|
||||
}
|
||||
|
||||
define i32 @lvk_open() {
|
||||
entry:
|
||||
%have = load ptr, ptr @lvk_lib
|
||||
%open = icmp ne ptr %have, null
|
||||
br i1 %open, label %ok, label %t0
|
||||
t0:
|
||||
%h0 = call ptr @lvk_try(ptr @.lvk_c0)
|
||||
%n0 = icmp eq ptr %h0, null
|
||||
br i1 %n0, label %t1, label %got0
|
||||
got0:
|
||||
store ptr %h0, ptr @lvk_lib
|
||||
br label %bind
|
||||
t1:
|
||||
%h1 = call ptr @lvk_try(ptr @.lvk_c1)
|
||||
%n1 = icmp eq ptr %h1, null
|
||||
br i1 %n1, label %t2, label %got1
|
||||
got1:
|
||||
store ptr %h1, ptr @lvk_lib
|
||||
br label %bind
|
||||
t2:
|
||||
%h2 = call ptr @lvk_try(ptr @.lvk_c2)
|
||||
%n2 = icmp eq ptr %h2, null
|
||||
br i1 %n2, label %t3, label %got2
|
||||
got2:
|
||||
store ptr %h2, ptr @lvk_lib
|
||||
br label %bind
|
||||
t3:
|
||||
%h3 = call ptr @lvk_try(ptr @.lvk_c3)
|
||||
%n3 = icmp eq ptr %h3, null
|
||||
br i1 %n3, label %t4, label %got3
|
||||
got3:
|
||||
store ptr %h3, ptr @lvk_lib
|
||||
br label %bind
|
||||
t4:
|
||||
%sdk = call ptr @getenv(ptr @.lvk_env)
|
||||
%nosdk = icmp eq ptr %sdk, null
|
||||
br i1 %nosdk, label %fail, label %t4b
|
||||
t4b:
|
||||
%buf = alloca [1024 x i8]
|
||||
%w = call i32 (ptr, i64, ptr, ...) @snprintf(ptr %buf, i64 1024, ptr @.lvk_fmt, ptr %sdk)
|
||||
%h4 = call ptr @lvk_try(ptr %buf)
|
||||
%n4 = icmp eq ptr %h4, null
|
||||
br i1 %n4, label %fail, label %got4
|
||||
got4:
|
||||
store ptr %h4, ptr @lvk_lib
|
||||
br label %bind
|
||||
bind:
|
||||
%n = call i32 @lvk_bind()
|
||||
br label %ok
|
||||
ok:
|
||||
ret i32 1
|
||||
fail:
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
define ptr @lvk_sym(ptr %name) {
|
||||
entry:
|
||||
%lib = load ptr, ptr @lvk_lib
|
||||
%none = icmp eq ptr %lib, null
|
||||
br i1 %none, label %no, label %look
|
||||
look:
|
||||
%p = call ptr @dlsym(ptr %lib, ptr %name)
|
||||
ret ptr %p
|
||||
no:
|
||||
ret ptr null
|
||||
}
|
||||
|
||||
define i32 @lvk_has(ptr %name) {
|
||||
entry:
|
||||
%p = call ptr @lvk_sym(ptr %name)
|
||||
%there = icmp ne ptr %p, null
|
||||
%r = zext i1 %there to i32
|
||||
ret i32 %r
|
||||
}
|
||||
3392
runtime/native/vk_thunks.ll
Normal file
3392
runtime/native/vk_thunks.ll
Normal file
File diff suppressed because it is too large
Load diff
61
runtime/native/vk_win.ll
Normal file
61
runtime/native/vk_win.ll
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
; ============================================================================
|
||||
; vk_win.ll — the Vulkan loader, for Windows, written in LLVM IR.
|
||||
;
|
||||
; vulkan-1.dll ships with every GPU driver on Windows 10 and 11, but it is not
|
||||
; guaranteed, so it is opened AT RUN TIME and never linked: a program that asks for
|
||||
; Vulkan on a machine without it gets 0 from vk_open and falls back to OpenGL
|
||||
; rather than failing to start.
|
||||
;
|
||||
; lvk_open() -> 1 | 0 open the loader and bind every command (vk_thunks.ll)
|
||||
; lvk_sym(name) -> ptr one entry point by name, null when the loader lacks it
|
||||
; lvk_has(name) -> 1 | 0 whether a command is there (an extension's, say)
|
||||
;
|
||||
; The commands themselves, their struct layouts and constants are generated from
|
||||
; the Vulkan registry by `ludic-dev vkgen` into vk_api.ludic and vk_thunks.ll.
|
||||
; ============================================================================
|
||||
|
||||
declare ptr @LoadLibraryA(ptr)
|
||||
declare ptr @GetProcAddress(ptr, ptr)
|
||||
declare i32 @lvk_bind()
|
||||
|
||||
@.lvk_dll = private unnamed_addr constant [13 x i8] c"vulkan-1.dll\00"
|
||||
@lvk_lib = internal global ptr null
|
||||
|
||||
define i32 @lvk_open() {
|
||||
entry:
|
||||
%have = load ptr, ptr @lvk_lib
|
||||
%open = icmp ne ptr %have, null
|
||||
br i1 %open, label %ok, label %load
|
||||
load:
|
||||
%lib = call ptr @LoadLibraryA(ptr @.lvk_dll)
|
||||
%none = icmp eq ptr %lib, null
|
||||
br i1 %none, label %fail, label %bind
|
||||
bind:
|
||||
store ptr %lib, ptr @lvk_lib
|
||||
%n = call i32 @lvk_bind()
|
||||
br label %ok
|
||||
ok:
|
||||
ret i32 1
|
||||
fail:
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
define ptr @lvk_sym(ptr %name) {
|
||||
entry:
|
||||
%lib = load ptr, ptr @lvk_lib
|
||||
%none = icmp eq ptr %lib, null
|
||||
br i1 %none, label %no, label %look
|
||||
look:
|
||||
%p = call ptr @GetProcAddress(ptr %lib, ptr %name)
|
||||
ret ptr %p
|
||||
no:
|
||||
ret ptr null
|
||||
}
|
||||
|
||||
define i32 @lvk_has(ptr %name) {
|
||||
entry:
|
||||
%p = call ptr @lvk_sym(ptr %name)
|
||||
%there = icmp ne ptr %p, null
|
||||
%r = zext i1 %there to i32
|
||||
ret i32 %r
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue