diff --git a/.github/workflows/build-full.yml b/.github/workflows/build-full.yml new file mode 100644 index 0000000..3e13d69 --- /dev/null +++ b/.github/workflows/build-full.yml @@ -0,0 +1,70 @@ +on: + push: + branches: + - "master" + pull_request: + branches: + - "*" + +jobs: + tests: + runs-on: ${{ matrix.os }} + strategy: + matrix: + nimversion: + - '2.2.6' + os: + - ubuntu-24.04 + steps: + - uses: actions/checkout@v1 + - name: Setup software OpenGL (Mesa) + Xvfb + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + xvfb \ + xauth \ + mesa-utils \ + libgl1-mesa-dri \ + libglx-mesa0 \ + libegl-mesa0 \ + libgles2 \ + vulkan-tools libvulkan1 libvulkan-dev \ + mesa-vulkan-drivers \ + vulkan-validationlayers \ + + + - uses: iffy/install-nim@v4 + with: + version: ${{ matrix.nimversion }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Cache packages + uses: actions/cache@v3 + with: + path: deps/ + key: ${{ runner.os }}-${{ hashFiles('figdraw.nimble') }} + + - name: Install Atlas + run: | + nimble install 'https://github.com/nim-lang/atlas@#head' + echo "Nim:: " + nim -v + echo "Nimble:: " + atlas -v + + - name: Install Deps + run: | + # sync deps + atlas install --feature:examples + + - name: Build Tests + env: + LIBGL_ALWAYS_SOFTWARE: "1" + MESA_GL_VERSION_OVERRIDE: "3.3" + MESA_GLSL_VERSION_OVERRIDE: "330" + VK_ICD_FILENAMES: "/usr/share/vulkan/icd.d/lvp_icd.x86_64.json" + run: | + xvfb-run -a -s "-screen 0 1280x1024x24" nim test + diff --git a/.gitignore b/.gitignore index 8a70a52..ca20992 100644 --- a/.gitignore +++ b/.gitignore @@ -1,171 +1,20 @@ -### C ### -# Prerequisites -*.d - -# Object files -*.o -*.ko -*.obj -*.elf - -# Linker output -*.ilk -*.map -*.exp - -# Precompiled Headers -*.gch -*.pch - -# Executables -*.exe +* +!*/ +!*.* +nim.cfg +vendor/*/ +nimcache *.out -*.app -*.i*86 -*.x86_64 -*.hex -# Debug files +.tool-versions *.dSYM/ -*.su -*.idb -*.pdb - -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf - -### C++ ### -# Prerequisites - -# Compiled Object files -*.slo - -# Precompiled Headers - -# Compiled Dynamic libraries - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai - -# Executables - -### Linux ### -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - -### macOS ### -*.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### Nim ### -nimcache/ - -### OSX ### - -# Icon must end with two \r - -# Thumbnails - -# Files that might appear in the root of a volume - -# Directories potentially created on remote AFP share - -### VisualStudioCode ### -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -.history - -### Windows ### -# Windows thumbnail cache files -Thumbs.db -ehthumbs.db -ehthumbs_vista.db - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Binaries -[Bb]in/ - -# Windows Installer files -*.cab -*.msi -*.msm -*.msp - -# Windows shortcuts -*.lnk - -# Dynamic Libraries -*.dll -*.so -*.dylib - -### NimGL ### - -# Tests -tests/general -tests/tgeneral -tests/tglfw -tests/tmath -tests/topengl -tests/test -tests/triangle -tools/generator -imgui.ini -docs/ - -# End of https://www.gitignore.io/api/c,osx,nim,c++,linux,macos,windows,visualstudiocode - -vk.xml -vk_layer_settings.txt +/*.png +nimble.develop +nimble.paths +.nimcache +deps/ +/examples/mandelbrot/build/ +.nimcache +nimbledeps +*.qoi +*.xml diff --git a/config.nims b/config.nims new file mode 100644 index 0000000..c946035 --- /dev/null +++ b/config.nims @@ -0,0 +1,19 @@ +import std/[strutils, os] + +task test, "Run tests/test.nim": + exec("nim c -r tests/test.nim") + exec("nim c -r tests/test_wrapper.nim") + exec("nim c examples/run_triangle.nim") + exec("nim c examples/run_mandelbrot.nim") + +task gen, "Generate bindings from source": + exec("nim c -d:ssl -r tools/generator.nim") + +task shaders, "Compile GLSL shaders to SPIR-V format": + let + shaderDir = "examples/mandelbrot/shaders" + outputDir = "examples/mandelbrot/build/shaders" + mkDir(outputDir) + for f in listFiles(shaderDir): + if f.endsWith(".glsl"): + exec "glslc -g -fshader-stage=comp " & f & " -o " & outputDir / splitFile(f).name & ".spv" diff --git a/examples/config.nims b/examples/config.nims new file mode 100644 index 0000000..dc86cf2 --- /dev/null +++ b/examples/config.nims @@ -0,0 +1,4 @@ +--path:"../src" +--define:vulkan +--path:"../deps/mandelbrot/src" +--path:"../deps/glfw" diff --git a/examples/mandelbrot/LICENSE.md b/examples/mandelbrot/LICENSE.md new file mode 100644 index 0000000..3d03943 --- /dev/null +++ b/examples/mandelbrot/LICENSE.md @@ -0,0 +1,4 @@ +Public Domain - Antonis Geralis + +see: https://github.com/planetis-m/mandelbrot/tree/master + diff --git a/examples/mandelbrot/mandelbrot.nim b/examples/mandelbrot/mandelbrot.nim new file mode 100644 index 0000000..cdaff95 --- /dev/null +++ b/examples/mandelbrot/mandelbrot.nim @@ -0,0 +1,436 @@ +# https://youtu.be/1BMGTyIF5dI +import std/[sequtils, math, strutils], chroma +import vulkan +import vulkan/wrapper + +## Ported from https://github.com/planetis-m/mandelbrot/tree/master +## + +type + MandelbrotGenerator* = object + width, height: int32 + workgroupSize: WorkgroupSize + instance: VkInstance + physicalDevice: VkPhysicalDevice + device: VkDevice + queue: VkQueue + queueFamilyIndex: uint32 + storageBuffer: VkBuffer + storageBufferMemory: VkDeviceMemory + uniformBuffer: VkBuffer + uniformBufferMemory: VkDeviceMemory + descriptorSetLayout: VkDescriptorSetLayout + descriptorPool: VkDescriptorPool + descriptorSets: seq[VkDescriptorSet] + pipelineLayout: VkPipelineLayout + pipeline: VkPipeline + commandPool: VkCommandPool + commandBuffer: VkCommandBuffer + when defined(vkDebug): + debugUtilsMessenger: VkDebugUtilsMessengerEXT + + WorkgroupSize = object + x, y: uint32 + +proc cleanup(x: MandelbrotGenerator) = + # Clean up + freeMemory(x.device, x.uniformBufferMemory) + destroyBuffer(x.device, x.uniformBuffer) + freeMemory(x.device, x.storageBufferMemory) + destroyBuffer(x.device, x.storageBuffer) + destroyPipeline(x.device, x.pipeline) + destroyPipelineLayout(x.device, x.pipelineLayout) + destroyDescriptorPool(x.device, x.descriptorPool) + destroyDescriptorSetLayout(x.device, x.descriptorSetLayout) + destroyCommandPool(x.device, x.commandPool) + destroyDevice(x.device) + when defined(vkDebug): + destroyDebugUtilsMessenger(x.instance, x.debugUtilsMessenger) + destroyInstance(x.instance) + +proc newMandelbrotGenerator*(width, height: int32): MandelbrotGenerator = + ## Create a generator with the width and the height of the image. + result = MandelbrotGenerator( + width: width, + height: height, + workgroupSize: WorkgroupSize(x: 32, y: 32) + ) + +proc fetchRenderedImage(x: MandelbrotGenerator): seq[ColorRGBA] = + let count = x.width*x.height + let mappedMemory = mapMemory(x.device, x.storageBufferMemory, 0.VkDeviceSize, + VkDeviceSize(sizeof(Color)*count), 0.VkMemoryMapFlags) + let data = cast[ptr UncheckedArray[Color]](mappedMemory) + result = newSeq[ColorRGBA](count) + # Transform data from [0.0f, 1.0f] (float) to [0, 255] (uint8). + for i in 0..result.high: + result[i] = rgba(data[i]) + unmapMemory(x.device, x.storageBufferMemory) + +proc getLayers(): seq[cstring] = + result = @[] + when defined(vkDebug): + result.add("VK_LAYER_KHRONOS_validation") + +proc getExtensions(): seq[cstring] = + result = @[] + when defined(vkDebug): + result.add(VK_EXT_DEBUG_UTILS_EXTENSION_NAME) + +proc createInstance(x: var MandelbrotGenerator) = + # Create an ApplicationInfo struct + let applicationInfo = newVkApplicationInfo( + pApplicationName = "Mandelbrot", + applicationVersion = vkMakeVersion(0, 1, 0, 0), + pEngineName = "No Engine", + engineVersion = vkMakeVersion(0, 1, 0, 0), + apiVersion = vkApiVersion1_3 + ) + when defined(vkDebug): + # Enable the Khronos validation layer + let layerProperties = enumerateInstanceLayerProperties() + let foundValidationLayer = layerProperties.anyIt( + "VK_LAYER_KHRONOS_validation" == cast[cstring](it.layerName.addr)) + assert foundValidationLayer, "Validation layer required, but not available" + # Shader printf is a feature of the validation layers that needs to be enabled + let features = newVkValidationFeaturesEXT( + enabledValidationFeatures = [VkValidationFeatureEnableEXT.DebugPrintf], + disabledValidationFeatures = [] + ) + # Create a Vulkan instance + let layers = getLayers() + let extensions = getExtensions() + let instanceCreateInfo = newVkInstanceCreateInfo( + pNext = when defined(vkDebug): addr features else: nil, + pApplicationInfo = applicationInfo.addr, + pEnabledLayerNames = layers, + pEnabledExtensionNames = extensions + ) + x.instance = createInstance(instanceCreateInfo) + +proc findPhysicalDevice(x: var MandelbrotGenerator) = + # Enumerate physical devices + let physicalDevices = enumeratePhysicalDevices(x.instance) + assert physicalDevices.len > 0, "Cannot find any physical devices." + # We simply choose the first available physical device. + x.physicalDevice = physicalDevices[0] + +proc getComputeQueueFamilyIndex(physicalDevice: VkPhysicalDevice): uint32 = + # Find a compute queue family + let queueFamilyProperties = getQueueFamilyProperties(physicalDevice) + for i in 0 ..< queueFamilyProperties.len: + let property = queueFamilyProperties[i] + if property.queueCount > 0 and + VkQueueFlagBits.ComputeBit in property.queueFlags: + return i.uint32 + assert false, "Could not find a queue family that supports operations" + +proc createDevice(x: var MandelbrotGenerator) = + x.queueFamilyIndex = getComputeQueueFamilyIndex(x.physicalDevice) + let queuePriority: array[1, float32] = [1.0] + let queueCreateInfo = newVkDeviceQueueCreateInfo( + queueFamilyIndex = x.queueFamilyIndex, + queuePriorities = queuePriority + ) + let layers = getLayers() + # let features = VkPhysicalDeviceFeatures( + # robustBufferAccess: true.VkBool32, + # fragmentStoresAndAtomics: true.VkBool32, + # vertexPipelineStoresAndAtomics: true.VkBool32 + # ) + let deviceCreateInfo = newVkDeviceCreateInfo( + queueCreateInfos = [queueCreateInfo], + pEnabledLayerNames = layers, + pEnabledExtensionNames = [], + enabledFeatures = [] + ) + # Create a logical device + x.device = createDevice(x.physicalDevice, deviceCreateInfo) + # Get the compute queue + x.queue = getDeviceQueue(x.device, x.queueFamilyIndex, 0) + +proc findMemoryType(physicalDevice: VkPhysicalDevice, typeFilter: uint32, + size: VkDeviceSize, properties: VkMemoryPropertyFlags): uint32 = + # Find a suitable memory type for a Vulkan physical device + let memoryProperties = getPhysicalDeviceMemoryProperties(physicalDevice) + for i in 0 ..< memoryProperties.memoryTypeCount.int: + let memoryType = memoryProperties.memoryTypes[i] + if (typeFilter and (1'u32 shl i.uint32)) != 0 and + memoryType.propertyFlags >= properties and + size <= memoryProperties.memoryHeaps[memoryType.heapIndex].size: + return i.uint32 + assert false, "Failed to find suitable memory type" + +proc createBuffer(x: MandelbrotGenerator, size: VkDeviceSize, usage: VkBufferUsageFlags, + properties: VkMemoryPropertyFlags): tuple[buffer: VkBuffer, memory: VkDeviceMemory] = + let bufferCreateInfo = newVkBufferCreateInfo( + size = size, + usage = usage, + sharingMode = VkSharingMode.Exclusive, + queueFamilyIndices = [] + ) + let buffer = createBuffer(x.device, bufferCreateInfo) + # Memory requirements + let bufferMemoryRequirements = getBufferMemoryRequirements(x.device, buffer) + # Allocate memory for the buffer + let allocInfo = newVkMemoryAllocateInfo( + allocationSize = bufferMemoryRequirements.size, + memoryTypeIndex = findMemoryType(x.physicalDevice, + bufferMemoryRequirements.memoryTypeBits, + bufferMemoryRequirements.size, properties) + ) + let bufferMemory = allocateMemory(x.device, allocInfo) + # Bind the memory to the buffer + bindBufferMemory(x.device, buffer, bufferMemory, 0.VkDeviceSize) + result = (buffer, bufferMemory) + +proc createBuffers(x: var MandelbrotGenerator) = + # Allocate memory for both buffers + (x.storageBuffer, x.storageBufferMemory) = x.createBuffer( + VkDeviceSize(sizeof(float32)*4*x.width*x.height), + VkBufferUsageFlags{StorageBufferBit}, + VkMemoryPropertyFlags{HostCoherentBit, HostVisibleBit}) + (x.uniformBuffer, x.uniformBufferMemory) = x.createBuffer( + VkDeviceSize(sizeof(int32)*2), + VkBufferUsageFlags{UniformBufferBit}, + VkMemoryPropertyFlags{HostCoherentBit, HostVisibleBit}) + # Map the memory and write to the uniform buffer + let mappedMemory = mapMemory(x.device, x.uniformBufferMemory, 0.VkDeviceSize, + VkDeviceSize(sizeof(int32)*2), 0.VkMemoryMapFlags) + let ubo = [x.width.int32, x.height.int32] + copyMem(mappedMemory, ubo.addr, sizeof(int32)*2) + unmapMemory(x.device, x.uniformBufferMemory) + +proc createDescriptorSetLayout(x: var MandelbrotGenerator) = + # Define the descriptor set layout bindings + let bindings = [ + newVkDescriptorSetLayoutBinding( + binding = 0, + descriptorType = VkDescriptorType.StorageBuffer, + descriptorCount = 1, + stageFlags = VkShaderStageFlags{ComputeBit}, + pImmutableSamplers = nil + ), + newVkDescriptorSetLayoutBinding( + binding = 1, + descriptorType = VkDescriptorType.UniformBuffer, + descriptorCount = 1, + stageFlags = VkShaderStageFlags{ComputeBit}, + pImmutableSamplers = nil + ) + ] + # Create a descriptor set layout + let createInfo = newVkDescriptorSetLayoutCreateInfo( + bindings = bindings + ) + x.descriptorSetLayout = createDescriptorSetLayout(x.device, createInfo) + +proc createDescriptorSets(x: var MandelbrotGenerator) = + # Create a descriptor pool + let descriptorPoolSizes = [ + newVkDescriptorPoolSize( + `type` = VkDescriptorType.StorageBuffer, + descriptorCount = 1 + ), + newVkDescriptorPoolSize( + `type` = VkDescriptorType.UniformBuffer, + descriptorCount = 1 + ) + ] + let descriptorPoolCreateInfo = newVkDescriptorPoolCreateInfo( + maxSets = 2, + poolSizes = descriptorPoolSizes + ) + x.descriptorPool = createDescriptorPool(x.device, descriptorPoolCreateInfo) + # Allocate a descriptor set + let descriptorSetAllocateInfo = newVkDescriptorSetAllocateInfo( + descriptorPool = x.descriptorPool, + setLayouts = [x.descriptorSetLayout] + ) + let descriptorSet = allocateDescriptorSets(x.device, descriptorSetAllocateInfo) + x.descriptorSets = @[descriptorSet] + # Update the descriptor set with the buffer information + let descriptorStorageBufferInfo = newVkDescriptorBufferInfo( + buffer = x.storageBuffer, + offset = 0.VkDeviceSize, + range = VkDeviceSize(sizeof(float32)*4*x.width*x.height) + ) + let descriptorUniformBufferInfo = newVkDescriptorBufferInfo( + buffer = x.uniformBuffer, + offset = 0.VkDeviceSize, + range = VkDeviceSize(sizeof(int32)*2) + ) + let writeDescriptorSets = [ + newVkWriteDescriptorSet( + dstSet = x.descriptorSets[0], + dstBinding = 0, + dstArrayElement = 0, + descriptorCount = 1, + descriptorType = VkDescriptorType.StorageBuffer, + pImageInfo = nil, + pBufferInfo = descriptorStorageBufferInfo.addr, + pTexelBufferView = nil + ), + newVkWriteDescriptorSet( + dstSet = x.descriptorSets[0], + dstBinding = 1, + dstArrayElement = 0, + descriptorCount = 1, + descriptorType = VkDescriptorType.UniformBuffer, + pImageInfo = nil, + pBufferInfo = descriptorUniformBufferInfo.addr, + pTexelBufferView = nil + ) + ] + updateDescriptorSets(x.device, writeDescriptorSets, []) + +proc createComputePipeline(x: var MandelbrotGenerator) = + # Create the shader module + let shaderModuleCreateInfo = newVkShaderModuleCreateInfo( + code = readFile("examples/mandelbrot/build/shaders/mandelbrot.comp.spv") + ) + let computeShaderModule = createShaderModule(x.device, shaderModuleCreateInfo) + let specializationMapEntries = [ + newVkSpecializationMapEntry( + constantID = 0, + offset = offsetOf(WorkgroupSize, x).uint32, + size = sizeof(uint32).uint + ), + newVkSpecializationMapEntry( + constantID = 1, + offset = offsetOf(WorkgroupSize, y).uint32, + size = sizeof(uint32).uint + ) + ] + let specializationInfo = newVkSpecializationInfo( + mapEntries = specializationMapEntries, + dataSize = sizeof(WorkgroupSize).uint, + pData = x.workgroupSize.addr + ) + let shaderStageCreateInfo = newVkPipelineShaderStageCreateInfo( + stage = VkShaderStageFlagBits.ComputeBit, + module = computeShaderModule, + pName = "main", + pSpecializationInfo = specializationInfo.addr + ) + # Create a pipeline layout with the descriptor set layout + let pipelineLayoutCreateInfo = newVkPipelineLayoutCreateInfo( + setLayouts = [x.descriptorSetLayout], + pushConstantRanges = [] + ) + x.pipelineLayout = createPipelineLayout(x.device, pipelineLayoutCreateInfo) + # Create the compute pipeline + let createInfos = [ + newVkComputePipelineCreateInfo( + stage = shaderStageCreateInfo, + layout = x.pipelineLayout, + basePipelineHandle = 0.VkPipeline, + basePipelineIndex = -1 + ) + ] + x.pipeline = createComputePipelines(x.device, 0.VkPipelineCache, createInfos) + destroyShaderModule(x.device, computeShaderModule) + +proc createCommandBuffer(x: var MandelbrotGenerator) = + # Create a command pool + let commandPoolCreateInfo = newVkCommandPoolCreateInfo( + queueFamilyIndex = x.queueFamilyIndex + ) + x.commandPool = createCommandPool(x.device, commandPoolCreateInfo) + # Allocate a command buffer from the command pool + let commandBufferAllocateInfo = newVkCommandBufferAllocateInfo( + commandPool = x.commandPool, + level = VkCommandBufferLevel.Primary, + commandBufferCount = 1 + ) + x.commandBuffer = allocateCommandBuffers(x.device, commandBufferAllocateInfo) + # Begin recording the command buffer + let commandBufferBeginInfo = newVkCommandBufferBeginInfo( + flags = VkCommandBufferUsageFlags{OneTimeSubmitBit}, + pInheritanceInfo = nil + ) + beginCommandBuffer(x.commandBuffer, commandBufferBeginInfo) + # Bind the compute pipeline + cmdBindPipeline(x.commandBuffer, VkPipelineBindPoint.Compute, x.pipeline) + # Bind the descriptor set + cmdBindDescriptorSets(x.commandBuffer, VkPipelineBindPoint.Compute, + x.pipelineLayout, 0, x.descriptorSets, []) + # Dispatch the compute work + let numWorkgroupX = ceilDiv(x.width.uint32, x.workgroupSize.x) + let numWorkgroupY = ceilDiv(x.height.uint32, x.workgroupSize.y) + cmdDispatch(x.commandBuffer, numWorkgroupX, numWorkgroupY, 1) + # End recording the command buffer + endCommandBuffer(x.commandBuffer) + +proc submitCommandBuffer(x: MandelbrotGenerator) = + let submitInfos = [ + newVkSubmitInfo( + waitSemaphores = [], + waitDstStageMask = [], + commandBuffers = [x.commandBuffer], + signalSemaphores = [] + ) + ] + # Create a fence + let fenceCreateInfo = newVkFenceCreateInfo() + let fence = createFence(x.device, fenceCreateInfo) + when defined(useRenderDoc): startFrameCapture(x.instance) + # Submit the command buffer + queueSubmit(x.queue, submitInfos, fence) + when defined(useRenderDoc): endFrameCapture(x.instance) + # Wait for the fence to be signaled, indicating completion of the command buffer execution + waitForFence(x.device, fence, true.VkBool32, high(uint64)) + destroyFence(x.device, fence) + +when defined(vkDebug): + proc debugCallback(messageSeverity: VkDebugUtilsMessageSeverityFlagBitsEXT, + messageTypes: VkDebugUtilsMessageTypeFlagsEXT, + pCallbackData: ptr VkDebugUtilsMessengerCallbackDataEXT, + pUserData: pointer): VkBool32 {.cdecl.} = + var message = $pCallbackData.pMessage + if "WARNING-DEBUG-PRINTF" == pCallbackData.pMessageIdName: + # Validation messages are a bit verbose. + let delimiter = "| vkQueueSubmit(): " + if (let pos = message.find(delimiter); pos >= 0): + # Extract the part of the message after the delimiter + message = message.substr(pos + len(delimiter)) + stderr.writeLine(message) + return false.VkBool32 + + proc setupDebugUtilsMessenger(x: var MandelbrotGenerator) = + let severityFlags = VkDebugUtilsMessageSeverityFlagsEXT{ + VerboseBit, InfoBit, WarningBit, ErrorBit} + let messageTypeFlags = VkDebugUtilsMessageTypeFlagsEXT{ + GeneralBit, ValidationBit, PerformanceBit} + let createInfo = newVkDebugUtilsMessengerCreateInfoEXT( + messageSeverity = severityFlags, + messageType = messageTypeFlags, + pfnUserCallback = debugCallback + ) + x.debugUtilsMessenger = createDebugUtilsMessengerEXT(x.instance, createInfo) + +proc generate*(x: var MandelbrotGenerator): seq[ColorRGBA] = + ## Return the raw data of a mandelbrot image. + try: + vkPreload() + # Hardware Setup Stage + createInstance(x) + vkInit(x.instance, load1_2 = false, load1_3 = false) + when defined(vkDebug): + loadVkExtDebugUtils() + setupDebugUtilsMessenger(x) + findPhysicalDevice(x) + createDevice(x) + # Resource Setup Stage + createBuffers(x) + # Pipeline Setup Stage + createDescriptorSetLayout(x) + createDescriptorSets(x) + createComputePipeline(x) + # Command Execution Stage + createCommandBuffer(x) + submitCommandBuffer(x) + # Fetch data from VRAM to RAM. + result = fetchRenderedImage(x) + finally: + cleanup(x) diff --git a/examples/mandelbrot/mandelbrot.nimble b/examples/mandelbrot/mandelbrot.nimble new file mode 100644 index 0000000..dcdac4e --- /dev/null +++ b/examples/mandelbrot/mandelbrot.nimble @@ -0,0 +1,12 @@ +# Package +version = "0.1.0" +author = "Antonis Geralis" +description = "Vulkan compute example" +license = "Public Domain" + +# Dependencies +requires "nim >= 2.1.0" +requires "pixie >= 5.0.7" +requires "https://github.com/planetis-m/vulkan.git >= 1.3.279" + +include "build_shaders.nims" diff --git a/examples/mandelbrot/shaders/mandelbrot.comp.glsl b/examples/mandelbrot/shaders/mandelbrot.comp.glsl new file mode 100644 index 0000000..8c9649c --- /dev/null +++ b/examples/mandelbrot/shaders/mandelbrot.comp.glsl @@ -0,0 +1,62 @@ +#version 450 +#extension GL_EXT_debug_printf : require + +// We set local workgroup size via Specialization Constants. +layout (local_size_x_id = 0, local_size_y_id = 1) in; + +// Otherwise, we have to hard code the workgroup size in compute shader like +// below: +// layout (local_size_x = 32, local_size_y = 32) in; + +// We access image data via storage buffer. +layout (std140, binding = 0) buffer buf { + vec4 image[]; +}; + +// The size of image is accessed via uniform buffer. +layout (binding = 1) uniform UBO { + int width; + int height; +} ubo; + +vec3 palette(float t) { + vec3 d = vec3(0.3f, 0.3f, 0.5f); + vec3 e = vec3(-0.2f, -0.3f, -0.5f); + vec3 f = vec3(2.1f, 2.0f, 3.0f); + vec3 g = vec3(0.0f, 0.1f, 0.0f); + + return d + e * cos(6.28318f * (f * t + g)); +} + +void main() { + // In order to fit the work into workgroups, some unnecessary invocations are + // launched. We terminate those invocations here. + if (gl_GlobalInvocationID.x >= ubo.width || + gl_GlobalInvocationID.y >= ubo.height) { return; } + + float x = float(gl_GlobalInvocationID.x) / float(ubo.width); + float y = float(gl_GlobalInvocationID.y) / float(ubo.height); + + // Code for computing mandelbrot set. + const vec2 uv = vec2(x, y - 0.5f) * + vec2(1.0f, float(ubo.height) / float(ubo.width)); + const vec2 c = uv * 3.0f + vec2(-2.1f, 0.0f); + vec2 z = vec2(0.0f); + const int m = 128; + int n = 0; + for (int i = 0; i < m; ++i) { + z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c; + if (dot(z, z) > 4) { break; } + ++n; + } + + // We use a cosine palette to determine the color of pixels. + vec4 color = vec4(palette(float(n) / float(m)), 1.0f); + + // Debug printf output + if (gl_GlobalInvocationID == uvec3(0)) { + debugPrintfEXT("Invocation ID: %u", 0); + } + // Store the color data into the storage buffer. + image[ubo.width * gl_GlobalInvocationID.y + gl_GlobalInvocationID.x] = color; +} diff --git a/examples/run_mandelbrot.nim b/examples/run_mandelbrot.nim new file mode 100644 index 0000000..2750706 --- /dev/null +++ b/examples/run_mandelbrot.nim @@ -0,0 +1,489 @@ +import std/[strutils, os] +import sets +import chroma +import glfw +import glfw/wrapper as glfww +import vulkan +import vulkan/wrapper +import ./mandelbrot/mandelbrot + +{.pragma: glfwImport, dynlib: "libglfw.so.3".} +proc glfwCreateWindowSurface*(instance: VkInstance, window: glfww.Window, + allocator: ptr VkAllocationCallbacks, + surface: ptr VkSurfaceKHR): VkResult + {.glfwImport, importc: "glfwCreateWindowSurface".} + +const + deviceExtensions = ["VK_KHR_swapchain"] + + +type + QueueFamilyIndices = object + graphicsFamily: uint32 + graphicsFamilyFound: bool + presentFamily: uint32 + presentFamilyFound: bool + + SwapChainSupportDetails = object + capabilities: VkSurfaceCapabilitiesKHR + formats: seq[VkSurfaceFormatKHR] + presentModes: seq[VkPresentModeKHR] + + SwapChain = object + handle: VkSwapchainKHR + images: seq[VkImage] + format: VkFormat + extent: VkExtent2D + +proc isComplete(indices: QueueFamilyIndices): bool = + indices.graphicsFamilyFound and indices.presentFamilyFound + +proc findQueueFamilies(pDevice: VkPhysicalDevice, surface: VkSurfaceKHR): QueueFamilyIndices = + var queueFamilyCount: uint32 = 0 + vkGetPhysicalDeviceQueueFamilyProperties(pDevice, queueFamilyCount.addr, nil) + if queueFamilyCount == 0: + return + var queueFamilies = newSeq[VkQueueFamilyProperties](queueFamilyCount) + vkGetPhysicalDeviceQueueFamilyProperties(pDevice, queueFamilyCount.addr, queueFamilies[0].addr) + + var index: uint32 = 0 + for queueFamily in queueFamilies: + if (queueFamily.queueFlags.uint32 and VkQueueGraphicsBit.uint32) > 0'u32: + result.graphicsFamily = index + result.graphicsFamilyFound = true + var presentSupport: VkBool32 + discard vkGetPhysicalDeviceSurfaceSupportKHR(pDevice, index, surface, presentSupport.addr) + if presentSupport.ord == 1: + result.presentFamily = index + result.presentFamilyFound = true + if result.isComplete: + break + index.inc + +proc checkDeviceExtensionSupport(pDevice: VkPhysicalDevice): bool = + var extCount: uint32 + discard vkEnumerateDeviceExtensionProperties(pDevice, nil, extCount.addr, nil) + var availableExts = newSeq[VkExtensionProperties](extCount) + discard vkEnumerateDeviceExtensionProperties(pDevice, nil, extCount.addr, availableExts[0].addr) + + var requiredExts = deviceExtensions.toHashSet + for ext in availableExts.mitems: + requiredExts.excl($cast[cstring](ext.extensionName.addr)) + requiredExts.len == 0 + +proc querySwapChainSupport(pDevice: VkPhysicalDevice, surface: VkSurfaceKHR): SwapChainSupportDetails = + discard vkGetPhysicalDeviceSurfaceCapabilitiesKHR(pDevice, surface, result.capabilities.addr) + var formatCount: uint32 + discard vkGetPhysicalDeviceSurfaceFormatsKHR(pDevice, surface, formatCount.addr, nil) + if formatCount != 0: + result.formats.setLen(formatCount) + discard vkGetPhysicalDeviceSurfaceFormatsKHR(pDevice, surface, formatCount.addr, result.formats[0].addr) + var presentModeCount: uint32 + discard vkGetPhysicalDeviceSurfacePresentModesKHR(pDevice, surface, presentModeCount.addr, nil) + if presentModeCount != 0: + result.presentModes.setLen(presentModeCount) + discard vkGetPhysicalDeviceSurfacePresentModesKHR(pDevice, surface, presentModeCount.addr, result.presentModes[0].addr) + +proc isDeviceSuitable(pDevice: VkPhysicalDevice, surface: VkSurfaceKHR): bool = + let indices: QueueFamilyIndices = findQueueFamilies(pDevice, surface) + let extsSupported = pDevice.checkDeviceExtensionSupport + var swapChainAdequate = false + if extsSupported: + let swapChainSupport = querySwapChainSupport(pDevice, surface) + swapChainAdequate = + swapChainSupport.formats.len != 0 and + swapChainSupport.presentModes.len != 0 + indices.isComplete and extsSupported and swapChainAdequate + +proc chooseSwapSurfaceFormat(availableFormats: seq[VkSurfaceFormatKHR]): VkSurfaceFormatKHR = + for availableFormat in availableFormats: + if availableFormat.format == VK_FORMAT_R8G8B8A8_UNORM and + availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR: + return availableFormat + for availableFormat in availableFormats: + if availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM and + availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR: + return availableFormat + availableFormats[0] + +proc chooseSwapPresentMode(availablePresentModes: seq[VkPresentModeKHR]): VkPresentModeKHR = + for presentMode in availablePresentModes: + if presentMode == VK_PRESENT_MODE_MAILBOX_KHR: + return presentMode + VK_PRESENT_MODE_FIFO_KHR + +proc chooseSwapExtent(capabilities: VkSurfaceCapabilitiesKHR, width, height: int32): VkExtent2D = + if capabilities.currentExtent.width != 0xFFFFFFFF'u32: + return capabilities.currentExtent + result.width = width.uint32 + result.height = height.uint32 + result.width = max(capabilities.minImageExtent.width, min(capabilities.maxImageExtent.width, result.width)) + result.height = max(capabilities.minImageExtent.height, min(capabilities.maxImageExtent.height, result.height)) + +proc createSwapChain(device: VkDevice, physicalDevice: VkPhysicalDevice, + surface: VkSurfaceKHR, width, height: int32): SwapChain = + let + swapChainSupport = querySwapChainSupport(physicalDevice, surface) + surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats) + presentMode = chooseSwapPresentMode(swapChainSupport.presentModes) + extent = chooseSwapExtent(swapChainSupport.capabilities, width, height) + var imageCount = swapChainSupport.capabilities.minImageCount + 1 + if swapChainSupport.capabilities.maxImageCount > 0 and + imageCount > swapChainSupport.capabilities.maxImageCount: + imageCount = swapChainSupport.capabilities.maxImageCount + + let indices = findQueueFamilies(physicalDevice, surface) + let queueFamilyIndices = + if indices.graphicsFamily != indices.presentFamily: + @[indices.graphicsFamily, indices.presentFamily] + else: + @[] + + var createInfo = newVkSwapchainCreateInfoKHR( + surface = surface, + minImageCount = imageCount, + imageFormat = surfaceFormat.format, + imageColorSpace = surfaceFormat.colorSpace, + imageExtent = extent, + imageArrayLayers = 1, + imageUsage = VkImageUsageFlags{TransferDstBit}, + imageSharingMode = if queueFamilyIndices.len > 0: + VK_SHARING_MODE_CONCURRENT else: VK_SHARING_MODE_EXCLUSIVE, + queueFamilyIndices = queueFamilyIndices, + preTransform = swapChainSupport.capabilities.currentTransform, + compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + presentMode = presentMode, + clipped = VkBool32(VK_TRUE), + oldSwapchain = VkSwapchainKHR(0) + ) + + checkVkResult vkCreateSwapchainKHR(device, createInfo.addr, nil, result.handle.addr) + discard vkGetSwapchainImagesKHR(device, result.handle, imageCount.addr, nil) + result.images.setLen(imageCount) + discard vkGetSwapchainImagesKHR(device, result.handle, imageCount.addr, result.images[0].addr) + result.format = surfaceFormat.format + result.extent = extent + +proc findMemoryType(physicalDevice: VkPhysicalDevice, typeFilter: uint32, + properties: VkMemoryPropertyFlags): uint32 = + let memoryProperties = getPhysicalDeviceMemoryProperties(physicalDevice) + for i in 0 ..< memoryProperties.memoryTypeCount.int: + let memoryType = memoryProperties.memoryTypes[i] + if (typeFilter and (1'u32 shl i.uint32)) != 0 and + memoryType.propertyFlags >= properties: + return i.uint32 + raise newException(Exception, "Failed to find suitable memory type") + +proc createBuffer(device: VkDevice, physicalDevice: VkPhysicalDevice, + size: VkDeviceSize, usage: VkBufferUsageFlags, + properties: VkMemoryPropertyFlags): tuple[buffer: VkBuffer, memory: VkDeviceMemory] = + let bufferCreateInfo = newVkBufferCreateInfo( + size = size, + usage = usage, + sharingMode = VkSharingMode.Exclusive, + queueFamilyIndices = [] + ) + let buffer = createBuffer(device, bufferCreateInfo) + let bufferMemoryRequirements = getBufferMemoryRequirements(device, buffer) + let allocInfo = newVkMemoryAllocateInfo( + allocationSize = bufferMemoryRequirements.size, + memoryTypeIndex = findMemoryType(physicalDevice, + bufferMemoryRequirements.memoryTypeBits, + properties) + ) + let bufferMemory = allocateMemory(device, allocInfo) + bindBufferMemory(device, buffer, bufferMemory, 0.VkDeviceSize) + result = (buffer, bufferMemory) + +proc toPixelBytes(pixels: seq[ColorRGBA], format: VkFormat): seq[uint8] = + result = newSeq[uint8](pixels.len * 4) + let useBgra = format == VK_FORMAT_B8G8R8A8_UNORM + for i, p in pixels: + let base = i * 4 + if useBgra: + result[base] = p.b + result[base + 1] = p.g + result[base + 2] = p.r + result[base + 3] = p.a + else: + result[base] = p.r + result[base + 1] = p.g + result[base + 2] = p.b + result[base + 3] = p.a + +proc recordCopy(cmd: VkCommandBuffer, image: VkImage, extent: VkExtent2D, + buffer: VkBuffer) = + let beginInfo = newVkCommandBufferBeginInfo(pInheritanceInfo = nil) + checkVkResult vkBeginCommandBuffer(cmd, beginInfo.addr) + + var barrierToTransfer = VkImageMemoryBarrier( + sType: VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + srcAccessMask: 0.VkAccessFlags, + dstAccessMask: VkAccessFlags{TransferWriteBit}, + oldLayout: VK_IMAGE_LAYOUT_UNDEFINED, + newLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + srcQueueFamilyIndex: VK_QUEUE_FAMILY_IGNORED, + dstQueueFamilyIndex: VK_QUEUE_FAMILY_IGNORED, + image: image, + subresourceRange: VkImageSubresourceRange( + aspectMask: VkImageAspectFlags{ColorBit}, + baseMipLevel: 0, + levelCount: 1, + baseArrayLayer: 0, + layerCount: 1 + ) + ) + + vkCmdPipelineBarrier( + cmd, + VkPipelineStageFlags{TopOfPipeBit}, + VkPipelineStageFlags{TransferBit}, + 0.VkDependencyFlags, + 0, nil, + 0, nil, + 1, barrierToTransfer.addr + ) + + var region = VkBufferImageCopy( + bufferOffset: 0.VkDeviceSize, + bufferRowLength: 0, + bufferImageHeight: 0, + imageSubresource: VkImageSubresourceLayers( + aspectMask: VkImageAspectFlags{ColorBit}, + mipLevel: 0, + baseArrayLayer: 0, + layerCount: 1 + ), + imageOffset: VkOffset3D(x: 0, y: 0, z: 0), + imageExtent: VkExtent3D(width: extent.width, height: extent.height, depth: 1) + ) + + vkCmdCopyBufferToImage( + cmd, + buffer, + image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + region.addr + ) + + var barrierToPresent = VkImageMemoryBarrier( + sType: VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + srcAccessMask: VkAccessFlags{TransferWriteBit}, + dstAccessMask: VkAccessFlags{MemoryReadBit}, + oldLayout: VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + newLayout: VkImageLayout.PresentSrcKhr, + srcQueueFamilyIndex: VK_QUEUE_FAMILY_IGNORED, + dstQueueFamilyIndex: VK_QUEUE_FAMILY_IGNORED, + image: image, + subresourceRange: VkImageSubresourceRange( + aspectMask: VkImageAspectFlags{ColorBit}, + baseMipLevel: 0, + levelCount: 1, + baseArrayLayer: 0, + layerCount: 1 + ) + ) + + vkCmdPipelineBarrier( + cmd, + VkPipelineStageFlags{TransferBit}, + VkPipelineStageFlags{BottomOfPipeBit}, + 0.VkDependencyFlags, + 0, nil, + 0, nil, + 1, barrierToPresent.addr + ) + + checkVkResult vkEndCommandBuffer(cmd) + +proc main(params: seq[string]) = + if params.len notin [0, 2]: + quit("Usage: mandelbrot ") + + let width = if params.len == 0: 800 else: params[0].parseInt + let height = if params.len == 0: 600 else: params[1].parseInt + + var generator = newMandelbrotGenerator(width.int32, height.int32) + let pixels = generator.generate() + doAssert pixels.len == width * height + + glfw.initialize() + glfww.windowHint(glfww.hClientApi.int32, glfww.oaNoApi.int32) + glfww.windowHint(glfww.hResizable.int32, 0) + + let handle = glfww.createWindow(width.int32, height.int32, "Mandelbrot", nil, nil) + if handle.isNil: + glfw.terminate() + quit("failed to create window") + + let window = newWindow(handle) + + vkPreload() + let + glfwExtensionCount = block: + var count: uint32 = 0 + discard glfww.getRequiredInstanceExtensions(count.addr) + count + glfwExtensions = glfww.getRequiredInstanceExtensions(glfwExtensionCount.addr) + + let instance = block: + let appInfo = newVkApplicationInfo( + pApplicationName = "Mandelbrot", + applicationVersion = vkMakeVersion(0, 1, 0, 0), + pEngineName = "No Engine", + engineVersion = vkMakeVersion(0, 1, 0, 0), + apiVersion = vkApiVersion1_1 + ) + var extensionNames = newSeq[cstring](glfwExtensionCount.int) + for i in 0.. 0: + result.setLen(layerCount) + res = vkEnumerateInstanceLayerProperties(layerCount.addr, result[0].addr) + if res == VkSuccess and layerCount < uint32(result.len): + result.setLen(layerCount) + elif res == VkSuccess: + break + checkVkResult res + +proc createInstance*(instanceCreateInfo: VkInstanceCreateInfo, allocator: ptr VkAllocationCallbacks = nil): VkInstance = + checkVkResult vkCreateInstance(instanceCreateInfo.addr, allocator, result.addr) + +proc enumeratePhysicalDevices*(instance: VkInstance): seq[VkPhysicalDevice] = + result = @[] + var physicalDeviceCount: uint32 = 0 + var res = VkIncomplete + while res == VkIncomplete: + res = vkEnumeratePhysicalDevices(instance, physicalDeviceCount.addr, nil) + if res == VkSuccess and physicalDeviceCount > 0: + result.setLen(physicalDeviceCount) + res = vkEnumeratePhysicalDevices(instance, physicalDeviceCount.addr, result[0].addr) + if res == VkSuccess and physicalDeviceCount < uint32(result.len): + result.setLen(physicalDeviceCount) + elif res == VkSuccess: + break + checkVkResult res + +proc getQueueFamilyProperties*(physicalDevice: VkPhysicalDevice): seq[VkQueueFamilyProperties] = + result = @[] + var queueFamilyCount: uint32 = 0 + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, queueFamilyCount.addr, nil) + result.setLen(queueFamilyCount) + vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, queueFamilyCount.addr, result[0].addr) + +proc getPhysicalDeviceFeatures*(physicalDevice: VkPhysicalDevice): VkPhysicalDeviceFeatures = + vkGetPhysicalDeviceFeatures(physicalDevice, result.addr) + +proc getPhysicalDeviceMemoryProperties*(physicalDevice: VkPhysicalDevice): VkPhysicalDeviceMemoryProperties = + vkGetPhysicalDeviceMemoryProperties(physicalDevice, result.addr) + +proc getPhysicalDeviceProperties*(physicalDevice: VkPhysicalDevice): VkPhysicalDeviceProperties = + vkGetPhysicalDeviceProperties(physicalDevice, result.addr) + +proc createDevice*(physicalDevice: VkPhysicalDevice, deviceCreateInfo: VkDeviceCreateInfo, + allocator: ptr VkAllocationCallbacks = nil): VkDevice = + checkVkResult vkCreateDevice(physicalDevice, deviceCreateInfo.addr, allocator, result.addr) + +proc getDeviceQueue*(device: VkDevice, queueFamilyIndex: uint32, queueIndex: uint32): VkQueue = + vkGetDeviceQueue(device, queueFamilyIndex, queueIndex, result.addr) + +proc createBuffer*(device: VkDevice, bufferCreateInfo: VkBufferCreateInfo, allocator: ptr VkAllocationCallbacks = nil): VkBuffer = + checkVkResult vkCreateBuffer(device, bufferCreateInfo.addr, allocator, result.addr) + +proc getBufferMemoryRequirements*(device: VkDevice, buffer: VkBuffer): VkMemoryRequirements = + vkGetBufferMemoryRequirements(device, buffer, result.addr) + +proc allocateMemory*(device: VkDevice, allocateInfo: VkMemoryAllocateInfo, allocator: ptr VkAllocationCallbacks = nil): VkDeviceMemory = + checkVkResult vkAllocateMemory(device, allocateInfo.addr, allocator, result.addr) + +proc bindBufferMemory*(device: VkDevice, buffer: VkBuffer, memory: VkDeviceMemory, memoryOffset: VkDeviceSize) = + checkVkResult vkBindBufferMemory(device, buffer, memory, memoryOffset) + +proc createDescriptorSetLayout*(device: VkDevice, createInfo: VkDescriptorSetLayoutCreateInfo, + allocator: ptr VkAllocationCallbacks = nil): VkDescriptorSetLayout = + checkVkResult vkCreateDescriptorSetLayout(device, createInfo.addr, allocator, result.addr) + +proc createDescriptorPool*(device: VkDevice, createInfo: VkDescriptorPoolCreateInfo, + allocator: ptr VkAllocationCallbacks = nil): VkDescriptorPool = + checkVkResult vkCreateDescriptorPool(device, createInfo.addr, allocator, result.addr) + +proc allocateDescriptorSets*(device: VkDevice, allocateInfo: VkDescriptorSetAllocateInfo): VkDescriptorSet = + var descriptorSet: VkDescriptorSet + checkVkResult vkAllocateDescriptorSets(device, allocateInfo.addr, descriptorSet.addr) + result = descriptorSet + +proc updateDescriptorSets*(device: VkDevice, descriptorWrites: openarray[VkWriteDescriptorSet], + descriptorCopies: openarray[VkCopyDescriptorSet]) = + vkUpdateDescriptorSets(device, descriptorWrites.len.uint32, if descriptorWrites.len == 0: nil else: cast[ptr VkWriteDescriptorSet](descriptorWrites), descriptorCopies.len.uint32, if descriptorWrites.len == 0: nil else: cast[ptr VkCopyDescriptorSet](descriptorCopies)) + +proc createShaderModule*(device: VkDevice, createInfo: VkShaderModuleCreateInfo, + allocator: ptr VkAllocationCallbacks = nil): VkShaderModule = + checkVkResult vkCreateShaderModule(device, createInfo.addr, allocator, result.addr) + +proc createPipelineLayout*(device: VkDevice, createInfo: VkPipelineLayoutCreateInfo, allocator: ptr VkAllocationCallbacks = nil): VkPipelineLayout = + checkVkResult vkCreatePipelineLayout(device, createInfo.addr, allocator, result.addr) + +proc createComputePipelines*(device: VkDevice, pipelineCache: VkPipelineCache, + createInfos: openarray[VkComputePipelineCreateInfo], + allocator: ptr VkAllocationCallbacks = nil): VkPipeline = + checkVkResult vkCreateComputePipelines(device, pipelineCache, createInfos.len.uint32, if createInfos.len == 0: nil else: cast[ptr VkComputePipelineCreateInfo](createInfos), allocator, result.addr) + +proc createFence*(device: VkDevice, createInfo: VkFenceCreateInfo, allocator: ptr VkAllocationCallbacks = nil): VkFence = + checkVkResult vkCreateFence(device, createInfo.addr, allocator, result.addr) + +proc queueSubmit*(queue: VkQueue, submits: openarray[VkSubmitInfo], fence: VkFence) = + checkVkResult vkQueueSubmit(queue, submits.len.uint32, if submits.len == 0: nil else: cast[ptr VkSubmitInfo](submits), fence) + +proc waitForFences*(device: VkDevice, fences: openarray[VkFence], waitAll: VkBool32, timeout: uint64) = + checkVkResult vkWaitForFences(device, fences.len.uint32, if fences.len == 0: nil else: cast[ptr VkFence](fences), waitAll, timeout) + +proc waitForFence*(device: VkDevice, fence: VkFence, waitAll: VkBool32, timeout: uint64) = + checkVkResult vkWaitForFences(device, 1, fence.addr, waitAll, timeout) + +proc createDebugUtilsMessengerEXT*(instance: VkInstance, createInfo: VkDebugUtilsMessengerCreateInfoEXT, + allocator: ptr VkAllocationCallbacks = nil): VkDebugUtilsMessengerEXT = + checkVkResult vkCreateDebugUtilsMessengerEXT(instance, createInfo.addr, allocator, result.addr) + +proc createCommandPool*(device: VkDevice, createInfo: VkCommandPoolCreateInfo, + allocator: ptr VkAllocationCallbacks = nil): VkCommandPool = + checkVkResult vkCreateCommandPool(device, createInfo.addr, allocator, result.addr) + +proc allocateCommandBuffers*(device: VkDevice, allocateInfo: VkCommandBufferAllocateInfo): VkCommandBuffer = + checkVkResult vkAllocateCommandBuffers(device, allocateInfo.addr, result.addr) + +proc beginCommandBuffer*(commandBuffer: VkCommandBuffer, beginInfo: VkCommandBufferBeginInfo) = + checkVkResult vkBeginCommandBuffer(commandBuffer, beginInfo.addr) + +proc cmdBindDescriptorSets*(commandBuffer: VkCommandBuffer, pipelineBindPoint: VkPipelineBindPoint, + layout: VkPipelineLayout, firstSet: uint32, descriptorSets: openarray[VkDescriptorSet], + dynamicOffsets: openarray[uint32]) = + vkCmdBindDescriptorSets(commandBuffer, pipelineBindPoint, layout, firstSet, descriptorSets.len.uint32, if descriptorSets.len == 0: nil else: cast[ptr VkDescriptorSet](descriptorSets), dynamicOffsets.len.uint32, if dynamicOffsets.len == 0: nil else: cast[ptr uint32](dynamicOffsets)) + +proc endCommandBuffer*(commandBuffer: VkCommandBuffer) = + checkVkResult vkEndCommandBuffer(commandBuffer) + +proc deviceWaitIdle*(device: VkDevice) = + checkVkResult vkDeviceWaitIdle(device) + +proc unmapMemory*(device: VkDevice, memory: VkDeviceMemory) = + vkUnmapMemory(device, memory) + +proc cmdDispatch*(commandBuffer: VkCommandBuffer, groupCountX: uint32, groupCountY: uint32, + groupCountZ: uint32) = + vkCmdDispatch(commandBuffer, groupCountX, groupCountY, groupCountZ) + +proc cmdBindPipeline*(commandBuffer: VkCommandBuffer, pipelineBindPoint: VkPipelineBindPoint, + pipeline: VkPipeline) = + vkCmdBindPipeline(commandBuffer, pipelineBindPoint, pipeline) + +proc freeMemory*(device: VkDevice, memory: VkDeviceMemory, allocator: ptr VkAllocationCallbacks = nil) = + if memory != 0.VkDeviceMemory: + vkFreeMemory(device, memory, allocator) + +proc destroyBuffer*(device: VkDevice, buffer: VkBuffer, allocator: ptr VkAllocationCallbacks = nil) = + if buffer != 0.VkBuffer: + vkDestroyBuffer(device, buffer, allocator) + +proc destroyPipeline*(device: VkDevice, pipeline: VkPipeline, allocator: ptr VkAllocationCallbacks = nil) = + if pipeline != 0.VkPipeline: + vkDestroyPipeline(device, pipeline, allocator) + +proc destroyPipelineLayout*(device: VkDevice, pipelineLayout: VkPipelineLayout, allocator: ptr VkAllocationCallbacks = nil) = + if pipelineLayout != 0.VkPipelineLayout: + vkDestroyPipelineLayout(device, pipelineLayout, allocator) + +proc freeDescriptorSets*(device: VkDevice, descriptorPool: VkDescriptorPool, + descriptorSets: openarray[VkDescriptorSet]) = + if descriptorSets.len > 0: + discard vkFreeDescriptorSets(device, descriptorPool, descriptorSets.len.uint32, cast[ptr VkDescriptorSet](descriptorSets)) + +proc destroyDescriptorPool*(device: VkDevice, descriptorPool: VkDescriptorPool, allocator: ptr VkAllocationCallbacks = nil) = + if descriptorPool != 0.VkDescriptorPool: + vkDestroyDescriptorPool(device, descriptorPool, allocator) + +proc destroyDescriptorSetLayout*(device: VkDevice, descriptorSetLayout: VkDescriptorSetLayout, + allocator: ptr VkAllocationCallbacks = nil) = + if descriptorSetLayout != 0.VkDescriptorSetLayout: + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, allocator) + +proc destroyCommandPool*(device: VkDevice, commandPool: VkCommandPool, allocator: ptr VkAllocationCallbacks = nil) = + if commandPool != 0.VkCommandPool: + vkDestroyCommandPool(device, commandPool, allocator) + +proc destroyDevice*(device: VkDevice, allocator: ptr VkAllocationCallbacks = nil) = + if device != 0.VkDevice: + vkDestroyDevice(device, allocator) + +proc destroyDebugUtilsMessenger*(instance: VkInstance, debugUtilsMessenger: VkDebugUtilsMessengerEXT, allocator: ptr VkAllocationCallbacks = nil) = + if debugUtilsMessenger != 0.VkDebugUtilsMessengerEXT: + vkDestroyDebugUtilsMessengerEXT(instance, debugUtilsMessenger, allocator) + +proc destroyInstance*(instance: VkInstance, allocator: ptr VkAllocationCallbacks = nil) = + if instance != 0.VkInstance: + vkDestroyInstance(instance, allocator) + +proc destroyShaderModule*(device: VkDevice, shaderModule: VkShaderModule, + allocator: ptr VkAllocationCallbacks = nil) = + if shaderModule != 0.VkShaderModule: + vkDestroyShaderModule(device, shaderModule, allocator) + +proc destroyFence*(device: VkDevice, fence: VkFence, allocator: ptr VkAllocationCallbacks = nil) = + if fence != 0.VkFence: + vkDestroyFence(device, fence, allocator) diff --git a/tests/test.nim b/tests/test.nim index 3caf3bd..24eb621 100644 --- a/tests/test.nim +++ b/tests/test.nim @@ -1,36 +1,8 @@ -import nimgl/glfw -from nimgl/vulkan import nil -from triangle import nil - -proc keyCallback(window: GLFWWindow, key: int32, scancode: int32, action: int32, mods: int32) {.cdecl.} = - if action == GLFW_PRESS and key == GLFWKey.Escape: - window.setWindowShouldClose(true) - -if isMainModule: - doAssert glfwInit() - - glfwWindowHint(GLFWClientApi, GLFWNoApi) - glfwWindowHint(GLFWResizable, GLFWFalse) - - var w = glfwCreateWindow(triangle.WIDTH, triangle.HEIGHT, "Vulkan Triangle") - if w == nil: - quit(-1) - - discard w.setKeyCallback(keyCallback) - - proc createSurface(instance: vulkan.VkInstance): vulkan.VkSurfaceKHR = - if glfwCreateWindowSurface(instance, w, nil, result.addr) != vulkan.VKSuccess: - quit("failed to create surface") - - var glfwExtensionCount: uint32 = 0 - var glfwExtensions: cstringArray - glfwExtensions = glfwGetRequiredInstanceExtensions(glfwExtensionCount.addr) - triangle.init(glfwExtensions, glfwExtensionCount, createSurface) - - while not w.windowShouldClose(): - glfwPollEvents() - triangle.tick() - - triangle.deinit() - w.destroyWindow() - glfwTerminate() +import vulkan + +when isMainModule: + doAssert VK_SUCCESS == VkSuccess + doAssert VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO == VkStructureType.InstanceCreateInfo + doAssert VK_IMAGE_LAYOUT_GENERAL == VkImageLayout.General + doAssert VK_TRUE == 1 + echo "ok" diff --git a/tests/test_wrapper.nim b/tests/test_wrapper.nim new file mode 100644 index 0000000..325ffef --- /dev/null +++ b/tests/test_wrapper.nim @@ -0,0 +1,12 @@ +import vulkan +import vulkan/wrapper + +when isMainModule: + var err: ref VulkanError + doAssert err.isNil + try: + raiseVkError("boom", VkSuccess) + doAssert false + except VulkanError as e: + doAssert e.res == VkSuccess + echo "ok" diff --git a/tests/triangle.nim b/tests/triangle.nim index 8e80e63..c596e4b 100644 --- a/tests/triangle.nim +++ b/tests/triangle.nim @@ -1,4 +1,4 @@ -import nimgl/vulkan +import vulkan import sets import bitops @@ -32,8 +32,6 @@ const HEIGHT* = 600 VK_NULL_HANDLE = 0 -loadVK_KHR_surface() -loadVK_KHR_swapchain() proc checkValidationLayers() = var layerCount: uint32 = 0 @@ -44,7 +42,7 @@ proc checkValidationLayers() = for validate in validationLayers: var found = false for layer in layers: - if cstring(layer.layerName.unsafeAddr) == validate: + if cast[cstring](layer.layerName.addr) == validate.cstring: found = true break if not found: @@ -87,28 +85,27 @@ proc createLogicalDevice(physicalDevice: VkPhysicalDevice, surface: VkSurfaceKHR let deviceQueueCreateInfo = newVkDeviceQueueCreateInfo( sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, queueFamilyIndex = queueFamily, - queueCount = 1, - pQueuePriorities = queuePriority.addr + queuePriorities = [queuePriority] ) queueCreateInfos.add(deviceQueueCreateInfo) var deviceFeatures = newSeq[VkPhysicalDeviceFeatures](1) - deviceExts = allocCStringArray(deviceExtensions) - deviceCreateInfo = newVkDeviceCreateInfo( - pQueueCreateInfos = queueCreateInfos[0].addr, - queueCreateInfoCount = queueCreateInfos.len.uint32, - pEnabledFeatures = deviceFeatures[0].addr, - enabledExtensionCount = deviceExtensions.len.uint32, - enabledLayerCount = 0, - ppEnabledLayerNames = nil, - ppEnabledExtensionNames = deviceExts - ) + deviceExts = newSeq[cstring](deviceExtensions.len) + + for i, ext in deviceExtensions: + deviceExts[i] = ext.cstring + + let deviceCreateInfo = newVkDeviceCreateInfo( + queueCreateInfos = queueCreateInfos, + enabledFeatures = deviceFeatures, + pEnabledLayerNames = [], + pEnabledExtensionNames = deviceExts + ) if vkCreateDevice(physicalDevice, deviceCreateInfo.addr, nil, result.addr) != VKSuccess: echo "failed to create logical device" - deallocCStringArray(deviceExts) vkGetDeviceQueue(result, indices.graphicsFamily, 0, graphicsQueue.addr) vkGetDeviceQueue(result, indices.presentFamily, 0, presentQueue.addr) @@ -121,7 +118,7 @@ proc checkDeviceExtensionSupport(pDevice: VkPhysicalDevice): bool = var requiredExts = deviceExtensions.toHashSet for ext in availableExts.mitems: - requiredExts.excl($ ext.extensionName.addr) + requiredExts.excl($cast[cstring](ext.extensionName.addr)) requiredExts.len == 0 proc querySwapChainSupport(pDevice: VkPhysicalDevice, surface: VkSurfaceKHR): SwapChainSupportDetails = @@ -159,18 +156,20 @@ proc isDeviceSuitable(pDevice: VkPhysicalDevice, surface: VkSurfaceKHR): bool = proc createInstance(glfwExtensions: cstringArray, glfwExtensionCount: uint32): VkInstance = var appInfo = newVkApplicationInfo( pApplicationName = "NimGL Vulkan Example", - applicationVersion = vkMakeVersion(1, 0, 0), + applicationVersion = vkMakeVersion(0, 1, 0, 0), pEngineName = "No Engine", - engineVersion = vkMakeVersion(1, 0, 0), + engineVersion = vkMakeVersion(0, 1, 0, 0), apiVersion = vkApiVersion1_1 ) + var extensionNames = newSeq[cstring](glfwExtensionCount.int) + for i in 0.., 2019 -import strutils, ./utils, httpClient, os, xmlparser, xmltree, streams, strformat, math, tables, algorithm, bitops +import std/[strutils, httpclient, os, xmlparser, xmltree, streams, strformat, math, tables, algorithm, bitops, sequtils] +import utils # local import type VkProc = object @@ -13,27 +14,58 @@ type VkStruct = object name: string members: seq[VkArg] + VkFlags = object + name: string + flagbits: string + bType: string var vkProcs: seq[VkProc] var vkStructs: seq[VkStruct] var vkStructureTypes: seq[string] +var vkFlagsTypes: seq[VkFlags] +var vkFlagBitsTypes: seq[string] +var vkHandleTypes: seq[VkFlags] + +proc camelCaseAscii*(s: string): string = + ## Converts snake_case to CamelCase + var L = s.len + while L > 0 and s[L-1] == '_': dec L + result = newStringOfCap(L) + var i = 0 + result.add s[i] + inc i + var flip = false + while i < L: + if s[i] == '_': + flip = true + else: + if flip: + result.add toUpperAscii(s[i]) + flip = false + else: result.add toLowerAscii(s[i]) + inc i proc translateType(s: string): string = result = s - result = result.replace("int64_t", "int64") - result = result.replace("int32_t", "int32") - result = result.replace("int16_t", "int16") - result = result.replace("int8_t", "int8") - result = result.replace("size_t", "uint") # uint matches pointer size just like size_t - result = result.replace("float", "float32") - result = result.replace("double", "float64") - result = result.replace("VK_DEFINE_HANDLE", "VkHandle") - result = result.replace("VK_DEFINE_NON_DISPATCHABLE_HANDLE", "VkNonDispatchableHandle") - result = result.replace("const ", "") - result = result.replace(" const", "") - result = result.replace("unsigned ", "u") - result = result.replace("signed ", "") - result = result.replace("struct ", "") + result = result.multiReplace({ + "int64_t": "int64", + "int32_t": "int32", + "int16_t": "int16", + "int8_t": "int8", + "size_t": "uint", # uint matches pointer size just like size_t + "float": "float32", + "double": "float64", + "VK_DEFINE_HANDLE": "VkHandle", + "VK_DEFINE_NON_DISPATCHABLE_HANDLE": "VkNonDispatchableHandle", + "const ": "", + " const": "", + "unsigned ": "u", + "signed ": "", + "struct ": "", + }) + + if result.startsWith('_'): + result = result.substr(1) if result.contains('*'): let levels = result.count('*') @@ -41,20 +73,20 @@ proc translateType(s: string): string = for i in 0..= s.members.high or not (s.members[i+1].isArray() or + s.members[i+1].isException)): + foundMany = false + output.add(&"): {s.name} =\n") + output.add(&" result = {s.name}(\n") + foundMany = false + for i, m in s.members: + output.add(" ") + if m.name.isCounter and + i < s.members.high and s.members[i+1].isArray(): + output.add(&"{m.name}: ") + if isSpecialCase: + output.add(&"if len({s.members[i+1].name.toArgName}) == 0: ") + output.add(&"len({s.members[i+2].name.toArgName}).{m.argType} ") + output.add(&"else: len({s.members[i+1].name.toArgName}).{m.argType},\n") + else: + output.add(&"len({s.members[i+1].name.toArgName}).{m.argType},\n") + foundMany = true + continue + if foundMany: + output.add(&"{m.name}: if len({m.name.toArgName}) == 0: nil ") + output.add(&"else: cast[{m.argType}]({m.name.toArgName}),\n") + else: + output.add(&"{m.name}: {m.name},\n") + if foundMany and (i >= s.members.high or not (s.members[i+1].isArray() or + s.members[i+1].isException)): + foundMany = false + output.add(" )\n") proc main() = if not os.fileExists("vk.xml"): let client = newHttpClient() - let glUrl = "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/master/xml/vk.xml" + let glUrl = "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/main/xml/vk.xml" client.downloadFile(glUrl, "vk.xml") - var output = srcHeader & "\n" - let file = newFileStream("vk.xml", fmRead) let xml = file.parseXml() @@ -490,10 +729,11 @@ proc main() = xml.genProcs(output) xml.genFeatures(output) xml.genExtensions(output) + genHelpers(output) output.add("\n" & vkInit) - writeFile("src/vulkan.nim", output) + writeFile("../src/vulkan.nim", output) if isMainModule: main() diff --git a/tools/utils.nim b/tools/utils.nim index 64789b0..044d434 100644 --- a/tools/utils.nim +++ b/tools/utils.nim @@ -1,6 +1,8 @@ # Written by Leonardo Mariscal , 2019 +import std/algorithm -const srcHeader* = """ +const + srcHeader* = """ # Written by Leonardo Mariscal , 2019 ## Vulkan Bindings @@ -8,7 +10,7 @@ const srcHeader* = """ ## WARNING: This is a generated file. Do not edit ## Any edits will be overwritten by the generator. -var vkGetProc: proc(procName: cstring): pointer {.cdecl.} +var vkGetProc: proc (procName: cstring): pointer {.cdecl.} var currInst: pointer = nil when not defined(vkCustomLoader): @@ -23,63 +25,85 @@ when not defined(vkCustomLoader): let vkHandleDLL = loadLib(vkDLL) if isNil(vkHandleDLL): - quit("could not load: " & vkDLL) - - let vkGetProcAddress = cast[proc(inst: pointer, s: cstring): pointer {.stdcall.}](symAddr(vkHandleDLL, "vkGetInstanceProcAddr")) - if vkGetProcAddress == nil: - quit("failed to load `vkGetInstanceProcAddr` from " & vkDLL) - - vkGetProc = proc(procName: cstring): pointer {.cdecl.} = - when defined(windows): - result = vkGetProcAddress(currInst, procName) - if result != nil: - return - result = symAddr(vkHandleDLL, procName) + raise newException(LibraryError, "could not load: " & vkDLL) + + let vkGetProcAddress = cast[proc (inst: pointer, s: cstring): pointer {.stdcall.}](checkedSymAddr(vkHandleDLL, "vkGetInstanceProcAddr")) + + vkGetProc = proc (procName: cstring): pointer {.cdecl.} = + result = vkGetProcAddress(currInst, procName) if result == nil: raiseInvalidLibrary(procName) -proc setVKGetProc*(getProc: proc(procName: cstring): pointer {.cdecl.}) = +proc setVKGetProc*(getProc: proc (procName: cstring): pointer {.cdecl.}) = vkGetProc = getProc type - VkHandle* = int64 - VkNonDispatchableHandle* = int64 - ANativeWindow = ptr object - CAMetalLayer = ptr object - AHardwareBuffer = ptr object + VkHandle* = uint + VkNonDispatchableHandle* = uint + ANativeWindow* = object + AHardwareBuffer* = object + CAMetalLayer* = object + MTLDevice_id* = object + MTLCommandQueue_id* = object + MTLBuffer_id* = object + MTLTexture_id* = object + MTLSharedEvent_id* = object + IOSurfaceRef* = object """ -const vkInit* = """ + vkInit* = """ var - vkCreateInstance*: proc(pCreateInfo: ptr VkInstanceCreateInfo , pAllocator: ptr VkAllocationCallbacks , pInstance: ptr VkInstance ): VkResult {.stdcall.} - vkEnumerateInstanceExtensionProperties*: proc(pLayerName: cstring , pPropertyCount: ptr uint32 , pProperties: ptr VkExtensionProperties ): VkResult {.stdcall.} - vkEnumerateInstanceLayerProperties*: proc(pPropertyCount: ptr uint32 , pProperties: ptr VkLayerProperties ): VkResult {.stdcall.} - vkEnumerateInstanceVersion*: proc(pApiVersion: ptr uint32 ): VkResult {.stdcall.} + vkCreateInstance*: proc (pCreateInfo: ptr VkInstanceCreateInfo, pAllocator: ptr VkAllocationCallbacks, pInstance: ptr VkInstance): VkResult {.stdcall.} + vkEnumerateInstanceExtensionProperties*: proc (pLayerName: cstring, pPropertyCount: ptr uint32, pProperties: ptr VkExtensionProperties): VkResult {.stdcall.} + vkEnumerateInstanceLayerProperties*: proc (pPropertyCount: ptr uint32, pProperties: ptr VkLayerProperties): VkResult {.stdcall.} + vkEnumerateInstanceVersion*: proc (pApiVersion: ptr uint32): VkResult {.stdcall.} proc vkPreload*(load1_1: bool = true) = - vkGetInstanceProcAddr = cast[proc(instance: VkInstance, pName: cstring ): PFN_vkVoidFunction {.stdcall.}](symAddr(vkHandleDLL, "vkGetInstanceProcAddr")) + vkGetInstanceProcAddr = cast[proc (instance: VkInstance, pName: cstring): PFN_vkVoidFunction {.stdcall.}](symAddr(vkHandleDLL, "vkGetInstanceProcAddr")) - vkCreateInstance = cast[proc(pCreateInfo: ptr VkInstanceCreateInfo , pAllocator: ptr VkAllocationCallbacks , pInstance: ptr VkInstance ): VkResult {.stdcall.}](vkGetProc("vkCreateInstance")) - vkEnumerateInstanceExtensionProperties = cast[proc(pLayerName: cstring , pPropertyCount: ptr uint32 , pProperties: ptr VkExtensionProperties ): VkResult {.stdcall.}](vkGetProc("vkEnumerateInstanceExtensionProperties")) - vkEnumerateInstanceLayerProperties = cast[proc(pPropertyCount: ptr uint32 , pProperties: ptr VkLayerProperties ): VkResult {.stdcall.}](vkGetProc("vkEnumerateInstanceLayerProperties")) + vkCreateInstance = cast[proc (pCreateInfo: ptr VkInstanceCreateInfo, pAllocator: ptr VkAllocationCallbacks, pInstance: ptr VkInstance): VkResult {.stdcall.}](vkGetProc("vkCreateInstance")) + vkEnumerateInstanceExtensionProperties = cast[proc (pLayerName: cstring, pPropertyCount: ptr uint32, pProperties: ptr VkExtensionProperties): VkResult {.stdcall.}](vkGetProc("vkEnumerateInstanceExtensionProperties")) + vkEnumerateInstanceLayerProperties = cast[proc (pPropertyCount: ptr uint32, pProperties: ptr VkLayerProperties): VkResult {.stdcall.}](vkGetProc("vkEnumerateInstanceLayerProperties")) if load1_1: - vkEnumerateInstanceVersion = cast[proc(pApiVersion: ptr uint32 ): VkResult {.stdcall.}](vkGetProc("vkEnumerateInstanceVersion")) + vkEnumerateInstanceVersion = cast[proc (pApiVersion: ptr uint32): VkResult {.stdcall.}](vkGetProc("vkEnumerateInstanceVersion")) -proc vkInit*(instance: VkInstance, load1_0: bool = true, load1_1: bool = true): bool = +proc vkInit*(instance: VkInstance, load1_0 = true, load1_1: bool = true, load1_2 = true, load1_3: bool = true) = currInst = cast[pointer](instance) + if currInst == nil: + raise newException(NilAccessDefect, "Instance is nil") if load1_0: vkLoad1_0() + if load1_1: + vkLoad1_1() + if load1_2: + vkLoad1_2() when not defined(macosx): - if load1_1: - vkLoad1_1() - return true + if load1_3: + vkLoad1_3() """ -let keywords* = ["addr", "and", "as", "asm", "bind", "block", "break", "case", "cast", "concept", - "const", "continue", "converter", "defer", "discard", "distinct", "div", "do", - "elif", "else", "end", "enum", "except", "export", "finally", "for", "from", "func", - "if", "import", "in", "include", "interface", "is", "isnot", "iterator", "let", - "macro", "method", "mixin", "mod", "nil", "not", "notin", "object", "of", "or", - "out", "proc", "ptr", "raise", "ref", "return", "shl", "shr", "static", "template", - "try", "tuple", "type", "using", "var", "when", "while", "xor", "yield"] + keywords = ["addr", "and", "as", "asm", + "bind", "block", "break", + "case", "cast", "concept", "const", "continue", "converter", + "defer", "discard", "distinct", "div", "do", + "elif", "else", "end", "enum", "except", "export", + "finally", "for", "from", "func", + "if", "import", "in", "include", "interface", "is", "isnot", "iterator", + "let", + "macro", "method", "mixin", "mod", + "nil", "not", "notin", + "object", "of", "or", "out", + "proc", "ptr", + "raise", "ref", "return", + "shl", "shr", "static", + "template", "try", "tuple", "type", + "using", + "var", + "when", "while", + "xor", + "yield"] + +proc isKeyword*(s: string): bool {.inline.} = + ## Checks if an indentifier is a Nim keyword + binarySearch(keywords, s) >= 0 diff --git a/vulkan.nimble b/vulkan.nimble index aa5b648..4c0095e 100644 --- a/vulkan.nimble +++ b/vulkan.nimble @@ -1,6 +1,6 @@ # Package -version = "1.2.1" +version = "1.3.295" author = "Leonardo Mariscal" description = "Vulkan bindings for Nim" license = "MIT" @@ -8,12 +8,10 @@ srcDir = "src" skipDirs = @["tests"] # Dependencies - requires "nim >= 1.0.0" -task gen, "Generate bindings from source": - exec("nim c -d:ssl -r tools/generator.nim") +feature "examples": + requires "glfw" + requires "chroma" + requires "sdl2" -task test, "Create basic triangle with Vulkan and GLFW": - requires "nimgl@#1.0" # Please https://github.com/nim-lang/nimble/issues/482 - exec("nim c -r tests/test.nim")