diff fuhtark_test/generate_vulkan_api.sh @ 1500:91c8c3b7cbf0 main

add: futhark tests for generating vulkan api
author sam <sam@basx.dev>
date Wed, 26 Nov 2025 21:36:48 +0700
parents
children f40d9d814c08
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/fuhtark_test/generate_vulkan_api.sh	Wed Nov 26 21:36:48 2025 +0700
@@ -0,0 +1,137 @@
+#!/bin/sh -e
+
+# Variables
+BASE_INCLUDE=$( realpath $( dirname $0 ) )"/include"
+WIN_INCLUDE=$( realpath $( dirname $0 ) )"/include/winapi"
+OUT_BIN=vulkan
+OUTFILE=$OUT_BIN.nim
+OUTFILE_FUTHARK=$OUT_BIN".gen.nim"
+
+# Convert C-headers to Nim using Futhark, if not existing yet
+NIM_GEN_CODE='import futhark, os
+importc:
+  outputPath "'"$OUTFILE_FUTHARK"'"
+  define VK_USE_PLATFORM_WIN32_KHR
+  define VK_USE_PLATFORM_XLIB_KHR
+  sysPath "'"$BASE_INCLUDE"'"
+  sysPath "'"$WIN_INCLUDE"'"
+  path "/usr/include/vulkan"
+  "vulkan.h"'
+
+if [ ! -f $OUTFILE_FUTHARK ] ; then
+        nim c --maxLoopIterationsVM:1000000000 --eval:"$NIM_GEN_CODE"
+fi
+
+cp $OUTFILE_FUTHARK $OUTFILE
+
+# convert futhark's function definition to function pointers to allow loading functions at runtime
+sed -i 's/^  proc \([a-zA-Z]*\*\)/  var \1: proc/' $OUTFILE
+sed -i 's/[, ] importc: "vk[a-zA-Z]*"//' $OUTFILE
+
+# ensure struct-name-definitions are on single line
+# needed for correct parsing and setting of sType struct values in the next step
+sed -i '/struct_Vk.*inheritable,$/{N;s/\n/ /}' $OUTFILE
+sed -i '/struct_Vk.*pure,$/{N;s/\n/ /}' $OUTFILE
+sed -i '/struct_Vk.* {\.$/{N;s/\n/ /}' $OUTFILE
+
+# set default struct values for member "sType"
+# not very clean, as we "abuse" the fact that Nim does not differentiate between camel-case and snake-case for identifiers
+awk -i inplace '{
+        if ( $0 ~ /^  struct_Vk.* = object/ && ! $0 ~ /VkBaseInStructure/ ) {
+                split($0, arr, "_");
+                print $0;
+                getline;
+                if ( $0 ~ / sType\*:/ ) {
+                        split($0, arr2, "##");
+                        print arr2[1] "= VK_STRUCTURE_TYPE_" substr(arr[2], 3) " ##" arr2[2];
+                } else {
+                        print;
+                }
+        } else {
+                print;
+        }
+}' $OUTFILE
+
+# allow to printing Vulkan handles with Nim, as those are using pointer-types
+cat $OUTFILE_FUTHARK | awk '/ = ptr struct_/ {print "proc `$`*(val: " $1 "): string = $(cast[int](val))"}' >> $OUTFILE
+
+# add some helper functions, the vulkan loader and instance creation
+cat <<EOF >> $OUTFILE
+
+import std/dynlib
+import std/strutils
+import std/logging
+
+var vkInstance*: VkInstance = VkInstance(nil)
+var vkPhysicalDevices: seq[VkPhysicalDevice]
+
+template checkVkResult*(call: untyped) =
+  when defined(release):
+    discard call
+  else:
+    # yes, a bit cheap, but this is only for nice debug output
+    var callstr = astToStr(call).replace("\n", "")
+    while callstr.find("  ") >= 0:
+      callstr = callstr.replace("  ", " ")
+    debug "Calling vulkan: ", callstr
+    let value = call
+    if value != VK_SUCCESS:
+      error "Vulkan error: ", astToStr(call), " returned ", \$value
+      raise newException(
+        Exception, "Vulkan error: " & astToStr(call) & " returned " & \$value
+      )
+
+proc loadFunc[T](instance: VkInstance, f: var T, name: string) =
+  f = cast[T](vkGetInstanceProcAddr(instance, name))
+
+proc initVulkan*() =
+  if vkGetInstanceProcAddr != nil:
+    return
+
+  when defined(linux):
+    let vulkanLib = loadLib("libvulkan.so.1")
+  when defined(windows):
+    let vulkanLib = loadLib("vulkan-1.dll")
+  if vulkanLib == nil:
+    raise newException(Exception, "Unable to load vulkan library")
+
+  # load function-pointer resolver function
+  vkGetInstanceProcAddr = cast[typeof(vkGetInstanceProcAddr)](checkedSymAddr(vulkanLib, "vkGetInstanceProcAddr"))
+
+  # need to create an instance before loading other function points
+  loadFunc(vkInstance, vkCreateInstance, "vkCreateInstance")
+  let createInfo = VkInstanceCreateInfo(
+    pNext: nil,
+    flags: 0,
+    pApplicationInfo: nil,
+    enabledLayerCount: 0,
+    ppEnabledLayerNames: nil,
+    enabledExtensionCount: 0,
+    ppEnabledExtensionNames: nil,
+  )
+  checkVkResult vkCreateInstance(addr createInfo, nil, addr vkInstance)
+
+  # load all functions (some might be null, not checking here)
+EOF
+
+cat vulkan.nim | grep -o '^  var vk[a-zA-Z]*'  | grep -v vkInstance | grep -v vkGetInstanceProcAddr | grep -v vkCreateInstance | awk '{print "  loadFunc(vkInstance, " $2 ", \"" $2 "\")"}' >> $OUTFILE
+
+cat <<EOF >> $OUTFILE
+  
+  # load and print all found devices
+  var nPhysicalDevices: uint32
+  checkVkResult vkEnumeratePhysicalDevices(vkInstance, addr nPhysicalDevices, nil)
+
+  if nPhysicalDevices > 0:
+    vkPhysicalDevices.setLen(nPhysicalDevices)
+    checkVkResult vkEnumeratePhysicalDevices(vkInstance, addr nPhysicalDevices, addr vkPhysicalDevices[0])
+    vkPhysicalDevices.setLen(nPhysicalDevices)
+  echo "physical devices: ", vkPhysicalDevices
+
+proc destroyVulkan*() =
+  vkDestroyInstance(vkInstance, nil)
+
+initVulkan()
+EOF
+
+nim c --run ./$OUTFILE && rm ./$OUT_BIN