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
36
docs/language/vk/_section.md
Normal file
36
docs/language/vk/_section.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
id: vk
|
||||
title: Vk
|
||||
order: 61
|
||||
---
|
||||
|
||||
Vulkan for Ludic. <code>Vk.*</code> binds <strong>Vulkan 1.0–1.4</strong> 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 (<code>vkCreateInstance</code> → <code>Vk.create_instance</code>, <code>vkCmdDrawIndexedIndirectCount</code> → <code>Vk.cmd_draw_indexed_indirect_count</code>), every <code>VK_*</code> constant is available as written, and every struct has its size and field offsets as constants: <code>VkDeviceCreateInfo_sizeof</code>, <code>VkDeviceCreateInfo_queueCreateInfoCount</code>. The binding is generated by <code>ludic-dev vkgen</code> from the Vulkan registry (<code>vk.xml</code>), and every size and offset is checked against the SDK's C headers.
|
||||
|
||||
A struct is plain memory filled by field name: <code>bytes(Vk…_sizeof)</code>, <code>Vk.zero(p, n)</code>, then <code>Vk.put_i32</code> / <code>Vk.put_i64</code> / <code>Vk.put_ptr(p, offset, value)</code>, read back with <code>Vk.get_i32</code> / <code>get_i64</code> / <code>get_ptr</code>, and <code>Vk.at(p, offset)</code> for a nested struct or an inline array. Numbers follow the C types: a <code>float</code> is its IEEE bit pattern in an <code>int</code>, and <code>uint64_t</code>, <code>VkDeviceSize</code> and non-dispatchable handles are <code>long</code>. A negative <code>long</code> has to come from a <code>long</code> variable — an <code>int</code> literal passed straight to a <code>long</code> parameter is zero-extended.
|
||||
|
||||
The loader is opened <strong>at run time</strong> by <code>Vk.open()</code>, never linked: <code>vulkan-1.dll</code> on Windows, <code>libvulkan.1.dylib</code> (the LunarG loader and MoltenVK) on macOS — beside the executable, in the library paths, or under <code>$VULKAN_SDK</code>. It returns 0 on a machine without Vulkan, and the program carries on; <code>Vk.has(name)</code> says whether one command is there. Using <code>Vk.*</code> links the generated thunks and the loader; a program that does not is unchanged. See <code>examples/rendering/vk_probe.ludic</code> (what a machine's Vulkan can do) and <code>vk_compute.ludic</code> (a Slang compute shader, dispatched and read back).
|
||||
|
||||
```ludic
|
||||
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()
|
||||
}
|
||||
}
|
||||
```
|
||||
349
examples/rendering/vk_compute.ludic
Normal file
349
examples/rendering/vk_compute.ludic
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
# vk_compute.ludic — the first work a Ludic program hands a GPU through Vulkan: a
|
||||
# Slang compute shader (vk_compute.slang) fills a storage buffer with a picture, the
|
||||
# CPU maps the buffer and writes it as a PPM. No window, so it runs headless over SSH
|
||||
# and under the validation layer.
|
||||
#
|
||||
# Every struct is filled by field name from the generated layouts (vk_api.ludic), and
|
||||
# every call goes through the generated thunks - this is the whole path the Vulkan
|
||||
# renderer will use, end to end: loader, instance, device, memory, shader module,
|
||||
# descriptor set, push constants, pipeline, command buffer, submit, fence, map.
|
||||
# slangc examples/rendering/vk_compute.slang -target spirv -profile spirv_1_5
|
||||
# -entry main -stage compute -o build/vk_compute.spv
|
||||
# Windows: bin/ludicc examples/rendering/vk_compute.ludic --headless
|
||||
# (Vk.* links the Vulkan runtime)
|
||||
# macOS: the same with vk_mac.ll; VULKAN_SDK set to the SDK's macOS directory
|
||||
program VkCompute {
|
||||
|
||||
property Marker { on: int = 1 }
|
||||
model Anchor { Marker }
|
||||
|
||||
const W: int = 640
|
||||
const H: int = 360
|
||||
|
||||
var dev: pointer = null
|
||||
|
||||
function fail(what: string, r: int) -> void { print(`vulkan: {what} failed, VkResult {r}`); quit() }
|
||||
function handle(out: bytes) -> long { return vk_get_i64(out, 0) }
|
||||
function has_ext(props: bytes, n: int, want: string) -> bool {
|
||||
for i in 0 .. n {
|
||||
if string(vk_at(props, i * VkExtensionProperties_sizeof + VkExtensionProperties_extensionName)) == want { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
function read_all(path: string) -> bytes {
|
||||
let f = file_open(path, "rb")
|
||||
if f == null { return null }
|
||||
file_seek(f, 0, 2)
|
||||
let n = file_tell(f)
|
||||
file_seek(f, 0, 0)
|
||||
let b = bytes(n + 4)
|
||||
file_read(f, b, n)
|
||||
file_close(f)
|
||||
spv_len = n
|
||||
return b
|
||||
}
|
||||
var spv_len: int = 0
|
||||
|
||||
handler Boot phase Start {
|
||||
spawn Anchor {}
|
||||
var spv_path = "build/vk_compute.spv"
|
||||
if Os.has_env("VKC_SPV") { spv_path = Os.env("VKC_SPV") }
|
||||
let spv = read_all(spv_path)
|
||||
if spv == null { print(`vulkan: no SPIR-V at {spv_path} (compile vk_compute.slang with slangc)`); quit() }
|
||||
if Vk.open() == 0 { print("vulkan: no loader"); quit() }
|
||||
|
||||
# ---- instance (portability enumeration where offered: MoltenVK)
|
||||
let cnt = bytes(4)
|
||||
vk_put_i32(cnt, 0, 0)
|
||||
vk_enumerate_instance_extension_properties(null, cnt, null)
|
||||
let nie = vk_get_i32(cnt, 0)
|
||||
let iexts = bytes(nie * VkExtensionProperties_sizeof + 8)
|
||||
vk_enumerate_instance_extension_properties(null, cnt, iexts)
|
||||
let portability = has_ext(iexts, nie, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)
|
||||
let app = bytes(VkApplicationInfo_sizeof)
|
||||
vk_zero(app, VkApplicationInfo_sizeof)
|
||||
vk_put_i32(app, VkApplicationInfo_sType, VK_STRUCTURE_TYPE_APPLICATION_INFO)
|
||||
vk_put_ptr(app, VkApplicationInfo_pApplicationName, "vk_compute")
|
||||
vk_put_i32(app, VkApplicationInfo_apiVersion, (1 << 22) | (3 << 12))
|
||||
let iext_names = bytes(8)
|
||||
let ici = bytes(VkInstanceCreateInfo_sizeof)
|
||||
vk_zero(ici, VkInstanceCreateInfo_sizeof)
|
||||
vk_put_i32(ici, VkInstanceCreateInfo_sType, VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO)
|
||||
vk_put_ptr(ici, VkInstanceCreateInfo_pApplicationInfo, app)
|
||||
if portability {
|
||||
vk_put_ptr(iext_names, 0, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)
|
||||
vk_put_i32(ici, VkInstanceCreateInfo_flags, VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR)
|
||||
vk_put_i32(ici, VkInstanceCreateInfo_enabledExtensionCount, 1)
|
||||
vk_put_ptr(ici, VkInstanceCreateInfo_ppEnabledExtensionNames, iext_names)
|
||||
}
|
||||
let out = bytes(8)
|
||||
var r = vk_create_instance(ici, null, out)
|
||||
if r != VK_SUCCESS { fail("vkCreateInstance", r) }
|
||||
let inst = vk_get_ptr(out, 0)
|
||||
|
||||
# ---- the first device, its first queue family that computes
|
||||
vk_put_i32(cnt, 0, 1)
|
||||
let devs = bytes(8)
|
||||
vk_enumerate_physical_devices(inst, cnt, devs)
|
||||
let pd = vk_get_ptr(devs, 0)
|
||||
let props = bytes(VkPhysicalDeviceProperties_sizeof)
|
||||
vk_get_physical_device_properties(pd, props)
|
||||
print(`vulkan: {string(vk_at(props, VkPhysicalDeviceProperties_deviceName))}`)
|
||||
vk_put_i32(cnt, 0, 0)
|
||||
vk_get_physical_device_queue_family_properties(pd, cnt, null)
|
||||
let nq = vk_get_i32(cnt, 0)
|
||||
let qprops = bytes(nq * VkQueueFamilyProperties_sizeof + 8)
|
||||
vk_get_physical_device_queue_family_properties(pd, cnt, qprops)
|
||||
var family = -1
|
||||
for q in 0 .. nq {
|
||||
let flags = vk_get_i32(qprops, q * VkQueueFamilyProperties_sizeof + VkQueueFamilyProperties_queueFlags)
|
||||
if family < 0 and (flags & VK_QUEUE_COMPUTE_BIT) != 0 { family = q }
|
||||
}
|
||||
vk_put_i32(cnt, 0, 0)
|
||||
vk_enumerate_device_extension_properties(pd, null, cnt, null)
|
||||
let nde = vk_get_i32(cnt, 0)
|
||||
let dexts = bytes(nde * VkExtensionProperties_sizeof + 8)
|
||||
vk_enumerate_device_extension_properties(pd, null, cnt, dexts)
|
||||
let prio = bytes(4)
|
||||
vk_put_i32(prio, 0, 0x3F800000)
|
||||
let qci = bytes(VkDeviceQueueCreateInfo_sizeof)
|
||||
vk_zero(qci, VkDeviceQueueCreateInfo_sizeof)
|
||||
vk_put_i32(qci, VkDeviceQueueCreateInfo_sType, VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO)
|
||||
vk_put_i32(qci, VkDeviceQueueCreateInfo_queueFamilyIndex, family)
|
||||
vk_put_i32(qci, VkDeviceQueueCreateInfo_queueCount, 1)
|
||||
vk_put_ptr(qci, VkDeviceQueueCreateInfo_pQueuePriorities, prio)
|
||||
let dext_names = bytes(8)
|
||||
let dci = bytes(VkDeviceCreateInfo_sizeof)
|
||||
vk_zero(dci, VkDeviceCreateInfo_sizeof)
|
||||
vk_put_i32(dci, VkDeviceCreateInfo_sType, VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO)
|
||||
vk_put_i32(dci, VkDeviceCreateInfo_queueCreateInfoCount, 1)
|
||||
vk_put_ptr(dci, VkDeviceCreateInfo_pQueueCreateInfos, qci)
|
||||
# a portability driver must be told the program knows it is one
|
||||
if has_ext(dexts, nde, "VK_KHR_portability_subset") {
|
||||
vk_put_ptr(dext_names, 0, "VK_KHR_portability_subset")
|
||||
vk_put_i32(dci, VkDeviceCreateInfo_enabledExtensionCount, 1)
|
||||
vk_put_ptr(dci, VkDeviceCreateInfo_ppEnabledExtensionNames, dext_names)
|
||||
}
|
||||
r = vk_create_device(pd, dci, null, out)
|
||||
if r != VK_SUCCESS { fail("vkCreateDevice", r) }
|
||||
dev = vk_get_ptr(out, 0)
|
||||
vk_get_device_queue(dev, family, 0, out)
|
||||
let queue = vk_get_ptr(out, 0)
|
||||
|
||||
# ---- a host-visible storage buffer for the picture
|
||||
let nbytes: long = W * H * 4
|
||||
let bci = bytes(VkBufferCreateInfo_sizeof)
|
||||
vk_zero(bci, VkBufferCreateInfo_sizeof)
|
||||
vk_put_i32(bci, VkBufferCreateInfo_sType, VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO)
|
||||
vk_put_i64(bci, VkBufferCreateInfo_size, nbytes)
|
||||
vk_put_i32(bci, VkBufferCreateInfo_usage, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT)
|
||||
vk_put_i32(bci, VkBufferCreateInfo_sharingMode, VK_SHARING_MODE_EXCLUSIVE)
|
||||
r = vk_create_buffer(dev, bci, null, out)
|
||||
if r != VK_SUCCESS { fail("vkCreateBuffer", r) }
|
||||
let buf = handle(out)
|
||||
let req = bytes(VkMemoryRequirements_sizeof)
|
||||
vk_get_buffer_memory_requirements(dev, buf, req)
|
||||
let mp = bytes(VkPhysicalDeviceMemoryProperties_sizeof)
|
||||
vk_get_physical_device_memory_properties(pd, mp)
|
||||
let want = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
|
||||
let allowed = vk_get_i32(req, VkMemoryRequirements_memoryTypeBits)
|
||||
var mtype = -1
|
||||
for t in 0 .. vk_get_i32(mp, VkPhysicalDeviceMemoryProperties_memoryTypeCount) {
|
||||
let pf = vk_get_i32(mp, VkPhysicalDeviceMemoryProperties_memoryTypes + t * VkMemoryType_sizeof + VkMemoryType_propertyFlags)
|
||||
if mtype < 0 and ((allowed >> t) & 1) == 1 and (pf & want) == want { mtype = t }
|
||||
}
|
||||
if mtype < 0 { print("vulkan: no host-visible memory type"); quit() }
|
||||
let mai = bytes(VkMemoryAllocateInfo_sizeof)
|
||||
vk_zero(mai, VkMemoryAllocateInfo_sizeof)
|
||||
vk_put_i32(mai, VkMemoryAllocateInfo_sType, VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO)
|
||||
vk_put_i64(mai, VkMemoryAllocateInfo_allocationSize, vk_get_i64(req, VkMemoryRequirements_size))
|
||||
vk_put_i32(mai, VkMemoryAllocateInfo_memoryTypeIndex, mtype)
|
||||
r = vk_allocate_memory(dev, mai, null, out)
|
||||
if r != VK_SUCCESS { fail("vkAllocateMemory", r) }
|
||||
let mem = handle(out)
|
||||
let zero: long = 0
|
||||
r = vk_bind_buffer_memory(dev, buf, mem, zero)
|
||||
if r != VK_SUCCESS { fail("vkBindBufferMemory", r) }
|
||||
|
||||
# ---- the shader, its layout, the pipeline
|
||||
let smci = bytes(VkShaderModuleCreateInfo_sizeof)
|
||||
vk_zero(smci, VkShaderModuleCreateInfo_sizeof)
|
||||
vk_put_i32(smci, VkShaderModuleCreateInfo_sType, VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO)
|
||||
let code_size: long = spv_len
|
||||
vk_put_i64(smci, VkShaderModuleCreateInfo_codeSize, code_size)
|
||||
vk_put_ptr(smci, VkShaderModuleCreateInfo_pCode, spv)
|
||||
r = vk_create_shader_module(dev, smci, null, out)
|
||||
if r != VK_SUCCESS { fail("vkCreateShaderModule", r) }
|
||||
let module = handle(out)
|
||||
|
||||
let binding = bytes(VkDescriptorSetLayoutBinding_sizeof)
|
||||
vk_zero(binding, VkDescriptorSetLayoutBinding_sizeof)
|
||||
vk_put_i32(binding, VkDescriptorSetLayoutBinding_binding, 0)
|
||||
vk_put_i32(binding, VkDescriptorSetLayoutBinding_descriptorType, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)
|
||||
vk_put_i32(binding, VkDescriptorSetLayoutBinding_descriptorCount, 1)
|
||||
vk_put_i32(binding, VkDescriptorSetLayoutBinding_stageFlags, VK_SHADER_STAGE_COMPUTE_BIT)
|
||||
let dslci = bytes(VkDescriptorSetLayoutCreateInfo_sizeof)
|
||||
vk_zero(dslci, VkDescriptorSetLayoutCreateInfo_sizeof)
|
||||
vk_put_i32(dslci, VkDescriptorSetLayoutCreateInfo_sType, VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO)
|
||||
vk_put_i32(dslci, VkDescriptorSetLayoutCreateInfo_bindingCount, 1)
|
||||
vk_put_ptr(dslci, VkDescriptorSetLayoutCreateInfo_pBindings, binding)
|
||||
let set_layouts = bytes(8)
|
||||
r = vk_create_descriptor_set_layout(dev, dslci, null, set_layouts)
|
||||
if r != VK_SUCCESS { fail("vkCreateDescriptorSetLayout", r) }
|
||||
|
||||
let pcr = bytes(VkPushConstantRange_sizeof)
|
||||
vk_put_i32(pcr, VkPushConstantRange_stageFlags, VK_SHADER_STAGE_COMPUTE_BIT)
|
||||
vk_put_i32(pcr, VkPushConstantRange_offset, 0)
|
||||
vk_put_i32(pcr, VkPushConstantRange_size, 12)
|
||||
let plci = bytes(VkPipelineLayoutCreateInfo_sizeof)
|
||||
vk_zero(plci, VkPipelineLayoutCreateInfo_sizeof)
|
||||
vk_put_i32(plci, VkPipelineLayoutCreateInfo_sType, VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO)
|
||||
vk_put_i32(plci, VkPipelineLayoutCreateInfo_setLayoutCount, 1)
|
||||
vk_put_ptr(plci, VkPipelineLayoutCreateInfo_pSetLayouts, set_layouts)
|
||||
vk_put_i32(plci, VkPipelineLayoutCreateInfo_pushConstantRangeCount, 1)
|
||||
vk_put_ptr(plci, VkPipelineLayoutCreateInfo_pPushConstantRanges, pcr)
|
||||
r = vk_create_pipeline_layout(dev, plci, null, out)
|
||||
if r != VK_SUCCESS { fail("vkCreatePipelineLayout", r) }
|
||||
let layout = handle(out)
|
||||
|
||||
let cpci = bytes(VkComputePipelineCreateInfo_sizeof)
|
||||
vk_zero(cpci, VkComputePipelineCreateInfo_sizeof)
|
||||
vk_put_i32(cpci, VkComputePipelineCreateInfo_sType, VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO)
|
||||
let st = VkComputePipelineCreateInfo_stage
|
||||
vk_put_i32(cpci, st + VkPipelineShaderStageCreateInfo_sType, VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO)
|
||||
vk_put_i32(cpci, st + VkPipelineShaderStageCreateInfo_stage, VK_SHADER_STAGE_COMPUTE_BIT)
|
||||
vk_put_i64(cpci, st + VkPipelineShaderStageCreateInfo_module, module)
|
||||
vk_put_ptr(cpci, st + VkPipelineShaderStageCreateInfo_pName, "main")
|
||||
vk_put_i64(cpci, VkComputePipelineCreateInfo_layout, layout)
|
||||
r = vk_create_compute_pipelines(dev, zero, 1, cpci, null, out)
|
||||
if r != VK_SUCCESS { fail("vkCreateComputePipelines", r) }
|
||||
let pipeline = handle(out)
|
||||
|
||||
# ---- the descriptor set that points the shader at the buffer
|
||||
let psz = bytes(VkDescriptorPoolSize_sizeof)
|
||||
vk_put_i32(psz, VkDescriptorPoolSize_type, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)
|
||||
vk_put_i32(psz, VkDescriptorPoolSize_descriptorCount, 1)
|
||||
let dpci = bytes(VkDescriptorPoolCreateInfo_sizeof)
|
||||
vk_zero(dpci, VkDescriptorPoolCreateInfo_sizeof)
|
||||
vk_put_i32(dpci, VkDescriptorPoolCreateInfo_sType, VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO)
|
||||
vk_put_i32(dpci, VkDescriptorPoolCreateInfo_maxSets, 1)
|
||||
vk_put_i32(dpci, VkDescriptorPoolCreateInfo_poolSizeCount, 1)
|
||||
vk_put_ptr(dpci, VkDescriptorPoolCreateInfo_pPoolSizes, psz)
|
||||
r = vk_create_descriptor_pool(dev, dpci, null, out)
|
||||
if r != VK_SUCCESS { fail("vkCreateDescriptorPool", r) }
|
||||
let pool = handle(out)
|
||||
let dsai = bytes(VkDescriptorSetAllocateInfo_sizeof)
|
||||
vk_zero(dsai, VkDescriptorSetAllocateInfo_sizeof)
|
||||
vk_put_i32(dsai, VkDescriptorSetAllocateInfo_sType, VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO)
|
||||
vk_put_i64(dsai, VkDescriptorSetAllocateInfo_descriptorPool, pool)
|
||||
vk_put_i32(dsai, VkDescriptorSetAllocateInfo_descriptorSetCount, 1)
|
||||
vk_put_ptr(dsai, VkDescriptorSetAllocateInfo_pSetLayouts, set_layouts)
|
||||
let sets = bytes(8)
|
||||
r = vk_allocate_descriptor_sets(dev, dsai, sets)
|
||||
if r != VK_SUCCESS { fail("vkAllocateDescriptorSets", r) }
|
||||
let dbi = bytes(VkDescriptorBufferInfo_sizeof)
|
||||
vk_put_i64(dbi, VkDescriptorBufferInfo_buffer, buf)
|
||||
vk_put_i64(dbi, VkDescriptorBufferInfo_offset, zero)
|
||||
vk_put_i64(dbi, VkDescriptorBufferInfo_range, nbytes)
|
||||
let wds = bytes(VkWriteDescriptorSet_sizeof)
|
||||
vk_zero(wds, VkWriteDescriptorSet_sizeof)
|
||||
vk_put_i32(wds, VkWriteDescriptorSet_sType, VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET)
|
||||
vk_put_i64(wds, VkWriteDescriptorSet_dstSet, vk_get_i64(sets, 0))
|
||||
vk_put_i32(wds, VkWriteDescriptorSet_dstBinding, 0)
|
||||
vk_put_i32(wds, VkWriteDescriptorSet_descriptorCount, 1)
|
||||
vk_put_i32(wds, VkWriteDescriptorSet_descriptorType, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)
|
||||
vk_put_ptr(wds, VkWriteDescriptorSet_pBufferInfo, dbi)
|
||||
vk_update_descriptor_sets(dev, 1, wds, 0, null)
|
||||
|
||||
# ---- record, submit, wait
|
||||
let cpi = bytes(VkCommandPoolCreateInfo_sizeof)
|
||||
vk_zero(cpi, VkCommandPoolCreateInfo_sizeof)
|
||||
vk_put_i32(cpi, VkCommandPoolCreateInfo_sType, VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO)
|
||||
vk_put_i32(cpi, VkCommandPoolCreateInfo_queueFamilyIndex, family)
|
||||
r = vk_create_command_pool(dev, cpi, null, out)
|
||||
if r != VK_SUCCESS { fail("vkCreateCommandPool", r) }
|
||||
let cmd_pool = handle(out)
|
||||
let cbai = bytes(VkCommandBufferAllocateInfo_sizeof)
|
||||
vk_zero(cbai, VkCommandBufferAllocateInfo_sizeof)
|
||||
vk_put_i32(cbai, VkCommandBufferAllocateInfo_sType, VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO)
|
||||
vk_put_i64(cbai, VkCommandBufferAllocateInfo_commandPool, cmd_pool)
|
||||
vk_put_i32(cbai, VkCommandBufferAllocateInfo_level, VK_COMMAND_BUFFER_LEVEL_PRIMARY)
|
||||
vk_put_i32(cbai, VkCommandBufferAllocateInfo_commandBufferCount, 1)
|
||||
let cbs = bytes(8)
|
||||
r = vk_allocate_command_buffers(dev, cbai, cbs)
|
||||
if r != VK_SUCCESS { fail("vkAllocateCommandBuffers", r) }
|
||||
let cb = vk_get_ptr(cbs, 0)
|
||||
let cbbi = bytes(VkCommandBufferBeginInfo_sizeof)
|
||||
vk_zero(cbbi, VkCommandBufferBeginInfo_sizeof)
|
||||
vk_put_i32(cbbi, VkCommandBufferBeginInfo_sType, VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO)
|
||||
vk_put_i32(cbbi, VkCommandBufferBeginInfo_flags, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT)
|
||||
r = vk_begin_command_buffer(cb, cbbi)
|
||||
if r != VK_SUCCESS { fail("vkBeginCommandBuffer", r) }
|
||||
vk_cmd_bind_pipeline(cb, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline)
|
||||
vk_cmd_bind_descriptor_sets(cb, VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0, 1, sets, 0, null)
|
||||
let pc = bytes(12)
|
||||
vk_put_i32(pc, 0, 0x3FC00000) # time = 1.5, as float bits
|
||||
vk_put_i32(pc, 4, W)
|
||||
vk_put_i32(pc, 8, H)
|
||||
vk_cmd_push_constants(cb, layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, 12, pc)
|
||||
vk_cmd_dispatch(cb, (W + 15) / 16, (H + 15) / 16, 1)
|
||||
r = vk_end_command_buffer(cb)
|
||||
if r != VK_SUCCESS { fail("vkEndCommandBuffer", r) }
|
||||
|
||||
let fci = bytes(VkFenceCreateInfo_sizeof)
|
||||
vk_zero(fci, VkFenceCreateInfo_sizeof)
|
||||
vk_put_i32(fci, VkFenceCreateInfo_sType, VK_STRUCTURE_TYPE_FENCE_CREATE_INFO)
|
||||
let fences = bytes(8)
|
||||
r = vk_create_fence(dev, fci, null, fences)
|
||||
if r != VK_SUCCESS { fail("vkCreateFence", r) }
|
||||
let si = bytes(VkSubmitInfo_sizeof)
|
||||
vk_zero(si, VkSubmitInfo_sizeof)
|
||||
vk_put_i32(si, VkSubmitInfo_sType, VK_STRUCTURE_TYPE_SUBMIT_INFO)
|
||||
vk_put_i32(si, VkSubmitInfo_commandBufferCount, 1)
|
||||
vk_put_ptr(si, VkSubmitInfo_pCommandBuffers, cbs)
|
||||
r = vk_queue_submit(queue, 1, si, vk_get_i64(fences, 0))
|
||||
if r != VK_SUCCESS { fail("vkQueueSubmit", r) }
|
||||
let forever: long = -1
|
||||
r = vk_wait_for_fences(dev, 1, fences, 1, forever)
|
||||
if r != VK_SUCCESS { fail("vkWaitForFences", r) }
|
||||
print(`vulkan: dispatched {(W + 15) / 16}x{(H + 15) / 16} groups and waited for the fence`)
|
||||
|
||||
# ---- read the picture back
|
||||
let pp = bytes(8)
|
||||
r = vk_map_memory(dev, mem, zero, nbytes, 0, pp)
|
||||
if r != VK_SUCCESS { fail("vkMapMemory", r) }
|
||||
let px = vk_get_ptr(pp, 0)
|
||||
var out_path = "build/vk_compute.ppm"
|
||||
if Os.has_env("VKC_OUT") { out_path = Os.env("VKC_OUT") }
|
||||
let f = file_open(out_path, "wb")
|
||||
let hdr = `P6\n{W} {H}\n255\n`
|
||||
file_write(f, hdr, len(hdr))
|
||||
let row = bytes(W * 3)
|
||||
var lit = 0
|
||||
for y in 0 .. H {
|
||||
for x in 0 .. W {
|
||||
let v = vk_get_i32(px, (y * W + x) * 4)
|
||||
row[x * 3] = v & 255; row[x * 3 + 1] = (v >> 8) & 255; row[x * 3 + 2] = (v >> 16) & 255
|
||||
if ((v >> 24) & 255) == 255 { lit += 1 }
|
||||
}
|
||||
file_write(f, row, W * 3)
|
||||
}
|
||||
file_close(f)
|
||||
vk_unmap_memory(dev, mem)
|
||||
print(`vulkan: {lit} of {W * H} pixels written by the shader -> {out_path}`)
|
||||
|
||||
vk_destroy_fence(dev, vk_get_i64(fences, 0), null)
|
||||
vk_destroy_command_pool(dev, cmd_pool, null)
|
||||
vk_destroy_descriptor_pool(dev, pool, null)
|
||||
vk_destroy_pipeline(dev, pipeline, null)
|
||||
vk_destroy_pipeline_layout(dev, layout, null)
|
||||
vk_destroy_descriptor_set_layout(dev, vk_get_i64(set_layouts, 0), null)
|
||||
vk_destroy_shader_module(dev, module, null)
|
||||
vk_destroy_buffer(dev, buf, null)
|
||||
vk_free_memory(dev, mem, null)
|
||||
vk_destroy_device(dev, null)
|
||||
vk_destroy_instance(inst, null)
|
||||
if lit == W * H { print("VKCOMPUTE OK") } else { print("VKCOMPUTE INCOMPLETE") }
|
||||
quit()
|
||||
}
|
||||
}
|
||||
35
examples/rendering/vk_compute.slang
Normal file
35
examples/rendering/vk_compute.slang
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// vk_compute.slang — the first Slang shader Ludic runs: a compute pass that writes a
|
||||
// picture into a storage buffer the CPU reads back. RGBA8 packed into one uint per
|
||||
// pixel, so the result needs no image, no staging copy and no format conversion.
|
||||
// slangc vk_compute.slang -target spirv -profile spirv_1_5 -entry main -stage compute -o vk_compute.spv
|
||||
|
||||
[[vk::binding(0, 0)]] RWStructuredBuffer<uint> pixels;
|
||||
|
||||
struct Params
|
||||
{
|
||||
float time;
|
||||
uint width;
|
||||
uint height;
|
||||
};
|
||||
[[vk::push_constant]] ConstantBuffer<Params> params;
|
||||
|
||||
uint pack(float3 c)
|
||||
{
|
||||
uint3 b = uint3(saturate(c) * 255.0 + 0.5);
|
||||
return b.r | (b.g << 8) | (b.b << 16) | (255u << 24);
|
||||
}
|
||||
|
||||
[shader("compute")]
|
||||
[numthreads(16, 16, 1)]
|
||||
void main(uint3 id : SV_DispatchThreadID)
|
||||
{
|
||||
if (id.x >= params.width || id.y >= params.height)
|
||||
return;
|
||||
float2 uv = (float2(id.xy) + 0.5) / float2(params.width, params.height);
|
||||
// a sky over a ridge line, so a wrong row order or a swapped channel is obvious
|
||||
float ridge = 0.55 + 0.08 * sin(uv.x * 9.0 + params.time) + 0.04 * sin(uv.x * 23.0);
|
||||
float3 sky = lerp(float3(0.95, 0.72, 0.45), float3(0.20, 0.35, 0.65), uv.y / ridge);
|
||||
float3 ground = lerp(float3(0.18, 0.28, 0.14), float3(0.08, 0.12, 0.07), (uv.y - ridge) / (1.0 - ridge));
|
||||
float3 c = uv.y < ridge ? sky : ground;
|
||||
pixels[id.y * params.width + id.x] = pack(c);
|
||||
}
|
||||
159
examples/rendering/vk_probe.ludic
Normal file
159
examples/rendering/vk_probe.ludic
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
# vk_probe.ludic — what a machine's Vulkan can do, through the generated bindings.
|
||||
#
|
||||
# Opens the loader at run time, makes an instance (with portability enumeration
|
||||
# where the loader offers it, which is how MoltenVK is found on a Mac), and for
|
||||
# every GPU says whether it meets the Vulkan renderer's floor - Vulkan 1.3 with
|
||||
# dynamic rendering, synchronization2, descriptor indexing, buffer device
|
||||
# addresses, timeline semaphores and indirect count draws - and which optional
|
||||
# features it would take. Then it opens a device with a graphics queue.
|
||||
# Windows: bin/ludicc examples/rendering/vk_probe.ludic --headless
|
||||
# (Vk.* links the Vulkan runtime)
|
||||
# macOS: the same with vk_mac.ll, and VULKAN_SDK set to the SDK's macOS directory
|
||||
program VkProbe {
|
||||
|
||||
property Marker { on: int = 1 }
|
||||
model Anchor { Marker }
|
||||
|
||||
const API_1_0: int = 4194304 # VK_MAKE_API_VERSION(0, 1, 0, 0)
|
||||
|
||||
function api_text(v: int) -> string { return `{(v >> 22) & 127}.{(v >> 12) & 1023}.{v & 4095}` }
|
||||
function yes(b: bool) -> string { if b { return "yes" }; return "no " }
|
||||
|
||||
# a name in an array of VkExtensionProperties
|
||||
function has_ext(props: bytes, n: int, want: string) -> bool {
|
||||
for i in 0 .. n {
|
||||
let at = vk_at(props, i * VkExtensionProperties_sizeof + VkExtensionProperties_extensionName)
|
||||
if string(at) == want { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
handler Boot phase Start {
|
||||
spawn Anchor {}
|
||||
if Vk.open() == 0 { print("vulkan: no loader on this machine - it would run OpenGL"); quit() }
|
||||
let ver = bytes(4)
|
||||
vk_put_i32(ver, 0, API_1_0)
|
||||
if vk_has("vkEnumerateInstanceVersion") == 1 { vk_enumerate_instance_version(ver) }
|
||||
print(`vulkan: loader {api_text(vk_get_i32(ver, 0))}`)
|
||||
|
||||
let cnt = bytes(4)
|
||||
vk_put_i32(cnt, 0, 0)
|
||||
vk_enumerate_instance_extension_properties(null, cnt, null)
|
||||
let nie = vk_get_i32(cnt, 0)
|
||||
let iexts = bytes(nie * VkExtensionProperties_sizeof + 8)
|
||||
vk_enumerate_instance_extension_properties(null, cnt, iexts)
|
||||
let portability = has_ext(iexts, nie, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)
|
||||
let colorspace = has_ext(iexts, nie, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME)
|
||||
|
||||
let app = bytes(VkApplicationInfo_sizeof)
|
||||
vk_zero(app, VkApplicationInfo_sizeof)
|
||||
vk_put_i32(app, VkApplicationInfo_sType, VK_STRUCTURE_TYPE_APPLICATION_INFO)
|
||||
vk_put_ptr(app, VkApplicationInfo_pApplicationName, "Ludic")
|
||||
vk_put_ptr(app, VkApplicationInfo_pEngineName, "Ludic")
|
||||
vk_put_i32(app, VkApplicationInfo_apiVersion, (1 << 22) | (3 << 12))
|
||||
let names = bytes(16)
|
||||
var nnames = 0
|
||||
if portability { vk_put_ptr(names, 0, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); nnames = 1 }
|
||||
let ici = bytes(VkInstanceCreateInfo_sizeof)
|
||||
vk_zero(ici, VkInstanceCreateInfo_sizeof)
|
||||
vk_put_i32(ici, VkInstanceCreateInfo_sType, VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO)
|
||||
vk_put_ptr(ici, VkInstanceCreateInfo_pApplicationInfo, app)
|
||||
if portability { vk_put_i32(ici, VkInstanceCreateInfo_flags, VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR) }
|
||||
vk_put_i32(ici, VkInstanceCreateInfo_enabledExtensionCount, nnames)
|
||||
vk_put_ptr(ici, VkInstanceCreateInfo_ppEnabledExtensionNames, names)
|
||||
let out = bytes(8)
|
||||
let r = vk_create_instance(ici, null, out)
|
||||
if r != VK_SUCCESS { print(`vulkan: vkCreateInstance failed, VkResult {r}`); quit() }
|
||||
let inst = vk_get_ptr(out, 0)
|
||||
print(`vulkan: instance (portability {yes(portability)}, HDR colour spaces {yes(colorspace)})`)
|
||||
|
||||
vk_put_i32(cnt, 0, 0)
|
||||
vk_enumerate_physical_devices(inst, cnt, null)
|
||||
let ndev = vk_get_i32(cnt, 0)
|
||||
let devs = bytes(ndev * 8 + 8)
|
||||
vk_enumerate_physical_devices(inst, cnt, devs)
|
||||
let props = bytes(VkPhysicalDeviceProperties_sizeof)
|
||||
for d in 0 .. ndev {
|
||||
let pd = vk_get_ptr(devs, d * 8)
|
||||
vk_get_physical_device_properties(pd, props)
|
||||
let api = vk_get_i32(props, VkPhysicalDeviceProperties_apiVersion)
|
||||
print(`vulkan: device {d}: {string(vk_at(props, VkPhysicalDeviceProperties_deviceName))} (type {vk_get_i32(props, VkPhysicalDeviceProperties_deviceType)}, api {api_text(api)})`)
|
||||
|
||||
let f13 = bytes(VkPhysicalDeviceVulkan13Features_sizeof)
|
||||
vk_zero(f13, VkPhysicalDeviceVulkan13Features_sizeof)
|
||||
vk_put_i32(f13, VkPhysicalDeviceVulkan13Features_sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES)
|
||||
let f12 = bytes(VkPhysicalDeviceVulkan12Features_sizeof)
|
||||
vk_zero(f12, VkPhysicalDeviceVulkan12Features_sizeof)
|
||||
vk_put_i32(f12, VkPhysicalDeviceVulkan12Features_sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES)
|
||||
vk_put_ptr(f12, VkPhysicalDeviceVulkan12Features_pNext, f13)
|
||||
let f2 = bytes(VkPhysicalDeviceFeatures2_sizeof)
|
||||
vk_zero(f2, VkPhysicalDeviceFeatures2_sizeof)
|
||||
vk_put_i32(f2, VkPhysicalDeviceFeatures2_sType, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2)
|
||||
vk_put_ptr(f2, VkPhysicalDeviceFeatures2_pNext, f12)
|
||||
vk_get_physical_device_features2(pd, f2)
|
||||
let dyn = vk_get_i32(f13, VkPhysicalDeviceVulkan13Features_dynamicRendering) == 1
|
||||
let sync2 = vk_get_i32(f13, VkPhysicalDeviceVulkan13Features_synchronization2) == 1
|
||||
let bindless = vk_get_i32(f12, VkPhysicalDeviceVulkan12Features_descriptorIndexing) == 1 and vk_get_i32(f12, VkPhysicalDeviceVulkan12Features_runtimeDescriptorArray) == 1
|
||||
let bda = vk_get_i32(f12, VkPhysicalDeviceVulkan12Features_bufferDeviceAddress) == 1
|
||||
let timeline = vk_get_i32(f12, VkPhysicalDeviceVulkan12Features_timelineSemaphore) == 1
|
||||
let indirect = vk_get_i32(f12, VkPhysicalDeviceVulkan12Features_drawIndirectCount) == 1
|
||||
let major = (api >> 22) & 127
|
||||
let minor = (api >> 12) & 1023
|
||||
let is13 = major > 1 or (major == 1 and minor >= 3)
|
||||
let floor = is13 and dyn and sync2 and bindless and bda and timeline and indirect
|
||||
print(`vulkan: floor (1.3): {yes(floor)} | api {yes(is13)} dynamic rendering {yes(dyn)} sync2 {yes(sync2)} bindless {yes(bindless)} buffer addresses {yes(bda)} timeline {yes(timeline)} indirect count {yes(indirect)}`)
|
||||
|
||||
vk_put_i32(cnt, 0, 0)
|
||||
vk_enumerate_device_extension_properties(pd, null, cnt, null)
|
||||
let nde = vk_get_i32(cnt, 0)
|
||||
let dexts = bytes(nde * VkExtensionProperties_sizeof + 8)
|
||||
vk_enumerate_device_extension_properties(pd, null, cnt, dexts)
|
||||
let rt = has_ext(dexts, nde, VK_KHR_RAY_QUERY_EXTENSION_NAME) and has_ext(dexts, nde, VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME)
|
||||
print(`vulkan: optional: ray query {yes(rt)} | mesh shaders {yes(has_ext(dexts, nde, VK_EXT_MESH_SHADER_EXTENSION_NAME))} | opacity micromaps {yes(has_ext(dexts, nde, VK_EXT_OPACITY_MICROMAP_EXTENSION_NAME))} | shading rate {yes(has_ext(dexts, nde, VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME))} | memory budget {yes(has_ext(dexts, nde, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME))} | pipeline library {yes(has_ext(dexts, nde, VK_EXT_GRAPHICS_PIPELINE_LIBRARY_EXTENSION_NAME))} | Reflex {yes(has_ext(dexts, nde, VK_NV_LOW_LATENCY_2_EXTENSION_NAME))}`)
|
||||
|
||||
vk_put_i32(cnt, 0, 0)
|
||||
vk_get_physical_device_queue_family_properties(pd, cnt, null)
|
||||
let nq = vk_get_i32(cnt, 0)
|
||||
let qprops = bytes(nq * VkQueueFamilyProperties_sizeof + 8)
|
||||
vk_get_physical_device_queue_family_properties(pd, cnt, qprops)
|
||||
var gfx = -1
|
||||
var compute_only = -1
|
||||
var transfer_only = -1
|
||||
for q in 0 .. nq {
|
||||
let flags = vk_get_i32(qprops, q * VkQueueFamilyProperties_sizeof + VkQueueFamilyProperties_queueFlags)
|
||||
if gfx < 0 and (flags & VK_QUEUE_GRAPHICS_BIT) != 0 { gfx = q }
|
||||
if compute_only < 0 and (flags & VK_QUEUE_GRAPHICS_BIT) == 0 and (flags & VK_QUEUE_COMPUTE_BIT) != 0 { compute_only = q }
|
||||
if transfer_only < 0 and (flags & (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT)) == 0 and (flags & VK_QUEUE_TRANSFER_BIT) != 0 { transfer_only = q }
|
||||
}
|
||||
print(`vulkan: queues: {nq} families, graphics {gfx}, dedicated compute {compute_only}, dedicated transfer {transfer_only}`)
|
||||
if gfx >= 0 {
|
||||
let prio = bytes(4)
|
||||
vk_put_i32(prio, 0, 0x3F800000)
|
||||
let qci = bytes(VkDeviceQueueCreateInfo_sizeof)
|
||||
vk_zero(qci, VkDeviceQueueCreateInfo_sizeof)
|
||||
vk_put_i32(qci, VkDeviceQueueCreateInfo_sType, VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO)
|
||||
vk_put_i32(qci, VkDeviceQueueCreateInfo_queueFamilyIndex, gfx)
|
||||
vk_put_i32(qci, VkDeviceQueueCreateInfo_queueCount, 1)
|
||||
vk_put_ptr(qci, VkDeviceQueueCreateInfo_pQueuePriorities, prio)
|
||||
let dci = bytes(VkDeviceCreateInfo_sizeof)
|
||||
vk_zero(dci, VkDeviceCreateInfo_sizeof)
|
||||
vk_put_i32(dci, VkDeviceCreateInfo_sType, VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO)
|
||||
vk_put_i32(dci, VkDeviceCreateInfo_queueCreateInfoCount, 1)
|
||||
vk_put_ptr(dci, VkDeviceCreateInfo_pQueueCreateInfos, qci)
|
||||
let dout = bytes(8)
|
||||
let dr = vk_create_device(pd, dci, null, dout)
|
||||
if dr != VK_SUCCESS { print(`vulkan: vkCreateDevice failed, VkResult {dr}`) }
|
||||
else {
|
||||
let dev = vk_get_ptr(dout, 0)
|
||||
let qout = bytes(8)
|
||||
vk_get_device_queue(dev, gfx, 0, qout)
|
||||
print(`vulkan: device opened, graphics queue {yes(vk_get_ptr(qout, 0) != null)}`)
|
||||
vk_destroy_device(dev, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
vk_destroy_instance(inst, null)
|
||||
print("VKPROBE OK")
|
||||
quit()
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
@ -909,6 +909,18 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
# 478 gl3.h entry points bound as externs gl_<snake_name>, plus the Ludic
|
||||
# helpers (gl_open / gl_swap / gl_screenshot / gl_program / …). Labels are the
|
||||
# declaration's parameter names, so `Gl.clear_color(red: 0.1, …)` works.
|
||||
# Vk.* — Vulkan (runtime/native/vk.ludic + the generated vk_api.ludic): the
|
||||
# registry's commands as externs vk_<snake_name>, and the loader and struct helpers.
|
||||
if (ns == "Vk") and (bare == null) {
|
||||
bare = "vk_" + meth
|
||||
var vdecl = find_fn(bare)
|
||||
if (vdecl == null) { vdecl = find_extern(bare) }
|
||||
if (vdecl != null) {
|
||||
let vnames = param_labels(vdecl)
|
||||
var vi = 0
|
||||
while vi < len(vnames) { push(labels, vnames[vi]); vi += 1 }
|
||||
}
|
||||
}
|
||||
if (ns == "Gl") and (bare == null) {
|
||||
bare = "gl_" + meth
|
||||
var gdecl = find_fn(bare)
|
||||
|
|
|
|||
|
|
@ -251,6 +251,8 @@ function p_postfix() -> Node {
|
|||
if e.a.kind == E_ID and e.a.s == "Http" { g_uses_http = true }
|
||||
# Gl.* — any Gl method splices the OpenGL runtime (and links the GL backend).
|
||||
if e.a.kind == E_ID and e.a.s == "Gl" { g_uses_gl = true }
|
||||
# Vk.* — any Vk method splices the Vulkan runtime (and links its loader)
|
||||
if e.a.kind == E_ID and e.a.s == "Vk" { g_uses_vk = true }
|
||||
# Tween.to/chain/delay/value/stop/parallel (#48): the fluent stateful handles
|
||||
# live in tween.ludic, advanced by an engine-owned system each Update tick.
|
||||
if e.a.kind == E_ID and e.a.s == "Tween" and (e.s == "to" or e.s == "chain" or e.s == "delay" or e.s == "value" or e.s == "stop" or e.s == "parallel") { g_uses_tween_rt = true }
|
||||
|
|
@ -673,6 +675,7 @@ var g_uses_fx: bool = false # Fx.sparks/number/clear -> splice fx.ludic; f
|
|||
var g_uses_audio: bool = false # Audio.* (#22) -> splice audio.ludic; a windowed build also links audio.ll + AVFoundation
|
||||
var g_uses_http: bool = false # Http.* (#6) -> splice http.ludic; links http.ll + Foundation (macOS)
|
||||
var g_uses_gl: bool = false # Gl.* -> splice gl.ludic (+ generated gl_api.ludic); links gl.ll + gl_thunks.ll + OpenGL
|
||||
var g_uses_vk: bool = false # Vk.* -> splice vk.ludic (+ generated vk_api.ludic); links vk_thunks.ll + the platform loader
|
||||
# issue #64: functions marked @System(Phase) in a prebuilt binary module — the
|
||||
# compiler registers each with the host at load (it supplies the fn address,
|
||||
# which Ludic source cannot take). Parallel arrays: fn name -> phase name.
|
||||
|
|
@ -1168,6 +1171,13 @@ function maybe_splice_runtime() -> void {
|
|||
do_import("runtime/native/gl.ludic")
|
||||
cur_dir = saved
|
||||
}
|
||||
# Vk.*: splice the Vulkan surface (vk.ludic + the generated vk_api.ludic). The
|
||||
# loader is opened at run time, so a program that never calls Vk.open needs none.
|
||||
if g_uses_vk {
|
||||
cur_dir = ""
|
||||
do_import("runtime/native/vk.ludic")
|
||||
cur_dir = saved
|
||||
}
|
||||
# Anim.play/Motion.to sugar (#48): the writes live in systems.ludic and use the
|
||||
# reflection ABI, so splice it and force the world table even when the game does
|
||||
# not otherwise trip uses_engine_systems.
|
||||
|
|
@ -1239,6 +1249,7 @@ function parse_program() -> void {
|
|||
g_toggled_layers = new []pointer
|
||||
g_uses_regex = false
|
||||
g_uses_gl = false
|
||||
g_uses_vk = false
|
||||
g_uses_bignum = false
|
||||
g_uses_dict = false
|
||||
g_uses_numeric = false
|
||||
|
|
|
|||
41157
selfhost/ludicc.seed.ll
41157
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -294,6 +294,15 @@ entry {
|
|||
cmd = `{cmd} {gl} {glt} -framework OpenGL`
|
||||
}
|
||||
}
|
||||
# Vk.* links the generated thunks and the loader that fills them at run time:
|
||||
# vk_win.ll (vulkan-1.dll) on Windows, vk_mac.ll (libvulkan.1.dylib, MoltenVK) on
|
||||
# macOS. Neither links a Vulkan library, so the program starts without one.
|
||||
if g_uses_vk {
|
||||
let vkt = join_path(home, "runtime/native/vk_thunks.ll")
|
||||
var vkl = join_path(home, "runtime/native/vk_mac.ll")
|
||||
if g_target_win { vkl = join_path(home, "runtime/native/vk_win.ll") }
|
||||
cmd = `{cmd} {vkt} {vkl}`
|
||||
}
|
||||
# A shipped Windows game is a GUI-subsystem program: started from Explorer, a
|
||||
# console-subsystem one opens a console window behind the game. mainCRTStartup keeps
|
||||
# the C runtime's ordinary main() entry rather than WinMain.
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool
|
|||
|
||||
if mode == 2 {
|
||||
if not shq(`{ludicc()} --headless {src} --emit-llvm -o {ll}`) { return false }
|
||||
if not shq(`{cc()} -O2 {ll}{gl_link_flags(ll)}{http_link_flags(ll)}{pbf} -o {out}`) { return false }
|
||||
if not shq(`{cc()} -O2 {ll}{gl_link_flags(ll)}{vk_link_flags(ll)}{http_link_flags(ll)}{pbf} -o {out}`) { return false }
|
||||
if not save { shell(`rm -f {ll}`) }
|
||||
return true
|
||||
}
|
||||
|
|
@ -58,7 +58,7 @@ function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool
|
|||
# canonical `ludicc -o` path links it only when Audio.* is used.
|
||||
let cocoa = `{home}runtime/native/cocoa.ll`
|
||||
let audio = `{home}runtime/native/audio.ll`
|
||||
if not shq(`{cc()} -O2 {ll} {cocoa} {audio} -framework Cocoa -Wl,-needed_framework,GameController -Wl,-needed_framework,AVFoundation -Wl,-rpath,@loader_path{gl_link_flags(ll)}{http_link_flags(ll)}{pbf} -o {out}`) { return false }
|
||||
if not shq(`{cc()} -O2 {ll} {cocoa} {audio} -framework Cocoa -Wl,-needed_framework,GameController -Wl,-needed_framework,AVFoundation -Wl,-rpath,@loader_path{gl_link_flags(ll)}{vk_link_flags(ll)}{http_link_flags(ll)}{pbf} -o {out}`) { return false }
|
||||
if not save { shell(`rm -f {ll}`) }
|
||||
return true
|
||||
}
|
||||
|
|
@ -71,6 +71,15 @@ function gl_link_flags(ll: pointer) -> pointer {
|
|||
return ` {home}runtime/native/gl.ll {home}runtime/native/gl_thunks.ll -framework OpenGL`
|
||||
}
|
||||
|
||||
# A program that uses Vk.* references the @lvk_* thunks; link them and the loader that
|
||||
# fills them at run time (vk_mac.ll opens libvulkan.1.dylib - MoltenVK - when it is
|
||||
# there). No Vulkan library is linked, so the program still starts on a Mac without one.
|
||||
function vk_link_flags(ll: pointer) -> pointer {
|
||||
if not shq(`grep -q "@lvk_" {ll}`) { return "" }
|
||||
let home = ludic_home()
|
||||
return ` {home}runtime/native/vk_thunks.ll {home}runtime/native/vk_mac.ll`
|
||||
}
|
||||
|
||||
# A program that uses Http.* calls the @hs_* transport; link http.ll and Foundation only
|
||||
# then, in both modes, as `ludicc -o` does (selfhost/main.ludic). Without it a Http.*
|
||||
# program built through `ludic build` failed to link on every hs_* symbol.
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ program LudicDev {
|
|||
import "docgen_gen.ludic"
|
||||
import "docgen_check.ludic"
|
||||
import "glgen.ludic"
|
||||
import "vkgen.ludic"
|
||||
import "assets.ludic"
|
||||
import "release.ludic"
|
||||
import "pkg.ludic"
|
||||
|
|
@ -69,6 +70,7 @@ program LudicDev {
|
|||
print(" docs-check [DIR] coverage/integrity guard over a generated docs site")
|
||||
print(" docs-palette [--check] regenerate emit_color.ludic + palette.json from the palette table")
|
||||
print(" glgen [--check] regenerate gl_api.ludic + gl_thunks.ll from the platform gl3.h")
|
||||
print(" vkgen [--xml f] [--check] regenerate vk_api.ludic + vk_thunks.ll from the Vulkan registry")
|
||||
print("")
|
||||
print("release:")
|
||||
print(" release [major|minor|patch] [--dry-run] [--publish]")
|
||||
|
|
@ -106,6 +108,7 @@ program LudicDev {
|
|||
if (cmd == "lint-asset") { return cmd_lint_asset() }
|
||||
if (cmd == "docs-palette") { return cmd_docs_palette() }
|
||||
if (cmd == "glgen") { return cmd_glgen() }
|
||||
if (cmd == "vkgen") { return cmd_vkgen() }
|
||||
if (cmd == "fetch-assets") { return cmd_fetch_assets() }
|
||||
if (cmd == "docs-gen") { return cmd_docs_gen() }
|
||||
if (cmd == "docs-check") { return cmd_docs_check() }
|
||||
|
|
|
|||
|
|
@ -809,5 +809,13 @@ function cmd_dev_test() -> int {
|
|||
} else { bad2("ludic-dev glgen --check", capture_line(`tail -1 {tmp_dir()}/glgen.out`)) }
|
||||
} else { skip("ludic-dev glgen --check (needs the macOS OpenGL headers)") }
|
||||
|
||||
# and the Vulkan one: vk_api.ludic + vk_thunks.ll regenerated from the registry's vk.xml,
|
||||
# which comes with the Vulkan SDK - so this guard runs wherever the SDK is installed
|
||||
if shq("ls $VULKAN_SDK/share/vulkan/registry/vk.xml $HOME/VulkanSDK/*/macOS/share/vulkan/registry/vk.xml >/dev/null 2>&1") {
|
||||
if shq(`bin/ludic-dev vkgen --check > {tmp_dir()}/vkgen.out 2>&1`) {
|
||||
ok("ludic-dev vkgen regenerates vk_api.ludic + vk_thunks.ll byte-identically")
|
||||
} else { bad2("ludic-dev vkgen --check", capture_line(`tail -1 {tmp_dir()}/vkgen.out`)) }
|
||||
} else { skip("ludic-dev vkgen --check (needs the Vulkan SDK's vk.xml)") }
|
||||
|
||||
return report()
|
||||
}
|
||||
|
|
|
|||
928
tools/ludic-cli/vkgen.ludic
Normal file
928
tools/ludic-cli/vkgen.ludic
Normal file
|
|
@ -0,0 +1,928 @@
|
|||
# vkgen.ludic — the Vulkan binding generator, in Ludic (glgen.ludic's sibling).
|
||||
#
|
||||
# ludic-dev vkgen [--xml <vk.xml>] [--check]
|
||||
# read the Vulkan registry and emit
|
||||
# runtime/native/vk_api.ludic constants, struct sizes and field offsets, externs
|
||||
# runtime/native/vk_thunks.ll one thunk per command, calling through a table
|
||||
# that @lvk_bind fills from the platform loader
|
||||
#
|
||||
# Vulkan is too large to bind by hand: the renderer fills hundreds of structs, and a
|
||||
# wrong offset raises no error - the driver reads garbage. So the layout is computed
|
||||
# here from the registry, once, with the x64 / arm64 C rules (both LP64 for this
|
||||
# purpose: pointers and handles 8 bytes, enums 4), and Ludic code writes fields by
|
||||
# name: vk_put_i32(ci, VkDeviceCreateInfo_queueCreateInfoCount, 1), and sizes them
|
||||
# with <Struct>_sizeof (not _size: VkBufferCreateInfo has a field called size).
|
||||
#
|
||||
# What is bound is Vulkan 1.0-1.4 core plus the extensions in VKG_EXTENSIONS - the
|
||||
# ones the Vulkan renderer is designed around. Numbers follow the renderer's rules:
|
||||
# a C float parameter is an IEEE bit pattern in an int (not fixed), a 64-bit value
|
||||
# (uint64_t, VkDeviceSize, a non-dispatchable handle) is a `long`.
|
||||
#
|
||||
# The registry is found at --xml, else $VULKAN_SDK/share/vulkan/registry/vk.xml,
|
||||
# else the newest ~/VulkanSDK/*/macOS/share/vulkan/registry/vk.xml.
|
||||
|
||||
const VKG_EXTENSIONS: string = "VK_KHR_surface VK_KHR_win32_surface VK_EXT_metal_surface VK_KHR_portability_enumeration VK_KHR_portability_subset VK_EXT_debug_utils VK_KHR_swapchain VK_EXT_swapchain_colorspace VK_EXT_hdr_metadata VK_KHR_present_id VK_KHR_present_wait VK_NV_low_latency2 VK_EXT_memory_budget VK_EXT_mesh_shader VK_KHR_deferred_host_operations VK_KHR_acceleration_structure VK_KHR_ray_query VK_EXT_opacity_micromap VK_KHR_fragment_shading_rate VK_KHR_pipeline_library VK_EXT_graphics_pipeline_library VK_EXT_descriptor_buffer VK_KHR_dynamic_rendering_local_read VK_EXT_pageable_device_local_memory VK_KHR_pipeline_binary VK_EXT_calibrated_timestamps VK_KHR_get_surface_capabilities2 VK_EXT_memory_priority"
|
||||
|
||||
# ---- a string -> int map --------------------------------------------------------------
|
||||
# (the tools already define a set_has, so the spliced Dict runtime cannot come in here)
|
||||
property VkMap { keys: pointers, vals: words, cap: int = 0, count: int = 0 }
|
||||
function vkm_new() -> VkMap {
|
||||
let m = new VkMap
|
||||
m.cap = 16384
|
||||
m.keys = bytes(m.cap * 8)
|
||||
for i in 0 .. m.cap { m.keys[i] = null }
|
||||
m.vals = words(m.cap)
|
||||
return m
|
||||
}
|
||||
function vkm_slot(m: VkMap, key: pointer) -> int {
|
||||
var h = 5381
|
||||
let n = slen(key)
|
||||
for i in 0 .. n { h = ((h * 33) + key[i]) & 0x7FFFFFFF }
|
||||
var at = h % m.cap
|
||||
while m.keys[at] != null and not (m.keys[at] == key) { at = (at + 1) % m.cap }
|
||||
return at
|
||||
}
|
||||
function vkm_grow(m: VkMap) -> void {
|
||||
let ok = m.keys; let ov = m.vals; let oc = m.cap
|
||||
m.cap = oc * 2
|
||||
m.keys = bytes(m.cap * 8)
|
||||
for i in 0 .. m.cap { m.keys[i] = null }
|
||||
m.vals = words(m.cap)
|
||||
m.count = 0
|
||||
for i in 0 .. oc { if ok[i] != null { vkm_set(m, ok[i], ov[i]) } }
|
||||
}
|
||||
function vkm_set(m: VkMap, key: pointer, v: int) -> void {
|
||||
if (m.count + 1) * 10 > m.cap * 7 { vkm_grow(m) }
|
||||
let at = vkm_slot(m, key)
|
||||
if m.keys[at] == null { m.keys[at] = key; m.count += 1 }
|
||||
m.vals[at] = v
|
||||
}
|
||||
function vkm_get_or(m: VkMap, key: pointer, dflt: int) -> int {
|
||||
let at = vkm_slot(m, key)
|
||||
if m.keys[at] == null { return dflt }
|
||||
return m.vals[at]
|
||||
}
|
||||
|
||||
# ---- xml helpers --------------------------------------------------------------
|
||||
# The registry is regular enough to scan: every tag this generator reads opens and
|
||||
# closes the way the spec's own generators expect.
|
||||
|
||||
# the value of attribute `name` inside the tag text `tag`, or null
|
||||
function vkg_attr(tag: pointer, name: pointer) -> pointer {
|
||||
let key = ` {name}="`
|
||||
let at = s_index(tag, key, 0)
|
||||
if at < 0 { return null }
|
||||
let from = at + slen(key)
|
||||
let to = s_index(tag, "\"", from)
|
||||
if to < 0 { return null }
|
||||
return str_sub(tag, from, to)
|
||||
}
|
||||
|
||||
# string equality that treats a missing value (null) as equal to nothing: Ludic's `==`
|
||||
# on strings reads through both sides, and an absent attribute is null
|
||||
function vkg_eq(a: pointer, b: pointer) -> bool {
|
||||
if a == null or b == null { return false }
|
||||
return a == b
|
||||
}
|
||||
|
||||
# whole-token membership in a comma-separated list ("vulkan,vulkansc")
|
||||
function vkg_in_csv(list: pointer, want: pointer) -> bool {
|
||||
if list == null { return false }
|
||||
let n = slen(list)
|
||||
var p = 0
|
||||
while p <= n {
|
||||
var q = p
|
||||
while q < n and list[q] != ',' { q += 1 }
|
||||
if str_sub(list, p, q) == want { return true }
|
||||
if q >= n { break }
|
||||
p = q + 1
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
# a tag or element applies to desktop Vulkan when it names no api, or names "vulkan"
|
||||
function vkg_api_ok(tag: pointer) -> bool {
|
||||
let api = vkg_attr(tag, "api")
|
||||
if api == null { return true }
|
||||
return vkg_in_csv(api, "vulkan")
|
||||
}
|
||||
|
||||
# text with <comment>...</comment> removed and every other tag replaced by a space
|
||||
function vkg_strip(s: pointer) -> pointer {
|
||||
let b = sb_new()
|
||||
let n = slen(s)
|
||||
var i = 0
|
||||
while i < n {
|
||||
if s[i] == '<' {
|
||||
if s_starts_at(s, i, "<comment>") {
|
||||
let e = s_index(s, "</comment>", i)
|
||||
if e < 0 { break }
|
||||
i = e + 10
|
||||
continue
|
||||
}
|
||||
while i < n and s[i] != '>' { i += 1 }
|
||||
sb_putc(b, 32)
|
||||
i += 1
|
||||
continue
|
||||
}
|
||||
sb_putc(b, s[i])
|
||||
i += 1
|
||||
}
|
||||
return sb_str(b)
|
||||
}
|
||||
|
||||
# the text between <tag> and </tag> after `from`, or null
|
||||
function vkg_inner(s: pointer, tag: pointer) -> pointer {
|
||||
# <name> or <name alias="...">: the registry puts attributes on inner tags too
|
||||
var a = s_index(s, `<{tag}>`, 0)
|
||||
let b = s_index(s, `<{tag} `, 0)
|
||||
if a < 0 or (b >= 0 and b < a) { a = b }
|
||||
if a < 0 { return null }
|
||||
let gt = s_index(s, ">", a)
|
||||
if gt < 0 { return null }
|
||||
let from = gt + 1
|
||||
let e = s_index(s, `</{tag}>`, from)
|
||||
if e < 0 { return null }
|
||||
return str_sub(s, from, e)
|
||||
}
|
||||
|
||||
function vkg_unquote(v: pointer) -> pointer {
|
||||
if v == null { return null }
|
||||
if s_starts(v, """) and slen(v) >= 12 { return str_sub(v, 6, slen(v) - 6) }
|
||||
return v
|
||||
}
|
||||
|
||||
# ---- one declarator: <member>/<param> -> base type, pointer depth, name, array, bits
|
||||
var vkg_d_base: pointer = null
|
||||
var vkg_d_star: int = 0
|
||||
var vkg_d_name: pointer = null
|
||||
var vkg_d_count: int = 1 # product of every [N]; 1 when not an array
|
||||
var vkg_d_array: bool = false
|
||||
var vkg_d_bits: int = 0 # a bitfield width, 0 when not one
|
||||
|
||||
function vkg_decl(body: pointer) -> void {
|
||||
vkg_d_base = vkg_inner(body, "type")
|
||||
vkg_d_name = vkg_inner(body, "name")
|
||||
vkg_d_star = 0
|
||||
vkg_d_count = 1
|
||||
vkg_d_array = false
|
||||
vkg_d_bits = 0
|
||||
let flat = vkg_strip(body)
|
||||
let n = slen(flat)
|
||||
var i = 0
|
||||
while i < n {
|
||||
if flat[i] == '*' { vkg_d_star += 1 }
|
||||
i += 1
|
||||
}
|
||||
# everything after the name: [N], [ENUM], :bits
|
||||
let ne = s_index(body, "</name>", 0)
|
||||
if ne < 0 { return }
|
||||
let tail = vkg_strip(str_sub(body, ne + 7, slen(body)))
|
||||
let tn = slen(tail)
|
||||
i = 0
|
||||
while i < tn {
|
||||
if tail[i] == '[' {
|
||||
var j = i + 1
|
||||
while j < tn and tail[j] != ']' { j += 1 }
|
||||
let dim = s_trim(str_sub(tail, i + 1, j))
|
||||
vkg_d_array = true
|
||||
vkg_d_count = vkg_d_count * vkg_const_int(dim)
|
||||
i = j + 1
|
||||
continue
|
||||
}
|
||||
if tail[i] == ':' {
|
||||
var j = i + 1
|
||||
while j < tn and tail[j] >= '0' and tail[j] <= '9' { j += 1 }
|
||||
vkg_d_bits = Text.to_int(str_sub(tail, i + 1, j))
|
||||
i = j
|
||||
continue
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
# ---- the type table ---------------------------------------------------------------
|
||||
const VK_T_OPAQUE: int = 0 # size unknown: only ever behind a pointer
|
||||
const VK_T_SCALAR: int = 1 # a C scalar, size in vkg_ty_size
|
||||
const VK_T_PTR: int = 2 # a pointer: typedef'd pointers, function pointers, ObjC ids
|
||||
const VK_T_HANDLE: int = 3 # a dispatchable handle (a pointer)
|
||||
const VK_T_NDHANDLE: int = 4 # a non-dispatchable handle (uint64_t)
|
||||
const VK_T_ENUM: int = 5 # 4 bytes
|
||||
const VK_T_STRUCT: int = 6
|
||||
const VK_T_UNION: int = 7
|
||||
const VK_T_ALIAS: int = 8 # another type's name, in vkg_ty_alias
|
||||
|
||||
var vkg_types: VkMap = null
|
||||
var vkg_ty_name: []pointer = null
|
||||
var vkg_ty_kind: []int = null
|
||||
var vkg_ty_size: []int = null # -1 not laid out yet, -2 being laid out
|
||||
var vkg_ty_align: []int = null
|
||||
var vkg_ty_alias: []pointer = null
|
||||
var vkg_ty_moff: []int = null # a struct's members: first index into vkg_m_*
|
||||
var vkg_ty_mcnt: []int = null
|
||||
var vkg_ty_float: []bool = null # a float scalar (bound as float bits)
|
||||
|
||||
var vkg_m_base: []pointer = null
|
||||
var vkg_m_star: []int = null
|
||||
var vkg_m_name: []pointer = null
|
||||
var vkg_m_count: []int = null
|
||||
var vkg_m_array: []bool = null
|
||||
var vkg_m_bits: []int = null
|
||||
var vkg_m_off: []int = null
|
||||
|
||||
function vkg_ty_add(name: pointer, kind: int, size: int) -> int {
|
||||
let i = len(vkg_ty_name)
|
||||
push(vkg_ty_name, name); push(vkg_ty_kind, kind); push(vkg_ty_size, size); push(vkg_ty_align, size)
|
||||
push(vkg_ty_alias, null); push(vkg_ty_moff, 0); push(vkg_ty_mcnt, 0); push(vkg_ty_float, false)
|
||||
vkm_set(vkg_types, name, i + 1)
|
||||
return i
|
||||
}
|
||||
function vkg_ty_find(name: pointer) -> int {
|
||||
if name == null { return -1 }
|
||||
return vkm_get_or(vkg_types, name, 0) - 1
|
||||
}
|
||||
# follow aliases to the type that has a layout
|
||||
function vkg_ty_real(i0: int) -> int {
|
||||
var i = i0
|
||||
var guard = 0
|
||||
while i >= 0 and vkg_ty_kind[i] == VK_T_ALIAS and guard < 16 { i = vkg_ty_find(vkg_ty_alias[i]); guard += 1 }
|
||||
return i
|
||||
}
|
||||
|
||||
function vkg_scalars() -> void {
|
||||
vkg_ty_add("void", VK_T_OPAQUE, 0)
|
||||
let one = "char int8_t uint8_t"
|
||||
let two = "int16_t uint16_t"
|
||||
let four = "int int32_t uint32_t DWORD"
|
||||
let eight = "int64_t uint64_t size_t double"
|
||||
for k in 0 .. 4 {
|
||||
var set = one; var sz = 1
|
||||
if k == 1 { set = two; sz = 2 }
|
||||
if k == 2 { set = four; sz = 4 }
|
||||
if k == 3 { set = eight; sz = 8 }
|
||||
let parts = Text.split(set, " ")
|
||||
for p in 0 .. len(parts) { vkg_ty_add(parts[p], VK_T_SCALAR, sz) }
|
||||
}
|
||||
let f = vkg_ty_add("float", VK_T_SCALAR, 4)
|
||||
vkg_ty_float[f] = true
|
||||
let ptrs = Text.split("HINSTANCE HWND HMONITOR HANDLE LPCWSTR", " ")
|
||||
for p in 0 .. len(ptrs) { vkg_ty_add(ptrs[p], VK_T_PTR, 8) }
|
||||
}
|
||||
|
||||
# the end of the element that opened at `at` in the <types> block: the last
|
||||
# </type> before the next <type opens (struct bodies hold inner <type> elements)
|
||||
function vkg_elem_end(x: pointer, at: int, stop: int) -> int {
|
||||
var next = s_index(x, "<type ", at + 6)
|
||||
if next < 0 or next > stop { next = stop }
|
||||
var last = -1
|
||||
var p = s_index(x, "</type>", at)
|
||||
while p >= 0 and p < next { last = p; p = s_index(x, "</type>", p + 7) }
|
||||
return last
|
||||
}
|
||||
|
||||
function vkg_parse_types(x: pointer) -> void {
|
||||
let start = s_index(x, "<types", 0)
|
||||
let stop = s_index(x, "</types>", start)
|
||||
var at = s_index(x, "<type ", start)
|
||||
while at >= 0 and at < stop {
|
||||
let te = s_index(x, ">", at)
|
||||
let tag = str_sub(x, at, te + 1)
|
||||
let selfclose = x[te - 1] == '/'
|
||||
var body: pointer = ""
|
||||
var next_at = te + 1
|
||||
if not selfclose {
|
||||
let e = vkg_elem_end(x, at, stop)
|
||||
if e > te { body = str_sub(x, te + 1, e); next_at = e + 7 }
|
||||
}
|
||||
if vkg_api_ok(tag) { vkg_type_one(tag, body) }
|
||||
at = s_index(x, "<type ", next_at)
|
||||
}
|
||||
}
|
||||
|
||||
function vkg_type_one(tag: pointer, body: pointer) -> void {
|
||||
let cat = vkg_attr(tag, "category")
|
||||
var name = vkg_attr(tag, "name")
|
||||
if name == null { name = vkg_inner(body, "name") }
|
||||
if name == null { return }
|
||||
let alias = vkg_attr(tag, "alias")
|
||||
if alias != null {
|
||||
if vkg_ty_find(name) < 0 { let i = vkg_ty_add(name, VK_T_ALIAS, -1); vkg_ty_alias[i] = alias }
|
||||
return
|
||||
}
|
||||
if vkg_ty_find(name) >= 0 { return }
|
||||
if cat == null {
|
||||
# requires="..." platform names this generator does not already know
|
||||
vkg_ty_add(name, VK_T_OPAQUE, 0)
|
||||
return
|
||||
}
|
||||
if cat == "struct" or cat == "union" {
|
||||
var kind = VK_T_STRUCT
|
||||
if cat == "union" { kind = VK_T_UNION }
|
||||
let i = vkg_ty_add(name, kind, -1)
|
||||
vkg_ty_moff[i] = len(vkg_m_name)
|
||||
var cnt = 0
|
||||
var m = s_index(body, "<member", 0)
|
||||
while m >= 0 {
|
||||
let me = s_index(body, ">", m)
|
||||
let mtag = str_sub(body, m, me + 1)
|
||||
let close = s_index(body, "</member>", me)
|
||||
if close < 0 { break }
|
||||
if vkg_api_ok(mtag) {
|
||||
vkg_decl(str_sub(body, me + 1, close))
|
||||
push(vkg_m_base, vkg_d_base); push(vkg_m_star, vkg_d_star); push(vkg_m_name, vkg_d_name)
|
||||
push(vkg_m_count, vkg_d_count); push(vkg_m_array, vkg_d_array); push(vkg_m_bits, vkg_d_bits)
|
||||
push(vkg_m_off, -1)
|
||||
cnt += 1
|
||||
}
|
||||
m = s_index(body, "<member", close)
|
||||
}
|
||||
vkg_ty_mcnt[i] = cnt
|
||||
return
|
||||
}
|
||||
if cat == "handle" {
|
||||
if s_contains(body, "NON_DISPATCHABLE") { vkg_ty_add(name, VK_T_NDHANDLE, 8) } else { vkg_ty_add(name, VK_T_HANDLE, 8) }
|
||||
return
|
||||
}
|
||||
if cat == "enum" { vkg_ty_add(name, VK_T_ENUM, 4); return }
|
||||
if cat == "bitmask" {
|
||||
if s_contains(body, "VkFlags64") { vkg_ty_add(name, VK_T_SCALAR, 8) } else { vkg_ty_add(name, VK_T_SCALAR, 4) }
|
||||
return
|
||||
}
|
||||
if cat == "funcpointer" { vkg_ty_add(name, VK_T_PTR, 8); return }
|
||||
if cat == "basetype" {
|
||||
let flat = vkg_strip(body)
|
||||
if s_contains(flat, "*") or s_contains(body, "__OBJC__") { vkg_ty_add(name, VK_T_PTR, 8); return }
|
||||
let base = vkg_inner(body, "type")
|
||||
if base != null and s_contains(flat, "typedef") {
|
||||
let i = vkg_ty_add(name, VK_T_ALIAS, -1)
|
||||
vkg_ty_alias[i] = base
|
||||
return
|
||||
}
|
||||
vkg_ty_add(name, VK_T_OPAQUE, 0)
|
||||
return
|
||||
}
|
||||
# include / define: nothing to bind
|
||||
}
|
||||
|
||||
# ---- layout -------------------------------------------------------------------------
|
||||
# C rules: each member at the next multiple of its alignment, the struct padded to its
|
||||
# largest; consecutive bitfields share a unit of their declared type while they fit.
|
||||
function vkg_layout(i0: int) -> bool {
|
||||
let i = vkg_ty_real(i0)
|
||||
if i < 0 { return false }
|
||||
let k = vkg_ty_kind[i]
|
||||
if k != VK_T_STRUCT and k != VK_T_UNION { return vkg_ty_size[i] > 0 }
|
||||
if vkg_ty_size[i] > 0 { return true }
|
||||
if vkg_ty_size[i] == -2 or vkg_ty_size[i] == -3 { return false }
|
||||
vkg_ty_size[i] = -2
|
||||
var off = 0
|
||||
var maxal = 1
|
||||
var unit_at = -1; var unit_sz = 0; var unit_used = 0
|
||||
let m0 = vkg_ty_moff[i]
|
||||
for m in m0 .. m0 + vkg_ty_mcnt[i] {
|
||||
var sz = 8; var al = 8
|
||||
if vkg_m_star[m] == 0 {
|
||||
let t = vkg_ty_real(vkg_ty_find(vkg_m_base[m]))
|
||||
if t < 0 or not vkg_layout(t) { vkg_ty_size[i] = -3; return false }
|
||||
sz = vkg_ty_size[t]; al = vkg_ty_align[t]
|
||||
}
|
||||
if al > maxal { maxal = al }
|
||||
if vkg_m_bits[m] > 0 {
|
||||
if unit_at >= 0 and unit_sz == sz and unit_used + vkg_m_bits[m] <= sz * 8 {
|
||||
vkg_m_off[m] = unit_at
|
||||
unit_used += vkg_m_bits[m]
|
||||
continue
|
||||
}
|
||||
off = (off + al - 1) / al * al
|
||||
unit_at = off; unit_sz = sz; unit_used = vkg_m_bits[m]
|
||||
vkg_m_off[m] = off
|
||||
off += sz
|
||||
continue
|
||||
}
|
||||
unit_at = -1
|
||||
let total = sz * vkg_m_count[m]
|
||||
if k == VK_T_UNION {
|
||||
vkg_m_off[m] = 0
|
||||
if total > off { off = total }
|
||||
} else {
|
||||
off = (off + al - 1) / al * al
|
||||
vkg_m_off[m] = off
|
||||
off += total
|
||||
}
|
||||
}
|
||||
vkg_ty_size[i] = (off + maxal - 1) / maxal * maxal
|
||||
vkg_ty_align[i] = maxal
|
||||
return true
|
||||
}
|
||||
|
||||
# ---- constants and enums -------------------------------------------------------------
|
||||
var vkg_enums: VkMap = null # name -> index + 1 into vkg_e_*
|
||||
var vkg_e_name: []pointer = null
|
||||
var vkg_e_ty: []pointer = null # "int" | "long" | "string"
|
||||
var vkg_e_val: []pointer = null # the literal as emitted
|
||||
var vkg_wide: VkMap = null # enum type names with bitwidth="64"
|
||||
|
||||
function vkg_enum_add(name: pointer, ty: pointer, val: pointer) -> void {
|
||||
if name == null or val == null { return }
|
||||
if vkm_get_or(vkg_enums, name, 0) > 0 { return }
|
||||
push(vkg_e_name, name); push(vkg_e_ty, ty); push(vkg_e_val, val)
|
||||
vkm_set(vkg_enums, name, len(vkg_e_name))
|
||||
}
|
||||
|
||||
function vkg_const_int(dim: pointer) -> int {
|
||||
if slen(dim) > 0 and dim[0] >= '0' and dim[0] <= '9' { return Text.to_int(dim) }
|
||||
let i = vkm_get_or(vkg_enums, dim, 0)
|
||||
if i == 0 { err(`vkgen: unknown array size {dim}\n`); return 1 }
|
||||
return Text.to_int(vkg_e_val[i - 1])
|
||||
}
|
||||
|
||||
# 2^b as decimal text (b < 64), for 64-bit flag bits
|
||||
function vkg_pow2(b: int) -> pointer {
|
||||
if b < 31 { return string(1 << b) }
|
||||
# decimal doubling on a digit string, least significant digit first
|
||||
let d = words(24)
|
||||
for z in 0 .. 24 { d[z] = 0 }
|
||||
d[0] = 1
|
||||
for s in 0 .. b {
|
||||
var carry = 0
|
||||
for z in 0 .. 24 { let v = d[z] * 2 + carry; d[z] = v % 10; carry = v / 10 }
|
||||
}
|
||||
var top = 23
|
||||
while top > 0 and d[top] == 0 { top -= 1 }
|
||||
let sb = sb_new()
|
||||
var z = top
|
||||
while z >= 0 { sb_putc(sb, '0' + d[z]); z -= 1 }
|
||||
return sb_str(sb)
|
||||
}
|
||||
|
||||
# one <enum .../> of a block or a require: its value as literal text and type
|
||||
var vkg_v_ty: pointer = null
|
||||
function vkg_enum_value(tag: pointer, wide: bool, extnum: int) -> pointer {
|
||||
vkg_v_ty = "int"
|
||||
if wide { vkg_v_ty = "long" }
|
||||
let alias = vkg_attr(tag, "alias")
|
||||
if alias != null {
|
||||
let i = vkm_get_or(vkg_enums, alias, 0)
|
||||
if i == 0 { return null }
|
||||
vkg_v_ty = vkg_e_ty[i - 1]
|
||||
return vkg_e_val[i - 1]
|
||||
}
|
||||
let bp = vkg_attr(tag, "bitpos")
|
||||
if bp != null {
|
||||
let b = Text.to_int(bp)
|
||||
if b == 31 and not wide { return "-2147483648" }
|
||||
if b >= 31 { vkg_v_ty = "long" }
|
||||
return vkg_pow2(b)
|
||||
}
|
||||
let off = vkg_attr(tag, "offset")
|
||||
if off != null {
|
||||
var en = extnum
|
||||
let ex = vkg_attr(tag, "extnumber")
|
||||
if ex != null { en = Text.to_int(ex) }
|
||||
let v = 1000000000 + (en - 1) * 1000 + Text.to_int(off)
|
||||
if vkg_eq(vkg_attr(tag, "dir"), "-") { return string(0 - v) }
|
||||
return string(v)
|
||||
}
|
||||
let raw = vkg_attr(tag, "value")
|
||||
if raw == null { return null }
|
||||
if s_starts(raw, """) { vkg_v_ty = "string"; return `"{vkg_unquote(raw)}"` }
|
||||
if raw == "(~0U)" { vkg_v_ty = "int"; return "-1" }
|
||||
if raw == "(~1U)" { vkg_v_ty = "int"; return "-2" }
|
||||
if raw == "(~2U)" { vkg_v_ty = "int"; return "-3" }
|
||||
if raw == "(~0ULL)" { vkg_v_ty = "long"; return "-1" }
|
||||
if raw == "1000.0F" { vkg_v_ty = "int"; return "1148846080" } # float bits of 1000.0
|
||||
# a float constant other than the one above has no integer meaning to bind
|
||||
if s_contains(raw, ".") { return null }
|
||||
var t = raw
|
||||
if s_starts(t, "0x") or s_starts(t, "-") or (slen(t) > 0 and t[0] >= '0' and t[0] <= '9') {
|
||||
while slen(t) > 0 and (t[slen(t) - 1] == 'U' or t[slen(t) - 1] == 'L') { t = str_sub(t, 0, slen(t) - 1) }
|
||||
return t
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
# every <enums> block: API Constants and each enum type's own values
|
||||
function vkg_parse_enum_blocks(x: pointer) -> void {
|
||||
var at = s_index(x, "<enums ", 0)
|
||||
while at >= 0 {
|
||||
let te = s_index(x, ">", at)
|
||||
let tag = str_sub(x, at, te + 1)
|
||||
let close = s_index(x, "</enums>", te)
|
||||
if close < 0 { break }
|
||||
let ename = vkg_attr(tag, "name")
|
||||
let wide = vkg_eq(vkg_attr(tag, "bitwidth"), "64")
|
||||
if wide and ename != null { vkm_set(vkg_wide, ename, 1) }
|
||||
var e = s_index(x, "<enum ", te)
|
||||
while e >= 0 and e < close {
|
||||
let ee = s_index(x, ">", e)
|
||||
let etag = str_sub(x, e, ee + 1)
|
||||
if vkg_api_ok(etag) {
|
||||
let v = vkg_enum_value(etag, wide, 0)
|
||||
vkg_enum_add(vkg_attr(etag, "name"), vkg_v_ty, v)
|
||||
}
|
||||
e = s_index(x, "<enum ", ee)
|
||||
}
|
||||
at = s_index(x, "<enums ", close)
|
||||
}
|
||||
}
|
||||
|
||||
# ---- what is bound ------------------------------------------------------------------
|
||||
var vkg_want_cmd: VkMap = null
|
||||
var vkg_cmd_order: []pointer = null
|
||||
var vkg_want_type: VkMap = null
|
||||
var vkg_type_order: []pointer = null
|
||||
|
||||
function vkg_take_require(x: pointer, from: int, to: int, extnum: int) -> void {
|
||||
var r = s_index(x, "<require", from)
|
||||
while r >= 0 and r < to {
|
||||
let re = s_index(x, ">", r)
|
||||
let rtag = str_sub(x, r, re + 1)
|
||||
var rclose = s_index(x, "</require>", re)
|
||||
if x[re - 1] == '/' { rclose = re }
|
||||
if rclose < 0 or rclose > to { rclose = to }
|
||||
if vkg_api_ok(rtag) {
|
||||
var p = s_index(x, "<", re + 1)
|
||||
while p >= 0 and p < rclose {
|
||||
let pe = s_index(x, ">", p)
|
||||
let ptag = str_sub(x, p, pe + 1)
|
||||
let nm = vkg_attr(ptag, "name")
|
||||
if vkg_api_ok(ptag) and nm != null {
|
||||
if s_starts(ptag, "<command ") and vkm_get_or(vkg_want_cmd, nm, 0) == 0 { vkm_set(vkg_want_cmd, nm, 1); push(vkg_cmd_order, nm) }
|
||||
if s_starts(ptag, "<type ") and vkm_get_or(vkg_want_type, nm, 0) == 0 { vkm_set(vkg_want_type, nm, 1); push(vkg_type_order, nm) }
|
||||
if s_starts(ptag, "<enum ") {
|
||||
let ext = vkg_attr(ptag, "extends")
|
||||
var wide = false
|
||||
if ext != null { wide = vkm_get_or(vkg_wide, ext, 0) == 1 }
|
||||
let v = vkg_enum_value(ptag, wide, extnum)
|
||||
vkg_enum_add(nm, vkg_v_ty, v)
|
||||
}
|
||||
}
|
||||
p = s_index(x, "<", pe + 1)
|
||||
}
|
||||
}
|
||||
r = s_index(x, "<require", rclose)
|
||||
}
|
||||
}
|
||||
|
||||
function vkg_parse_selection(x: pointer) -> void {
|
||||
var at = s_index(x, "<feature ", 0)
|
||||
while at >= 0 {
|
||||
let te = s_index(x, ">", at)
|
||||
let tag = str_sub(x, at, te + 1)
|
||||
let close = s_index(x, "</feature>", te)
|
||||
if close < 0 { break }
|
||||
if vkg_api_ok(tag) { vkg_take_require(x, te, close, 0) }
|
||||
at = s_index(x, "<feature ", close)
|
||||
}
|
||||
let wanted = Text.split(VKG_EXTENSIONS, " ")
|
||||
at = s_index(x, "<extension ", 0)
|
||||
while at >= 0 {
|
||||
let te = s_index(x, ">", at)
|
||||
let tag = str_sub(x, at, te + 1)
|
||||
var close = s_index(x, "</extension>", te)
|
||||
if x[te - 1] == '/' { close = te }
|
||||
if close < 0 { break }
|
||||
let nm = vkg_attr(tag, "name")
|
||||
var take = false
|
||||
for w in 0 .. len(wanted) { if vkg_eq(wanted[w], nm) { take = true } }
|
||||
if take and vkg_in_csv(vkg_attr(tag, "supported"), "vulkan") {
|
||||
vkg_take_require(x, te, close, Text.to_int(vkg_attr(tag, "number")))
|
||||
}
|
||||
at = s_index(x, "<extension ", close)
|
||||
}
|
||||
}
|
||||
|
||||
# ---- commands -----------------------------------------------------------------------
|
||||
var vkg_cmds: VkMap = null
|
||||
var vkg_c_name: []pointer = null
|
||||
var vkg_c_rbase: []pointer = null
|
||||
var vkg_c_rstar: []int = null
|
||||
var vkg_c_poff: []int = null
|
||||
var vkg_c_pcnt: []int = null
|
||||
var vkg_p_base: []pointer = null
|
||||
var vkg_p_star: []int = null
|
||||
var vkg_p_name: []pointer = null
|
||||
var vkg_p_array: []bool = null
|
||||
var vkg_c_alias: VkMap = null # alias command name -> index + 1 of the command it names
|
||||
var vkg_alias_from: []pointer = null
|
||||
var vkg_alias_to: []pointer = null
|
||||
|
||||
function vkg_parse_commands(x: pointer) -> void {
|
||||
let start = s_index(x, "<commands", 0)
|
||||
let stop = s_index(x, "</commands>", start)
|
||||
var at = s_index(x, "<command", start + 9)
|
||||
while at >= 0 and at < stop {
|
||||
let te = s_index(x, ">", at)
|
||||
let tag = str_sub(x, at, te + 1)
|
||||
if x[te - 1] == '/' {
|
||||
let a = vkg_attr(tag, "alias")
|
||||
if a != null { push(vkg_alias_from, vkg_attr(tag, "name")); push(vkg_alias_to, a) }
|
||||
at = s_index(x, "<command", te)
|
||||
continue
|
||||
}
|
||||
let close = s_index(x, "</command>", te)
|
||||
if close < 0 { break }
|
||||
let body = str_sub(x, te + 1, close)
|
||||
if vkg_api_ok(tag) {
|
||||
let proto = vkg_inner(body, "proto")
|
||||
vkg_decl(proto)
|
||||
let name = vkg_d_name
|
||||
if name != null and vkm_get_or(vkg_cmds, name, 0) == 0 {
|
||||
push(vkg_c_name, name); push(vkg_c_rbase, vkg_d_base); push(vkg_c_rstar, vkg_d_star)
|
||||
push(vkg_c_poff, len(vkg_p_name))
|
||||
var cnt = 0
|
||||
var p = s_index(body, "<param", 0)
|
||||
while p >= 0 {
|
||||
let pe = s_index(body, ">", p)
|
||||
let ptag = str_sub(body, p, pe + 1)
|
||||
let pc = s_index(body, "</param>", pe)
|
||||
if pc < 0 { break }
|
||||
# <implicitexternsyncparams> holds <param> prose, with no <type>: not a parameter
|
||||
let pbody = str_sub(body, pe + 1, pc)
|
||||
if vkg_api_ok(ptag) and s_contains(pbody, "<type>") {
|
||||
vkg_decl(pbody)
|
||||
push(vkg_p_base, vkg_d_base); push(vkg_p_star, vkg_d_star); push(vkg_p_name, vkg_d_name); push(vkg_p_array, vkg_d_array)
|
||||
cnt += 1
|
||||
}
|
||||
p = s_index(body, "<param", pc)
|
||||
}
|
||||
push(vkg_c_pcnt, cnt)
|
||||
vkm_set(vkg_cmds, name, len(vkg_c_name))
|
||||
}
|
||||
}
|
||||
at = s_index(x, "<command", close)
|
||||
}
|
||||
for k in 0 .. len(vkg_alias_from) {
|
||||
let t = vkm_get_or(vkg_cmds, vkg_alias_to[k], 0)
|
||||
if t > 0 { vkm_set(vkg_c_alias, vkg_alias_from[k], t) }
|
||||
}
|
||||
}
|
||||
|
||||
# the LLVM type of a C parameter or return; null when this generator cannot bind it
|
||||
function vkg_ir(base: pointer, star: int, array: bool) -> pointer {
|
||||
if star > 0 or array { return "ptr" }
|
||||
if base == null { return null }
|
||||
if base == "void" { return "void" }
|
||||
let t = vkg_ty_real(vkg_ty_find(base))
|
||||
if t < 0 { return null }
|
||||
let k = vkg_ty_kind[t]
|
||||
if k == VK_T_HANDLE or k == VK_T_PTR { return "ptr" }
|
||||
if k == VK_T_NDHANDLE { return "i64" }
|
||||
if k == VK_T_ENUM { return "i32" }
|
||||
if k == VK_T_SCALAR {
|
||||
let sz = vkg_ty_size[t]
|
||||
if sz == 1 { return "i8" }
|
||||
if sz == 2 { return "i16" }
|
||||
if sz == 4 { if vkg_ty_float[t] { return "float" }; return "i32" }
|
||||
if sz == 8 { if vkg_ty_name[t] == "double" { return null }; return "i64" }
|
||||
}
|
||||
return null
|
||||
}
|
||||
# what carries it across the Ludic boundary
|
||||
function vkg_abi(ir: pointer) -> pointer {
|
||||
if ir == "ptr" or ir == "i64" or ir == "void" { return ir }
|
||||
return "i32"
|
||||
}
|
||||
function vkg_ludic(ir: pointer) -> pointer {
|
||||
if ir == "ptr" { return "pointer" }
|
||||
if ir == "i64" { return "long" }
|
||||
if ir == "void" { return "void" }
|
||||
return "int"
|
||||
}
|
||||
|
||||
# CamelCase -> snake_case, with digits counted as the end of a word ("Win32Surface")
|
||||
function vkg_snake(n: pointer) -> pointer {
|
||||
let b = sb_new()
|
||||
let m = slen(n)
|
||||
for i in 0 .. m {
|
||||
let c = n[i]
|
||||
if glg_upper(c) and i > 0 {
|
||||
let prev = n[i - 1]
|
||||
var cut = glg_lower(prev) or glg_digit(prev)
|
||||
if glg_upper(prev) and i + 1 < m and glg_lower(n[i + 1]) { cut = true }
|
||||
if cut { sb_putc(b, '_') }
|
||||
}
|
||||
if glg_upper(c) { sb_putc(b, c + 32) } else { sb_putc(b, c) }
|
||||
}
|
||||
return sb_str(b)
|
||||
}
|
||||
function vkg_pname(raw: pointer, i: int) -> pointer {
|
||||
if raw == null { return `a{string(i)}` }
|
||||
let n = vkg_snake(raw)
|
||||
if glg_reserved(n) { return n + "_" }
|
||||
return n
|
||||
}
|
||||
|
||||
# the command a wanted name resolves to (itself, or the core command it aliases)
|
||||
function vkg_cmd_of(name: pointer) -> int {
|
||||
let i = vkm_get_or(vkg_cmds, name, 0)
|
||||
if i > 0 { return i - 1 }
|
||||
return vkm_get_or(vkg_c_alias, name, 0) - 1
|
||||
}
|
||||
function vkg_cmd_ok(c: int) -> bool {
|
||||
if vkg_ir(vkg_c_rbase[c], vkg_c_rstar[c], false) == null { return false }
|
||||
for p in vkg_c_poff[c] .. vkg_c_poff[c] + vkg_c_pcnt[c] {
|
||||
let ir = vkg_ir(vkg_p_base[p], vkg_p_star[p], vkg_p_array[p])
|
||||
if ir == null or ir == "void" { return false }
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
# ---- emit vk_api.ludic ---------------------------------------------------------------
|
||||
var vkg_n_layouts: int = 0
|
||||
var vkg_n_cmds: int = 0
|
||||
|
||||
function vkg_emit_ludic(path: pointer) -> bool {
|
||||
let b = sb_new()
|
||||
sb_puts(b, "# ============================================================================\n")
|
||||
sb_puts(b, "# vk_api.ludic — Vulkan 1.0-1.4 and the renderer's extensions, bound for Ludic.\n")
|
||||
sb_puts(b, "# GENERATED by `ludic-dev vkgen` from the Vulkan registry (vk.xml). Do not edit.\n")
|
||||
sb_puts(b, "# Constants; every struct's size (<Struct>_sizeof) and field offsets (<Struct>_<field>,\n")
|
||||
sb_puts(b, "# a bitfield at the offset of its storage unit); one extern per command. A C float\n")
|
||||
sb_puts(b, "# is float bits in an int; uint64_t, VkDeviceSize and non-dispatchable handles are long.\n")
|
||||
sb_puts(b, "# ============================================================================\n\n")
|
||||
for i in 0 .. len(vkg_e_name) {
|
||||
sb_puts(b, `const {vkg_e_name[i]}: {vkg_e_ty[i]} = {vkg_e_val[i]}\n`)
|
||||
}
|
||||
sb_puts(b, "\n")
|
||||
vkg_n_layouts = 0
|
||||
for k in 0 .. len(vkg_type_order) {
|
||||
let name = vkg_type_order[k]
|
||||
let t = vkg_ty_real(vkg_ty_find(name))
|
||||
if t < 0 { continue }
|
||||
if vkg_ty_kind[t] != VK_T_STRUCT and vkg_ty_kind[t] != VK_T_UNION { continue }
|
||||
if not vkg_layout(t) { continue }
|
||||
vkg_n_layouts += 1
|
||||
sb_puts(b, `const {name}_sizeof: int = {string(vkg_ty_size[t])}\n`)
|
||||
let m0 = vkg_ty_moff[t]
|
||||
for m in m0 .. m0 + vkg_ty_mcnt[t] {
|
||||
if vkg_m_name[m] != null { sb_puts(b, `const {name}_{vkg_m_name[m]}: int = {string(vkg_m_off[m])}\n`) }
|
||||
}
|
||||
}
|
||||
sb_puts(b, "\n# the loader (vk_win.ll / vk_mac.ll) and memory access for filling structs (vk_thunks.ll)\n")
|
||||
sb_puts(b, "extern function vk_open() -> int = \"lvk_open\"\n")
|
||||
sb_puts(b, "extern function vk_has(name: pointer) -> int = \"lvk_has\"\n")
|
||||
sb_puts(b, "extern function vk_zero(p: pointer, n: int) = \"lvk_zero\"\n")
|
||||
sb_puts(b, "extern function vk_at(p: pointer, off: int) -> pointer = \"lvk_at\"\n")
|
||||
sb_puts(b, "extern function vk_put_i32(p: pointer, off: int, v: int) = \"lvk_put_i32\"\n")
|
||||
sb_puts(b, "extern function vk_put_i64(p: pointer, off: int, v: long) = \"lvk_put_i64\"\n")
|
||||
sb_puts(b, "extern function vk_put_ptr(p: pointer, off: int, v: pointer) = \"lvk_put_ptr\"\n")
|
||||
sb_puts(b, "extern function vk_get_i32(p: pointer, off: int) -> int = \"lvk_get_i32\"\n")
|
||||
sb_puts(b, "extern function vk_get_i64(p: pointer, off: int) -> long = \"lvk_get_i64\"\n")
|
||||
sb_puts(b, "extern function vk_get_ptr(p: pointer, off: int) -> pointer = \"lvk_get_ptr\"\n\n")
|
||||
vkg_n_cmds = 0
|
||||
for k in 0 .. len(vkg_cmd_order) {
|
||||
let want = vkg_cmd_order[k]
|
||||
let c = vkg_cmd_of(want)
|
||||
if c < 0 or not vkg_cmd_ok(c) { continue }
|
||||
vkg_n_cmds += 1
|
||||
let bare = str_sub(want, 2, slen(want))
|
||||
let ps = sb_new()
|
||||
var n = 0
|
||||
for p in vkg_c_poff[c] .. vkg_c_poff[c] + vkg_c_pcnt[c] {
|
||||
if n > 0 { sb_puts(ps, ", ") }
|
||||
sb_puts(ps, `{vkg_pname(vkg_p_name[p], n)}: {vkg_ludic(vkg_ir(vkg_p_base[p], vkg_p_star[p], vkg_p_array[p]))}`)
|
||||
n += 1
|
||||
}
|
||||
let rt = vkg_ludic(vkg_ir(vkg_c_rbase[c], vkg_c_rstar[c], false))
|
||||
var rets = ""
|
||||
if rt != "void" { rets = " -> " + rt }
|
||||
sb_puts(b, `extern function vk_{vkg_snake(bare)}({sb_str(ps)}){rets} = "lvk_{bare}"\n`)
|
||||
}
|
||||
return write_file(path, sb_str(b))
|
||||
}
|
||||
|
||||
# ---- emit vk_thunks.ll ---------------------------------------------------------------
|
||||
function vkg_emit_thunks(path: pointer) -> bool {
|
||||
let b = sb_new()
|
||||
let bind = sb_new()
|
||||
sb_puts(b, "; ============================================================================\n")
|
||||
sb_puts(b, "; vk_thunks.ll — one thunk per bound Vulkan command. GENERATED by `ludic-dev vkgen`.\n")
|
||||
sb_puts(b, "; Each @lvk_<Name> takes the Ludic ABI (i32 / i64 / ptr; a float as its bits) and\n")
|
||||
sb_puts(b, "; calls through @lvk_p_<Name>, which @lvk_bind fills from the platform loader's\n")
|
||||
sb_puts(b, "; @lvk_sym (vk_win.ll: GetProcAddress, vk_mac.ll: dlsym). A command the loader\n")
|
||||
sb_puts(b, "; lacks stays null; ask vk_has(name) before calling an extension's command.\n")
|
||||
sb_puts(b, "; ============================================================================\n\n")
|
||||
sb_puts(b, "declare ptr @lvk_sym(ptr)\n")
|
||||
sb_puts(b, "declare void @llvm.memset.p0.i64(ptr, i8, i64, i1)\n\n")
|
||||
sb_puts(bind, "define i32 @lvk_bind() {\nentry:\n")
|
||||
var found = 0
|
||||
for k in 0 .. len(vkg_cmd_order) {
|
||||
let want = vkg_cmd_order[k]
|
||||
let c = vkg_cmd_of(want)
|
||||
if c < 0 or not vkg_cmd_ok(c) { continue }
|
||||
let bare = str_sub(want, 2, slen(want))
|
||||
sb_puts(b, `@.lvkn_{bare} = private unnamed_addr constant [{string(slen(want) + 1)} x i8] c"{want}\\00"\n`)
|
||||
sb_puts(b, `@lvk_p_{bare} = internal global ptr null\n`)
|
||||
let cret = vkg_ir(vkg_c_rbase[c], vkg_c_rstar[c], false)
|
||||
let lret = vkg_abi(cret)
|
||||
let sig = sb_new(); let body = sb_new(); let call = sb_new()
|
||||
var n = 0
|
||||
for p in vkg_c_poff[c] .. vkg_c_poff[c] + vkg_c_pcnt[c] {
|
||||
let ct = vkg_ir(vkg_p_base[p], vkg_p_star[p], vkg_p_array[p])
|
||||
let lt = vkg_abi(ct)
|
||||
let a = `%a{string(n)}`
|
||||
if n > 0 { sb_puts(sig, ", "); sb_puts(call, ", ") }
|
||||
sb_puts(sig, `{lt} {a}`)
|
||||
if ct == lt { sb_puts(call, `{ct} {a}`) }
|
||||
else if ct == "float" {
|
||||
sb_puts(body, ` %c{string(n)} = bitcast i32 {a} to float\n`)
|
||||
sb_puts(call, `float %c{string(n)}`)
|
||||
} else {
|
||||
sb_puts(body, ` %c{string(n)} = trunc i32 {a} to {ct}\n`)
|
||||
sb_puts(call, `{ct} %c{string(n)}`)
|
||||
}
|
||||
n += 1
|
||||
}
|
||||
sb_puts(b, `define {lret} @lvk_{bare}({sb_str(sig)}) `)
|
||||
sb_puts(b, "{\nentry:\n")
|
||||
sb_puts(b, ` %fp = load ptr, ptr @lvk_p_{bare}\n`)
|
||||
sb_puts(b, sb_str(body))
|
||||
let invoke = `call {cret} %fp({sb_str(call)})`
|
||||
if cret == "void" { sb_puts(b, ` {invoke}\n ret void\n`) }
|
||||
else if cret == lret { sb_puts(b, ` %r = {invoke}\n ret {lret} %r\n`) }
|
||||
else if cret == "float" { sb_puts(b, ` %r = {invoke}\n %z = bitcast float %r to i32\n ret i32 %z\n`) }
|
||||
else { sb_puts(b, ` %r = {invoke}\n %z = zext {cret} %r to i32\n ret i32 %z\n`) }
|
||||
sb_puts(b, "}\n")
|
||||
let f = string(found)
|
||||
sb_puts(bind, ` %s{f} = call ptr @lvk_sym(ptr @.lvkn_{bare})\n store ptr %s{f}, ptr @lvk_p_{bare}\n`)
|
||||
found += 1
|
||||
}
|
||||
sb_puts(bind, ` ret i32 {string(found)}\n`)
|
||||
sb_puts(bind, "}\n")
|
||||
sb_puts(b, "\n")
|
||||
sb_puts(b, sb_str(bind))
|
||||
sb_puts(b, "\ndefine void @lvk_zero(ptr %p, i32 %n) {\nentry:\n %w = sext i32 %n to i64\n call void @llvm.memset.p0.i64(ptr %p, i8 0, i64 %w, i1 false)\n ret void\n}\n")
|
||||
sb_puts(b, "define ptr @lvk_at(ptr %p, i32 %o) {\nentry:\n %q = getelementptr i8, ptr %p, i32 %o\n ret ptr %q\n}\n")
|
||||
let kinds = Text.split("i32 i64 ptr", " ")
|
||||
for k in 0 .. 3 {
|
||||
let t = kinds[k]
|
||||
sb_puts(b, `define void @lvk_put_{t}(ptr %p, i32 %o, {t} %v) `)
|
||||
sb_puts(b, "{\nentry:\n")
|
||||
sb_puts(b, ` %q = getelementptr i8, ptr %p, i32 %o\n store {t} %v, ptr %q, align 1\n ret void\n`)
|
||||
sb_puts(b, "}\n")
|
||||
sb_puts(b, `define {t} @lvk_get_{t}(ptr %p, i32 %o) `)
|
||||
sb_puts(b, "{\nentry:\n")
|
||||
sb_puts(b, ` %q = getelementptr i8, ptr %p, i32 %o\n %v = load {t}, ptr %q, align 1\n ret {t} %v\n`)
|
||||
sb_puts(b, "}\n")
|
||||
}
|
||||
return write_file(path, sb_str(b))
|
||||
}
|
||||
|
||||
# ---- the task ------------------------------------------------------------------------
|
||||
function vkg_find_xml() -> pointer {
|
||||
var i = 2
|
||||
while i < arg_count() {
|
||||
if arg(i) == "--xml" and i + 1 < arg_count() { return arg(i + 1) }
|
||||
i += 1
|
||||
}
|
||||
let sdk = getenv_or("VULKAN_SDK", "")
|
||||
if sdk != "" and file_exists(`{sdk}/share/vulkan/registry/vk.xml`) { return `{sdk}/share/vulkan/registry/vk.xml` }
|
||||
let found = Text.trim(capture("ls -d $HOME/VulkanSDK/*/macOS/share/vulkan/registry/vk.xml /c/VulkanSDK/*/share/vulkan/registry/vk.xml 2>/dev/null | tail -1"))
|
||||
if found != "" { return found }
|
||||
return null
|
||||
}
|
||||
|
||||
function cmd_vkgen() -> int {
|
||||
var check = false
|
||||
var i = 2
|
||||
while i < arg_count() { if arg(i) == "--check" { check = true }; i += 1 }
|
||||
let xpath = vkg_find_xml()
|
||||
if xpath == null { err("vkgen: no vk.xml (install the Vulkan SDK, set VULKAN_SDK, or pass --xml)\n"); return 1 }
|
||||
let x = read_file(xpath)
|
||||
if x == null { err(`vkgen: cannot read {xpath}\n`); return 1 }
|
||||
vkg_types = vkm_new(); vkg_enums = vkm_new(); vkg_wide = vkm_new()
|
||||
vkg_want_cmd = vkm_new(); vkg_want_type = vkm_new(); vkg_cmds = vkm_new(); vkg_c_alias = vkm_new()
|
||||
vkg_ty_name = new []pointer; vkg_ty_kind = new []int; vkg_ty_size = new []int; vkg_ty_align = new []int
|
||||
vkg_ty_alias = new []pointer; vkg_ty_moff = new []int; vkg_ty_mcnt = new []int; vkg_ty_float = new []bool
|
||||
vkg_m_base = new []pointer; vkg_m_star = new []int; vkg_m_name = new []pointer; vkg_m_count = new []int
|
||||
vkg_m_array = new []bool; vkg_m_bits = new []int; vkg_m_off = new []int
|
||||
vkg_e_name = new []pointer; vkg_e_ty = new []pointer; vkg_e_val = new []pointer
|
||||
vkg_cmd_order = new []pointer; vkg_type_order = new []pointer
|
||||
vkg_c_name = new []pointer; vkg_c_rbase = new []pointer; vkg_c_rstar = new []int; vkg_c_poff = new []int; vkg_c_pcnt = new []int
|
||||
vkg_p_base = new []pointer; vkg_p_star = new []int; vkg_p_name = new []pointer; vkg_p_array = new []bool
|
||||
vkg_alias_from = new []pointer; vkg_alias_to = new []pointer
|
||||
|
||||
vkg_scalars()
|
||||
vkg_parse_enum_blocks(x)
|
||||
vkg_parse_types(x)
|
||||
vkg_parse_selection(x)
|
||||
vkg_parse_commands(x)
|
||||
|
||||
var skipped = 0
|
||||
for k in 0 .. len(vkg_cmd_order) {
|
||||
let c = vkg_cmd_of(vkg_cmd_order[k])
|
||||
if c < 0 or not vkg_cmd_ok(c) { skipped += 1; err(`vkgen: not bound: {vkg_cmd_order[k]}\n`) }
|
||||
}
|
||||
var api_out = "runtime/native/vk_api.ludic"
|
||||
var thunk_out = "runtime/native/vk_thunks.ll"
|
||||
if check { api_out = tmp_path("vk_api.ludic"); thunk_out = tmp_path("vk_thunks.ll") }
|
||||
if not vkg_emit_ludic(api_out) { err("vkgen: cannot write vk_api.ludic\n"); return 1 }
|
||||
if not vkg_emit_thunks(thunk_out) { err("vkgen: cannot write vk_thunks.ll\n"); return 1 }
|
||||
if check {
|
||||
if not shq(`cmp -s {api_out} runtime/native/vk_api.ludic`) { err("vk_api.ludic drifted from vk.xml (run: ludic-dev vkgen)\n"); return 1 }
|
||||
if not shq(`cmp -s {thunk_out} runtime/native/vk_thunks.ll`) { err("vk_thunks.ll drifted from vk.xml (run: ludic-dev vkgen)\n"); return 1 }
|
||||
}
|
||||
print(`OK {string(len(vkg_e_name))} constants, {string(vkg_n_layouts)} struct layouts, {string(vkg_n_cmds)} commands ({string(skipped)} not bound) from {xpath}`)
|
||||
return 0
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue