// SPDX-License-Identifier: Unlicense OR MIT

//go:build linux || freebsd
// +build linux freebsd

package vk

/*
#cgo linux freebsd LDFLAGS: -ldl
#cgo freebsd CFLAGS: -I/usr/local/include
#cgo CFLAGS: -Werror -Werror=return-type

#define VK_NO_PROTOTYPES 1
#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;
#include <vulkan/vulkan.h>
#define __USE_GNU
#include <dlfcn.h>
#include <stdlib.h>

static VkResult vkCreateInstance(PFN_vkCreateInstance f, VkInstanceCreateInfo pCreateInfo, const VkAllocationCallbacks *pAllocator, VkInstance *pInstance) {
	return f(&pCreateInfo, pAllocator, pInstance);
}

static void vkDestroyInstance(PFN_vkDestroyInstance f, VkInstance instance, const VkAllocationCallbacks *pAllocator) {
	f(instance, pAllocator);
}

static VkResult vkEnumeratePhysicalDevices(PFN_vkEnumeratePhysicalDevices f, VkInstance instance, uint32_t *pPhysicalDeviceCount, VkPhysicalDevice *pPhysicalDevices) {
	return f(instance, pPhysicalDeviceCount, pPhysicalDevices);
}

static void vkGetPhysicalDeviceQueueFamilyProperties(PFN_vkGetPhysicalDeviceQueueFamilyProperties f, VkPhysicalDevice physicalDevice, uint32_t *pQueueFamilyPropertyCount, VkQueueFamilyProperties *pQueueFamilyProperties) {
	f(physicalDevice, pQueueFamilyPropertyCount, pQueueFamilyProperties);
}

static void vkGetPhysicalDeviceFormatProperties(PFN_vkGetPhysicalDeviceFormatProperties f, VkPhysicalDevice physicalDevice, VkFormat format, VkFormatProperties *pFormatProperties) {
	f(physicalDevice, format, pFormatProperties);
}

static VkResult vkCreateDevice(PFN_vkCreateDevice f, VkPhysicalDevice physicalDevice, VkDeviceCreateInfo pCreateInfo, VkDeviceQueueCreateInfo qinf, const VkAllocationCallbacks *pAllocator, VkDevice *pDevice) {
	pCreateInfo.pQueueCreateInfos = &qinf;
	return f(physicalDevice, &pCreateInfo, pAllocator, pDevice);
}

static void vkDestroyDevice(PFN_vkDestroyDevice f, VkDevice device, const VkAllocationCallbacks *pAllocator) {
	f(device, pAllocator);
}

static void vkGetDeviceQueue(PFN_vkGetDeviceQueue f, VkDevice device, uint32_t queueFamilyIndex, uint32_t queueIndex, VkQueue *pQueue) {
	f(device, queueFamilyIndex, queueIndex, pQueue);
}

static VkResult vkCreateImageView(PFN_vkCreateImageView f, VkDevice device, const VkImageViewCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkImageView *pView) {
	return f(device, pCreateInfo, pAllocator, pView);
}

static void vkDestroyImageView(PFN_vkDestroyImageView f, VkDevice device, VkImageView imageView, const VkAllocationCallbacks *pAllocator) {
	f(device, imageView, pAllocator);
}

static VkResult vkCreateFramebuffer(PFN_vkCreateFramebuffer f, VkDevice device, VkFramebufferCreateInfo pCreateInfo, const VkAllocationCallbacks *pAllocator, VkFramebuffer *pFramebuffer) {
	return f(device, &pCreateInfo, pAllocator, pFramebuffer);
}

static void vkDestroyFramebuffer(PFN_vkDestroyFramebuffer f, VkDevice device, VkFramebuffer framebuffer, const VkAllocationCallbacks *pAllocator) {
	f(device, framebuffer, pAllocator);
}

static VkResult vkDeviceWaitIdle(PFN_vkDeviceWaitIdle f, VkDevice device) {
	return f(device);
}

static VkResult vkQueueWaitIdle(PFN_vkQueueWaitIdle f, VkQueue queue) {
	return f(queue);
}

static VkResult vkCreateSemaphore(PFN_vkCreateSemaphore f, VkDevice device, const VkSemaphoreCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSemaphore *pSemaphore) {
	return f(device, pCreateInfo, pAllocator, pSemaphore);
}

static void vkDestroySemaphore(PFN_vkDestroySemaphore f, VkDevice device, VkSemaphore semaphore, const VkAllocationCallbacks *pAllocator) {
	f(device, semaphore, pAllocator);
}

static VkResult vkCreateRenderPass(PFN_vkCreateRenderPass f, VkDevice device, VkRenderPassCreateInfo pCreateInfo, VkSubpassDescription subpassInf, const VkAllocationCallbacks *pAllocator, VkRenderPass *pRenderPass) {
	pCreateInfo.pSubpasses = &subpassInf;
	return f(device, &pCreateInfo, pAllocator, pRenderPass);
}

static void vkDestroyRenderPass(PFN_vkDestroyRenderPass f, VkDevice device, VkRenderPass renderPass, const VkAllocationCallbacks *pAllocator) {
	f(device, renderPass, pAllocator);
}

static VkResult vkCreateCommandPool(PFN_vkCreateCommandPool f, VkDevice device, const VkCommandPoolCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkCommandPool *pCommandPool) {
	return f(device, pCreateInfo, pAllocator, pCommandPool);
}

static void vkDestroyCommandPool(PFN_vkDestroyCommandPool f, VkDevice device, VkCommandPool commandPool, const VkAllocationCallbacks *pAllocator) {
	f(device, commandPool, pAllocator);
}

static VkResult vkAllocateCommandBuffers(PFN_vkAllocateCommandBuffers f, VkDevice device, const VkCommandBufferAllocateInfo *pAllocateInfo, VkCommandBuffer *pCommandBuffers) {
	return f(device, pAllocateInfo, pCommandBuffers);
}

static void vkFreeCommandBuffers(PFN_vkFreeCommandBuffers f, VkDevice  device, VkCommandPool commandPool, uint32_t commandBufferCount, const VkCommandBuffer *pCommandBuffers) {
	f(device, commandPool, commandBufferCount, pCommandBuffers);
}

static VkResult vkBeginCommandBuffer(PFN_vkBeginCommandBuffer f, VkCommandBuffer commandBuffer, VkCommandBufferBeginInfo pBeginInfo) {
	return f(commandBuffer, &pBeginInfo);
}

static VkResult vkEndCommandBuffer(PFN_vkEndCommandBuffer f, VkCommandBuffer commandBuffer) {
	return f(commandBuffer);
}

static VkResult vkQueueSubmit(PFN_vkQueueSubmit f, VkQueue queue, VkSubmitInfo pSubmits, VkFence fence) {
	return f(queue, 1, &pSubmits, fence);
}

static void vkCmdBeginRenderPass(PFN_vkCmdBeginRenderPass f, VkCommandBuffer commandBuffer, VkRenderPassBeginInfo pRenderPassBegin, VkSubpassContents contents) {
	f(commandBuffer, &pRenderPassBegin, contents);
}

static void vkCmdEndRenderPass(PFN_vkCmdEndRenderPass f, VkCommandBuffer commandBuffer) {
	f(commandBuffer);
}

static void vkCmdCopyBuffer(PFN_vkCmdCopyBuffer f, VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer, uint32_t regionCount, const VkBufferCopy *pRegions) {
	f(commandBuffer, srcBuffer, dstBuffer, regionCount, pRegions);
}

static void vkCmdCopyBufferToImage(PFN_vkCmdCopyBufferToImage f, VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkBufferImageCopy *pRegions) {
	f(commandBuffer, srcBuffer, dstImage, dstImageLayout, regionCount, pRegions);
}

static void vkCmdPipelineBarrier(PFN_vkCmdPipelineBarrier f, VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount, const VkMemoryBarrier *pMemoryBarriers, uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier *pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier *pImageMemoryBarriers) {
	f(commandBuffer, srcStageMask, dstStageMask, dependencyFlags, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers);
}

static void vkCmdPushConstants(PFN_vkCmdPushConstants f, VkCommandBuffer commandBuffer, VkPipelineLayout layout, VkShaderStageFlags stageFlags, uint32_t offset, uint32_t size, const void *pValues) {
	f(commandBuffer, layout, stageFlags, offset, size, pValues);
}

static void vkCmdBindPipeline(PFN_vkCmdBindPipeline f, VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipeline pipeline) {
	f(commandBuffer, pipelineBindPoint, pipeline);
}

static void vkCmdBindVertexBuffers(PFN_vkCmdBindVertexBuffers f, VkCommandBuffer commandBuffer, uint32_t firstBinding, uint32_t bindingCount, const VkBuffer *pBuffers, const VkDeviceSize *pOffsets) {
	f(commandBuffer, firstBinding, bindingCount, pBuffers, pOffsets);
}

static void vkCmdSetViewport(PFN_vkCmdSetViewport f, VkCommandBuffer commandBuffer, uint32_t firstViewport, uint32_t viewportCount, const VkViewport *pViewports) {
	f(commandBuffer, firstViewport, viewportCount, pViewports);
}

static void vkCmdBindIndexBuffer(PFN_vkCmdBindIndexBuffer f, VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset, VkIndexType indexType) {
	f(commandBuffer, buffer, offset, indexType);
}

static void vkCmdDraw(PFN_vkCmdDraw f, VkCommandBuffer commandBuffer, uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance) {
	f(commandBuffer, vertexCount, instanceCount, firstVertex, firstInstance);
}

static void vkCmdDrawIndexed(PFN_vkCmdDrawIndexed f, VkCommandBuffer commandBuffer, uint32_t indexCount, uint32_t instanceCount, uint32_t firstIndex, int32_t vertexOffset, uint32_t firstInstance) {
	f(commandBuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstance);
}

static void vkCmdBindDescriptorSets(PFN_vkCmdBindDescriptorSets f, VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout, uint32_t firstSet, uint32_t descriptorSetCount, const VkDescriptorSet *pDescriptorSets, uint32_t dynamicOffsetCount, const uint32_t *pDynamicOffsets) {
	f(commandBuffer, pipelineBindPoint, layout, firstSet, descriptorSetCount, pDescriptorSets, dynamicOffsetCount, pDynamicOffsets);
}

static void vkCmdCopyImageToBuffer(PFN_vkCmdCopyImageToBuffer f, VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkBuffer dstBuffer, uint32_t regionCount, const VkBufferImageCopy *pRegions) {
	f(commandBuffer, srcImage, srcImageLayout, dstBuffer, regionCount, pRegions);
}

static void vkCmdDispatch(PFN_vkCmdDispatch f, VkCommandBuffer commandBuffer, uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ) {
	f(commandBuffer, groupCountX, groupCountY, groupCountZ);
}

static VkResult vkCreateImage(PFN_vkCreateImage f, VkDevice device, const VkImageCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkImage *pImage) {
	return f(device, pCreateInfo, pAllocator, pImage);
}

static void vkDestroyImage(PFN_vkDestroyImage f, VkDevice device, VkImage image, const VkAllocationCallbacks *pAllocator) {
	f(device, image, pAllocator);
}

static void vkGetImageMemoryRequirements(PFN_vkGetImageMemoryRequirements f, VkDevice device, VkImage image, VkMemoryRequirements *pMemoryRequirements) {
	f(device, image, pMemoryRequirements);
}

static VkResult vkAllocateMemory(PFN_vkAllocateMemory f, VkDevice device, const VkMemoryAllocateInfo *pAllocateInfo, const VkAllocationCallbacks *pAllocator, VkDeviceMemory *pMemory) {
	return f(device, pAllocateInfo, pAllocator, pMemory);
}

static VkResult vkBindImageMemory(PFN_vkBindImageMemory f, VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset) {
	return f(device, image, memory, memoryOffset);
}

static void vkFreeMemory(PFN_vkFreeMemory f, VkDevice device, VkDeviceMemory memory, const VkAllocationCallbacks *pAllocator) {
	f(device, memory, pAllocator);
}

static void vkGetPhysicalDeviceMemoryProperties(PFN_vkGetPhysicalDeviceMemoryProperties f, VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties *pMemoryProperties) {
	f(physicalDevice, pMemoryProperties);
}

static VkResult vkCreateSampler(PFN_vkCreateSampler f,VkDevice device, const VkSamplerCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSampler *pSampler) {
	return f(device, pCreateInfo, pAllocator, pSampler);
}

static void vkDestroySampler(PFN_vkDestroySampler f, VkDevice device, VkSampler sampler, const VkAllocationCallbacks *pAllocator) {
	f(device, sampler, pAllocator);
}

static VkResult vkCreateBuffer(PFN_vkCreateBuffer f, VkDevice device, const VkBufferCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkBuffer *pBuffer) {
	return f(device, pCreateInfo, pAllocator, pBuffer);
}

static void vkDestroyBuffer(PFN_vkDestroyBuffer f, VkDevice device, VkBuffer buffer, const VkAllocationCallbacks *pAllocator) {
	f(device, buffer, pAllocator);
}

static void vkGetBufferMemoryRequirements(PFN_vkGetBufferMemoryRequirements f, VkDevice device, VkBuffer buffer, VkMemoryRequirements *pMemoryRequirements) {
	f(device, buffer, pMemoryRequirements);
}

static VkResult vkBindBufferMemory(PFN_vkBindBufferMemory f, VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize memoryOffset) {
	return f(device, buffer, memory, memoryOffset);
}

static VkResult vkCreateShaderModule(PFN_vkCreateShaderModule f, VkDevice device, VkShaderModuleCreateInfo pCreateInfo, const VkAllocationCallbacks *pAllocator, VkShaderModule *pShaderModule) {
	return f(device, &pCreateInfo, pAllocator, pShaderModule);
}

static void vkDestroyShaderModule(PFN_vkDestroyShaderModule f, VkDevice device, VkShaderModule shaderModule, const VkAllocationCallbacks *pAllocator) {
	f(device, shaderModule, pAllocator);
}

static VkResult vkCreateGraphicsPipelines(PFN_vkCreateGraphicsPipelines f, VkDevice device, VkPipelineCache pipelineCache, VkGraphicsPipelineCreateInfo pCreateInfo, VkPipelineDynamicStateCreateInfo dynInf, VkPipelineColorBlendStateCreateInfo blendInf, VkPipelineVertexInputStateCreateInfo vertexInf, VkPipelineViewportStateCreateInfo viewportInf, const VkAllocationCallbacks *pAllocator, VkPipeline *pPipelines) {
	pCreateInfo.pDynamicState = &dynInf;
	pCreateInfo.pViewportState = &viewportInf;
	pCreateInfo.pColorBlendState = &blendInf;
	pCreateInfo.pVertexInputState = &vertexInf;
	return f(device, pipelineCache, 1, &pCreateInfo, pAllocator, pPipelines);
}

static void vkDestroyPipeline(PFN_vkDestroyPipeline f, VkDevice device, VkPipeline pipeline, const VkAllocationCallbacks *pAllocator) {
	f(device, pipeline, pAllocator);
}

static VkResult vkCreatePipelineLayout(PFN_vkCreatePipelineLayout f, VkDevice device, VkPipelineLayoutCreateInfo pCreateInfo, const VkAllocationCallbacks *pAllocator, VkPipelineLayout *pPipelineLayout) {
	return f(device, &pCreateInfo, pAllocator, pPipelineLayout);
}

static void vkDestroyPipelineLayout(PFN_vkDestroyPipelineLayout f, VkDevice device, VkPipelineLayout pipelineLayout, const VkAllocationCallbacks *pAllocator) {
	f(device, pipelineLayout, pAllocator);
}

static VkResult vkCreateDescriptorSetLayout(PFN_vkCreateDescriptorSetLayout f, VkDevice device, VkDescriptorSetLayoutCreateInfo pCreateInfo, const VkAllocationCallbacks *pAllocator, VkDescriptorSetLayout *pSetLayout) {
	return f(device, &pCreateInfo, pAllocator, pSetLayout);
}

static void vkDestroyDescriptorSetLayout(PFN_vkDestroyDescriptorSetLayout f, VkDevice device, VkDescriptorSetLayout descriptorSetLayout, const VkAllocationCallbacks *pAllocator) {
	f(device, descriptorSetLayout, pAllocator);
}

static VkResult vkMapMemory(PFN_vkMapMemory f, VkDevice device, VkDeviceMemory memory, VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void **ppData) {
	return f(device, memory, offset, size, flags, ppData);
}

static void vkUnmapMemory(PFN_vkUnmapMemory f, VkDevice device, VkDeviceMemory memory) {
	f(device, memory);
}

static VkResult vkResetCommandBuffer(PFN_vkResetCommandBuffer f, VkCommandBuffer commandBuffer, VkCommandBufferResetFlags flags) {
	return f(commandBuffer, flags);
}

static VkResult vkCreateDescriptorPool(PFN_vkCreateDescriptorPool f, VkDevice device, VkDescriptorPoolCreateInfo pCreateInfo, const VkAllocationCallbacks *pAllocator, VkDescriptorPool *pDescriptorPool) {
	return f(device, &pCreateInfo, pAllocator, pDescriptorPool);
}

static void vkDestroyDescriptorPool(PFN_vkDestroyDescriptorPool f, VkDevice device, VkDescriptorPool descriptorPool, const VkAllocationCallbacks *pAllocator) {
	f(device, descriptorPool, pAllocator);
}

static VkResult vkAllocateDescriptorSets(PFN_vkAllocateDescriptorSets f, VkDevice device, VkDescriptorSetAllocateInfo pAllocateInfo, VkDescriptorSet *pDescriptorSets) {
	return f(device, &pAllocateInfo, pDescriptorSets);
}

static VkResult vkFreeDescriptorSets(PFN_vkFreeDescriptorSets f, VkDevice device, VkDescriptorPool descriptorPool, uint32_t descriptorSetCount, const VkDescriptorSet *pDescriptorSets) {
	return f(device, descriptorPool, descriptorSetCount, pDescriptorSets);
}

static void vkUpdateDescriptorSets(PFN_vkUpdateDescriptorSets f, VkDevice device, VkWriteDescriptorSet pDescriptorWrite, uint32_t descriptorCopyCount, const VkCopyDescriptorSet *pDescriptorCopies) {
	f(device, 1, &pDescriptorWrite, descriptorCopyCount, pDescriptorCopies);
}

static VkResult vkResetDescriptorPool(PFN_vkResetDescriptorPool f, VkDevice device, VkDescriptorPool descriptorPool, VkDescriptorPoolResetFlags flags) {
	return f(device, descriptorPool, flags);
}

static void vkCmdCopyImage(PFN_vkCmdCopyImage f, VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout, VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount, const VkImageCopy *pRegions) {
	f(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions);
}

static VkResult vkCreateComputePipelines(PFN_vkCreateComputePipelines f, VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount, const VkComputePipelineCreateInfo *pCreateInfos, const VkAllocationCallbacks *pAllocator, VkPipeline *pPipelines) {
	return f(device, pipelineCache, createInfoCount, pCreateInfos, pAllocator, pPipelines);
}

static VkResult vkCreateFence(PFN_vkCreateFence f, VkDevice device, const VkFenceCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkFence *pFence) {
	return f(device, pCreateInfo, pAllocator, pFence);
}

static void vkDestroyFence(PFN_vkDestroyFence f, VkDevice device, VkFence fence, const VkAllocationCallbacks *pAllocator) {
	f(device, fence, pAllocator);
}

static VkResult vkWaitForFences(PFN_vkWaitForFences f, VkDevice device, uint32_t fenceCount, const VkFence *pFences, VkBool32 waitAll, uint64_t timeout) {
	return f(device, fenceCount, pFences, waitAll, timeout);
}

static VkResult vkResetFences(PFN_vkResetFences f, VkDevice device, uint32_t fenceCount, const VkFence *pFences) {
	return f(device, fenceCount, pFences);
}

static void vkGetPhysicalDeviceProperties(PFN_vkGetPhysicalDeviceProperties f, VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties *pProperties) {
	f(physicalDevice, pProperties);
}

static VkResult vkGetPhysicalDeviceSurfaceSupportKHR(PFN_vkGetPhysicalDeviceSurfaceSupportKHR f, VkPhysicalDevice physicalDevice, uint32_t queueFamilyIndex, VkSurfaceKHR surface, VkBool32 *pSupported) {
	return f(physicalDevice, queueFamilyIndex, surface, pSupported);
}

static void vkDestroySurfaceKHR(PFN_vkDestroySurfaceKHR f, VkInstance instance, VkSurfaceKHR surface, const VkAllocationCallbacks *pAllocator) {
	f(instance, surface, pAllocator);
}

static VkResult vkGetPhysicalDeviceSurfaceFormatsKHR(PFN_vkGetPhysicalDeviceSurfaceFormatsKHR f, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t *pSurfaceFormatCount, VkSurfaceFormatKHR *pSurfaceFormats) {
	return f(physicalDevice, surface, pSurfaceFormatCount, pSurfaceFormats);
}

static VkResult vkGetPhysicalDeviceSurfacePresentModesKHR(PFN_vkGetPhysicalDeviceSurfacePresentModesKHR f, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t *pPresentModeCount, VkPresentModeKHR *pPresentModes) {
	return f(physicalDevice, surface, pPresentModeCount, pPresentModes);
}

static VkResult vkGetPhysicalDeviceSurfaceCapabilitiesKHR(PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR f, VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR *pSurfaceCapabilities) {
	return f(physicalDevice, surface, pSurfaceCapabilities);
}

static VkResult vkCreateSwapchainKHR(PFN_vkCreateSwapchainKHR f, VkDevice device, const VkSwapchainCreateInfoKHR *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkSwapchainKHR *pSwapchain) {
	return f(device, pCreateInfo, pAllocator, pSwapchain);
}

static void vkDestroySwapchainKHR(PFN_vkDestroySwapchainKHR f, VkDevice device, VkSwapchainKHR swapchain, const VkAllocationCallbacks *pAllocator) {
	f(device, swapchain, pAllocator);
}

static VkResult vkGetSwapchainImagesKHR(PFN_vkGetSwapchainImagesKHR f, VkDevice device, VkSwapchainKHR swapchain, uint32_t *pSwapchainImageCount, VkImage *pSwapchainImages) {
	return f(device, swapchain, pSwapchainImageCount, pSwapchainImages);
}

// indexAndResult holds both an integer and a result returned by value, to
// avoid Go heap allocation of the integer with Vulkan's return style.
struct intAndResult {
	uint32_t uint;
	VkResult res;
};

static struct intAndResult vkAcquireNextImageKHR(PFN_vkAcquireNextImageKHR f, VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore, VkFence fence) {
	struct intAndResult res;
	res.res = f(device, swapchain, timeout, semaphore, fence, &res.uint);
	return res;
}

static VkResult vkQueuePresentKHR(PFN_vkQueuePresentKHR f, VkQueue queue, const VkPresentInfoKHR pPresentInfo) {
	return f(queue, &pPresentInfo);
}
*/
import "C"
import (
	"errors"
	"fmt"
	"image"
	"math"
	"reflect"
	"runtime"
	"sync"
	"unsafe"
)

type (
	AttachmentLoadOp      = C.VkAttachmentLoadOp
	AccessFlags           = C.VkAccessFlags
	BlendFactor           = C.VkBlendFactor
	Buffer                = C.VkBuffer
	BufferImageCopy       = C.VkBufferImageCopy
	BufferMemoryBarrier   = C.VkBufferMemoryBarrier
	BufferUsageFlags      = C.VkBufferUsageFlags
	CommandPool           = C.VkCommandPool
	CommandBuffer         = C.VkCommandBuffer
	DependencyFlags       = C.VkDependencyFlags
	DescriptorPool        = C.VkDescriptorPool
	DescriptorPoolSize    = C.VkDescriptorPoolSize
	DescriptorSet         = C.VkDescriptorSet
	DescriptorSetLayout   = C.VkDescriptorSetLayout
	DescriptorType        = C.VkDescriptorType
	Device                = C.VkDevice
	DeviceMemory          = C.VkDeviceMemory
	DeviceSize            = C.VkDeviceSize
	Fence                 = C.VkFence
	Queue                 = C.VkQueue
	IndexType             = C.VkIndexType
	Image                 = C.VkImage
	ImageCopy             = C.VkImageCopy
	ImageLayout           = C.VkImageLayout
	ImageMemoryBarrier    = C.VkImageMemoryBarrier
	ImageUsageFlags       = C.VkImageUsageFlags
	ImageView             = C.VkImageView
	Instance              = C.VkInstance
	Filter                = C.VkFilter
	Format                = C.VkFormat
	FormatFeatureFlags    = C.VkFormatFeatureFlags
	Framebuffer           = C.VkFramebuffer
	MemoryBarrier         = C.VkMemoryBarrier
	MemoryPropertyFlags   = C.VkMemoryPropertyFlags
	Pipeline              = C.VkPipeline
	PipelineBindPoint     = C.VkPipelineBindPoint
	PipelineLayout        = C.VkPipelineLayout
	PipelineStageFlags    = C.VkPipelineStageFlags
	PhysicalDevice        = C.VkPhysicalDevice
	PrimitiveTopology     = C.VkPrimitiveTopology
	PushConstantRange     = C.VkPushConstantRange
	QueueFamilyProperties = C.VkQueueFamilyProperties
	QueueFlags            = C.VkQueueFlags
	RenderPass            = C.VkRenderPass
	Sampler               = C.VkSampler
	SamplerMipmapMode     = C.VkSamplerMipmapMode
	Semaphore             = C.VkSemaphore
	ShaderModule          = C.VkShaderModule
	ShaderStageFlags      = C.VkShaderStageFlags
	SubpassDependency     = C.VkSubpassDependency
	Viewport              = C.VkViewport
	WriteDescriptorSet    = C.VkWriteDescriptorSet

	Surface             = C.VkSurfaceKHR
	SurfaceCapabilities = C.VkSurfaceCapabilitiesKHR

	Swapchain = C.VkSwapchainKHR
)

type VertexInputBindingDescription struct {
	Binding int
	Stride  int
}

type VertexInputAttributeDescription struct {
	Location int
	Binding  int
	Format   Format
	Offset   int
}

type DescriptorSetLayoutBinding struct {
	Binding        int
	DescriptorType DescriptorType
	StageFlags     ShaderStageFlags
}

type Error C.VkResult

const (
	FORMAT_R8G8B8A8_UNORM      Format = C.VK_FORMAT_R8G8B8A8_UNORM
	FORMAT_B8G8R8A8_SRGB       Format = C.VK_FORMAT_B8G8R8A8_SRGB
	FORMAT_R8G8B8A8_SRGB       Format = C.VK_FORMAT_R8G8B8A8_SRGB
	FORMAT_R16_SFLOAT          Format = C.VK_FORMAT_R16_SFLOAT
	FORMAT_R32_SFLOAT          Format = C.VK_FORMAT_R32_SFLOAT
	FORMAT_R32G32_SFLOAT       Format = C.VK_FORMAT_R32G32_SFLOAT
	FORMAT_R32G32B32_SFLOAT    Format = C.VK_FORMAT_R32G32B32_SFLOAT
	FORMAT_R32G32B32A32_SFLOAT Format = C.VK_FORMAT_R32G32B32A32_SFLOAT

	FORMAT_FEATURE_COLOR_ATTACHMENT_BIT       FormatFeatureFlags = C.VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT
	FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT FormatFeatureFlags = C.VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT
	FORMAT_FEATURE_SAMPLED_IMAGE_BIT          FormatFeatureFlags = C.VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT

	IMAGE_USAGE_SAMPLED_BIT          ImageUsageFlags = C.VK_IMAGE_USAGE_SAMPLED_BIT
	IMAGE_USAGE_COLOR_ATTACHMENT_BIT ImageUsageFlags = C.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
	IMAGE_USAGE_STORAGE_BIT          ImageUsageFlags = C.VK_IMAGE_USAGE_STORAGE_BIT
	IMAGE_USAGE_TRANSFER_DST_BIT     ImageUsageFlags = C.VK_IMAGE_USAGE_TRANSFER_DST_BIT
	IMAGE_USAGE_TRANSFER_SRC_BIT     ImageUsageFlags = C.VK_IMAGE_USAGE_TRANSFER_SRC_BIT

	FILTER_NEAREST Filter = C.VK_FILTER_NEAREST
	FILTER_LINEAR  Filter = C.VK_FILTER_LINEAR

	ATTACHMENT_LOAD_OP_CLEAR     AttachmentLoadOp = C.VK_ATTACHMENT_LOAD_OP_CLEAR
	ATTACHMENT_LOAD_OP_DONT_CARE AttachmentLoadOp = C.VK_ATTACHMENT_LOAD_OP_DONT_CARE
	ATTACHMENT_LOAD_OP_LOAD      AttachmentLoadOp = C.VK_ATTACHMENT_LOAD_OP_LOAD

	IMAGE_LAYOUT_UNDEFINED                ImageLayout = C.VK_IMAGE_LAYOUT_UNDEFINED
	IMAGE_LAYOUT_PRESENT_SRC_KHR          ImageLayout = C.VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
	IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ImageLayout = C.VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
	IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL ImageLayout = C.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
	IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL     ImageLayout = C.VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
	IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL     ImageLayout = C.VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
	IMAGE_LAYOUT_GENERAL                  ImageLayout = C.VK_IMAGE_LAYOUT_GENERAL

	BUFFER_USAGE_TRANSFER_DST_BIT   BufferUsageFlags = C.VK_BUFFER_USAGE_TRANSFER_DST_BIT
	BUFFER_USAGE_TRANSFER_SRC_BIT   BufferUsageFlags = C.VK_BUFFER_USAGE_TRANSFER_SRC_BIT
	BUFFER_USAGE_UNIFORM_BUFFER_BIT BufferUsageFlags = C.VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
	BUFFER_USAGE_STORAGE_BUFFER_BIT BufferUsageFlags = C.VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
	BUFFER_USAGE_INDEX_BUFFER_BIT   BufferUsageFlags = C.VK_BUFFER_USAGE_INDEX_BUFFER_BIT
	BUFFER_USAGE_VERTEX_BUFFER_BIT  BufferUsageFlags = C.VK_BUFFER_USAGE_VERTEX_BUFFER_BIT

	ERROR_OUT_OF_DATE_KHR  = Error(C.VK_ERROR_OUT_OF_DATE_KHR)
	ERROR_SURFACE_LOST_KHR = Error(C.VK_ERROR_SURFACE_LOST_KHR)
	ERROR_DEVICE_LOST      = Error(C.VK_ERROR_DEVICE_LOST)
	SUBOPTIMAL_KHR         = Error(C.VK_SUBOPTIMAL_KHR)

	BLEND_FACTOR_ZERO                BlendFactor = C.VK_BLEND_FACTOR_ZERO
	BLEND_FACTOR_ONE                 BlendFactor = C.VK_BLEND_FACTOR_ONE
	BLEND_FACTOR_ONE_MINUS_SRC_ALPHA BlendFactor = C.VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA
	BLEND_FACTOR_DST_COLOR           BlendFactor = C.VK_BLEND_FACTOR_DST_COLOR

	PRIMITIVE_TOPOLOGY_TRIANGLE_LIST  PrimitiveTopology = C.VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST
	PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP PrimitiveTopology = C.VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP

	SHADER_STAGE_VERTEX_BIT   ShaderStageFlags = C.VK_SHADER_STAGE_VERTEX_BIT
	SHADER_STAGE_FRAGMENT_BIT ShaderStageFlags = C.VK_SHADER_STAGE_FRAGMENT_BIT
	SHADER_STAGE_COMPUTE_BIT  ShaderStageFlags = C.VK_SHADER_STAGE_COMPUTE_BIT

	DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER DescriptorType = C.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
	DESCRIPTOR_TYPE_UNIFORM_BUFFER         DescriptorType = C.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
	DESCRIPTOR_TYPE_STORAGE_BUFFER         DescriptorType = C.VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
	DESCRIPTOR_TYPE_STORAGE_IMAGE          DescriptorType = C.VK_DESCRIPTOR_TYPE_STORAGE_IMAGE

	MEMORY_PROPERTY_DEVICE_LOCAL_BIT  MemoryPropertyFlags = C.VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
	MEMORY_PROPERTY_HOST_VISIBLE_BIT  MemoryPropertyFlags = C.VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
	MEMORY_PROPERTY_HOST_COHERENT_BIT MemoryPropertyFlags = C.VK_MEMORY_PROPERTY_HOST_COHERENT_BIT

	DEPENDENCY_BY_REGION_BIT DependencyFlags = C.VK_DEPENDENCY_BY_REGION_BIT

	PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT PipelineStageFlags = C.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
	PIPELINE_STAGE_TRANSFER_BIT                PipelineStageFlags = C.VK_PIPELINE_STAGE_TRANSFER_BIT
	PIPELINE_STAGE_FRAGMENT_SHADER_BIT         PipelineStageFlags = C.VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
	PIPELINE_STAGE_COMPUTE_SHADER_BIT          PipelineStageFlags = C.VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
	PIPELINE_STAGE_TOP_OF_PIPE_BIT             PipelineStageFlags = C.VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT
	PIPELINE_STAGE_HOST_BIT                    PipelineStageFlags = C.VK_PIPELINE_STAGE_HOST_BIT
	PIPELINE_STAGE_VERTEX_INPUT_BIT            PipelineStageFlags = C.VK_PIPELINE_STAGE_VERTEX_INPUT_BIT
	PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT          PipelineStageFlags = C.VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT

	ACCESS_MEMORY_READ_BIT            AccessFlags = C.VK_ACCESS_MEMORY_READ_BIT
	ACCESS_MEMORY_WRITE_BIT           AccessFlags = C.VK_ACCESS_MEMORY_WRITE_BIT
	ACCESS_TRANSFER_READ_BIT          AccessFlags = C.VK_ACCESS_TRANSFER_READ_BIT
	ACCESS_TRANSFER_WRITE_BIT         AccessFlags = C.VK_ACCESS_TRANSFER_WRITE_BIT
	ACCESS_SHADER_READ_BIT            AccessFlags = C.VK_ACCESS_SHADER_READ_BIT
	ACCESS_SHADER_WRITE_BIT           AccessFlags = C.VK_ACCESS_SHADER_WRITE_BIT
	ACCESS_COLOR_ATTACHMENT_READ_BIT  AccessFlags = C.VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
	ACCESS_COLOR_ATTACHMENT_WRITE_BIT AccessFlags = C.VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT
	ACCESS_HOST_READ_BIT              AccessFlags = C.VK_ACCESS_HOST_READ_BIT
	ACCESS_HOST_WRITE_BIT             AccessFlags = C.VK_ACCESS_HOST_WRITE_BIT
	ACCESS_VERTEX_ATTRIBUTE_READ_BIT  AccessFlags = C.VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT
	ACCESS_INDEX_READ_BIT             AccessFlags = C.VK_ACCESS_INDEX_READ_BIT

	PIPELINE_BIND_POINT_COMPUTE  PipelineBindPoint = C.VK_PIPELINE_BIND_POINT_COMPUTE
	PIPELINE_BIND_POINT_GRAPHICS PipelineBindPoint = C.VK_PIPELINE_BIND_POINT_GRAPHICS

	INDEX_TYPE_UINT16 IndexType = C.VK_INDEX_TYPE_UINT16
	INDEX_TYPE_UINT32 IndexType = C.VK_INDEX_TYPE_UINT32

	QUEUE_GRAPHICS_BIT QueueFlags = C.VK_QUEUE_GRAPHICS_BIT
	QUEUE_COMPUTE_BIT  QueueFlags = C.VK_QUEUE_COMPUTE_BIT
)

var (
	once    sync.Once
	loadErr error

	loadFuncs []func(dlopen func(name string) *[0]byte)
)

var funcs struct {
	vkCreateInstance                         C.PFN_vkCreateInstance
	vkDestroyInstance                        C.PFN_vkDestroyInstance
	vkEnumeratePhysicalDevices               C.PFN_vkEnumeratePhysicalDevices
	vkGetPhysicalDeviceQueueFamilyProperties C.PFN_vkGetPhysicalDeviceQueueFamilyProperties
	vkGetPhysicalDeviceFormatProperties      C.PFN_vkGetPhysicalDeviceFormatProperties
	vkCreateDevice                           C.PFN_vkCreateDevice
	vkDestroyDevice                          C.PFN_vkDestroyDevice
	vkGetDeviceQueue                         C.PFN_vkGetDeviceQueue
	vkCreateImageView                        C.PFN_vkCreateImageView
	vkDestroyImageView                       C.PFN_vkDestroyImageView
	vkCreateFramebuffer                      C.PFN_vkCreateFramebuffer
	vkDestroyFramebuffer                     C.PFN_vkDestroyFramebuffer
	vkDeviceWaitIdle                         C.PFN_vkDeviceWaitIdle
	vkQueueWaitIdle                          C.PFN_vkQueueWaitIdle
	vkCreateSemaphore                        C.PFN_vkCreateSemaphore
	vkDestroySemaphore                       C.PFN_vkDestroySemaphore
	vkCreateRenderPass                       C.PFN_vkCreateRenderPass
	vkDestroyRenderPass                      C.PFN_vkDestroyRenderPass
	vkCreateCommandPool                      C.PFN_vkCreateCommandPool
	vkDestroyCommandPool                     C.PFN_vkDestroyCommandPool
	vkAllocateCommandBuffers                 C.PFN_vkAllocateCommandBuffers
	vkFreeCommandBuffers                     C.PFN_vkFreeCommandBuffers
	vkBeginCommandBuffer                     C.PFN_vkBeginCommandBuffer
	vkEndCommandBuffer                       C.PFN_vkEndCommandBuffer
	vkQueueSubmit                            C.PFN_vkQueueSubmit
	vkCmdBeginRenderPass                     C.PFN_vkCmdBeginRenderPass
	vkCmdEndRenderPass                       C.PFN_vkCmdEndRenderPass
	vkCmdCopyBuffer                          C.PFN_vkCmdCopyBuffer
	vkCmdCopyBufferToImage                   C.PFN_vkCmdCopyBufferToImage
	vkCmdPipelineBarrier                     C.PFN_vkCmdPipelineBarrier
	vkCmdPushConstants                       C.PFN_vkCmdPushConstants
	vkCmdBindPipeline                        C.PFN_vkCmdBindPipeline
	vkCmdBindVertexBuffers                   C.PFN_vkCmdBindVertexBuffers
	vkCmdSetViewport                         C.PFN_vkCmdSetViewport
	vkCmdBindIndexBuffer                     C.PFN_vkCmdBindIndexBuffer
	vkCmdDraw                                C.PFN_vkCmdDraw
	vkCmdDrawIndexed                         C.PFN_vkCmdDrawIndexed
	vkCmdBindDescriptorSets                  C.PFN_vkCmdBindDescriptorSets
	vkCmdCopyImageToBuffer                   C.PFN_vkCmdCopyImageToBuffer
	vkCmdDispatch                            C.PFN_vkCmdDispatch
	vkCreateImage                            C.PFN_vkCreateImage
	vkDestroyImage                           C.PFN_vkDestroyImage
	vkGetImageMemoryRequirements             C.PFN_vkGetImageMemoryRequirements
	vkAllocateMemory                         C.PFN_vkAllocateMemory
	vkBindImageMemory                        C.PFN_vkBindImageMemory
	vkFreeMemory                             C.PFN_vkFreeMemory
	vkGetPhysicalDeviceMemoryProperties      C.PFN_vkGetPhysicalDeviceMemoryProperties
	vkCreateSampler                          C.PFN_vkCreateSampler
	vkDestroySampler                         C.PFN_vkDestroySampler
	vkCreateBuffer                           C.PFN_vkCreateBuffer
	vkDestroyBuffer                          C.PFN_vkDestroyBuffer
	vkGetBufferMemoryRequirements            C.PFN_vkGetBufferMemoryRequirements
	vkBindBufferMemory                       C.PFN_vkBindBufferMemory
	vkCreateShaderModule                     C.PFN_vkCreateShaderModule
	vkDestroyShaderModule                    C.PFN_vkDestroyShaderModule
	vkCreateGraphicsPipelines                C.PFN_vkCreateGraphicsPipelines
	vkDestroyPipeline                        C.PFN_vkDestroyPipeline
	vkCreatePipelineLayout                   C.PFN_vkCreatePipelineLayout
	vkDestroyPipelineLayout                  C.PFN_vkDestroyPipelineLayout
	vkCreateDescriptorSetLayout              C.PFN_vkCreateDescriptorSetLayout
	vkDestroyDescriptorSetLayout             C.PFN_vkDestroyDescriptorSetLayout
	vkMapMemory                              C.PFN_vkMapMemory
	vkUnmapMemory                            C.PFN_vkUnmapMemory
	vkResetCommandBuffer                     C.PFN_vkResetCommandBuffer
	vkCreateDescriptorPool                   C.PFN_vkCreateDescriptorPool
	vkDestroyDescriptorPool                  C.PFN_vkDestroyDescriptorPool
	vkAllocateDescriptorSets                 C.PFN_vkAllocateDescriptorSets
	vkFreeDescriptorSets                     C.PFN_vkFreeDescriptorSets
	vkUpdateDescriptorSets                   C.PFN_vkUpdateDescriptorSets
	vkResetDescriptorPool                    C.PFN_vkResetDescriptorPool
	vkCmdCopyImage                           C.PFN_vkCmdCopyImage
	vkCreateComputePipelines                 C.PFN_vkCreateComputePipelines
	vkCreateFence                            C.PFN_vkCreateFence
	vkDestroyFence                           C.PFN_vkDestroyFence
	vkWaitForFences                          C.PFN_vkWaitForFences
	vkResetFences                            C.PFN_vkResetFences
	vkGetPhysicalDeviceProperties            C.PFN_vkGetPhysicalDeviceProperties

	vkGetPhysicalDeviceSurfaceSupportKHR      C.PFN_vkGetPhysicalDeviceSurfaceSupportKHR
	vkDestroySurfaceKHR                       C.PFN_vkDestroySurfaceKHR
	vkGetPhysicalDeviceSurfaceFormatsKHR      C.PFN_vkGetPhysicalDeviceSurfaceFormatsKHR
	vkGetPhysicalDeviceSurfacePresentModesKHR C.PFN_vkGetPhysicalDeviceSurfacePresentModesKHR
	vkGetPhysicalDeviceSurfaceCapabilitiesKHR C.PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR

	vkCreateSwapchainKHR    C.PFN_vkCreateSwapchainKHR
	vkDestroySwapchainKHR   C.PFN_vkDestroySwapchainKHR
	vkGetSwapchainImagesKHR C.PFN_vkGetSwapchainImagesKHR
	vkAcquireNextImageKHR   C.PFN_vkAcquireNextImageKHR
	vkQueuePresentKHR       C.PFN_vkQueuePresentKHR
}

var (
	nilSurface             C.VkSurfaceKHR
	nilSwapchain           C.VkSwapchainKHR
	nilSemaphore           C.VkSemaphore
	nilImageView           C.VkImageView
	nilRenderPass          C.VkRenderPass
	nilFramebuffer         C.VkFramebuffer
	nilCommandPool         C.VkCommandPool
	nilImage               C.VkImage
	nilDeviceMemory        C.VkDeviceMemory
	nilSampler             C.VkSampler
	nilBuffer              C.VkBuffer
	nilShaderModule        C.VkShaderModule
	nilPipeline            C.VkPipeline
	nilPipelineCache       C.VkPipelineCache
	nilPipelineLayout      C.VkPipelineLayout
	nilDescriptorSetLayout C.VkDescriptorSetLayout
	nilDescriptorPool      C.VkDescriptorPool
	nilDescriptorSet       C.VkDescriptorSet
	nilFence               C.VkFence
)

func vkInit() error {
	once.Do(func() {
		var libName string
		switch {
		case runtime.GOOS == "android":
			libName = "libvulkan.so"
		default:
			libName = "libvulkan.so.1"
		}
		lib := dlopen(libName)
		if lib == nil {
			loadErr = fmt.Errorf("vulkan: %s", C.GoString(C.dlerror()))
			return
		}
		dlopen := func(name string) *[0]byte {
			return (*[0]byte)(dlsym(lib, name))
		}
		must := func(name string) *[0]byte {
			ptr := dlopen(name)
			if ptr != nil {
				return ptr
			}
			if loadErr == nil {
				loadErr = fmt.Errorf("vulkan: function %q not found: %s", name, C.GoString(C.dlerror()))
			}
			return nil
		}
		funcs.vkCreateInstance = must("vkCreateInstance")
		funcs.vkDestroyInstance = must("vkDestroyInstance")
		funcs.vkEnumeratePhysicalDevices = must("vkEnumeratePhysicalDevices")
		funcs.vkGetPhysicalDeviceQueueFamilyProperties = must("vkGetPhysicalDeviceQueueFamilyProperties")
		funcs.vkGetPhysicalDeviceFormatProperties = must("vkGetPhysicalDeviceFormatProperties")
		funcs.vkCreateDevice = must("vkCreateDevice")
		funcs.vkDestroyDevice = must("vkDestroyDevice")
		funcs.vkGetDeviceQueue = must("vkGetDeviceQueue")
		funcs.vkCreateImageView = must("vkCreateImageView")
		funcs.vkDestroyImageView = must("vkDestroyImageView")
		funcs.vkCreateFramebuffer = must("vkCreateFramebuffer")
		funcs.vkDestroyFramebuffer = must("vkDestroyFramebuffer")
		funcs.vkDeviceWaitIdle = must("vkDeviceWaitIdle")
		funcs.vkQueueWaitIdle = must("vkQueueWaitIdle")
		funcs.vkCreateSemaphore = must("vkCreateSemaphore")
		funcs.vkDestroySemaphore = must("vkDestroySemaphore")
		funcs.vkCreateRenderPass = must("vkCreateRenderPass")
		funcs.vkDestroyRenderPass = must("vkDestroyRenderPass")
		funcs.vkCreateCommandPool = must("vkCreateCommandPool")
		funcs.vkDestroyCommandPool = must("vkDestroyCommandPool")
		funcs.vkAllocateCommandBuffers = must("vkAllocateCommandBuffers")
		funcs.vkFreeCommandBuffers = must("vkFreeCommandBuffers")
		funcs.vkBeginCommandBuffer = must("vkBeginCommandBuffer")
		funcs.vkEndCommandBuffer = must("vkEndCommandBuffer")
		funcs.vkQueueSubmit = must("vkQueueSubmit")
		funcs.vkCmdBeginRenderPass = must("vkCmdBeginRenderPass")
		funcs.vkCmdEndRenderPass = must("vkCmdEndRenderPass")
		funcs.vkCmdCopyBuffer = must("vkCmdCopyBuffer")
		funcs.vkCmdCopyBufferToImage = must("vkCmdCopyBufferToImage")
		funcs.vkCmdPipelineBarrier = must("vkCmdPipelineBarrier")
		funcs.vkCmdPushConstants = must("vkCmdPushConstants")
		funcs.vkCmdBindPipeline = must("vkCmdBindPipeline")
		funcs.vkCmdBindVertexBuffers = must("vkCmdBindVertexBuffers")
		funcs.vkCmdSetViewport = must("vkCmdSetViewport")
		funcs.vkCmdBindIndexBuffer = must("vkCmdBindIndexBuffer")
		funcs.vkCmdDraw = must("vkCmdDraw")
		funcs.vkCmdDrawIndexed = must("vkCmdDrawIndexed")
		funcs.vkCmdBindDescriptorSets = must("vkCmdBindDescriptorSets")
		funcs.vkCmdCopyImageToBuffer = must("vkCmdCopyImageToBuffer")
		funcs.vkCmdDispatch = must("vkCmdDispatch")
		funcs.vkCreateImage = must("vkCreateImage")
		funcs.vkDestroyImage = must("vkDestroyImage")
		funcs.vkGetImageMemoryRequirements = must("vkGetImageMemoryRequirements")
		funcs.vkAllocateMemory = must("vkAllocateMemory")
		funcs.vkBindImageMemory = must("vkBindImageMemory")
		funcs.vkFreeMemory = must("vkFreeMemory")
		funcs.vkGetPhysicalDeviceMemoryProperties = must("vkGetPhysicalDeviceMemoryProperties")
		funcs.vkCreateSampler = must("vkCreateSampler")
		funcs.vkDestroySampler = must("vkDestroySampler")
		funcs.vkCreateBuffer = must("vkCreateBuffer")
		funcs.vkDestroyBuffer = must("vkDestroyBuffer")
		funcs.vkGetBufferMemoryRequirements = must("vkGetBufferMemoryRequirements")
		funcs.vkBindBufferMemory = must("vkBindBufferMemory")
		funcs.vkCreateShaderModule = must("vkCreateShaderModule")
		funcs.vkDestroyShaderModule = must("vkDestroyShaderModule")
		funcs.vkCreateGraphicsPipelines = must("vkCreateGraphicsPipelines")
		funcs.vkDestroyPipeline = must("vkDestroyPipeline")
		funcs.vkCreatePipelineLayout = must("vkCreatePipelineLayout")
		funcs.vkDestroyPipelineLayout = must("vkDestroyPipelineLayout")
		funcs.vkCreateDescriptorSetLayout = must("vkCreateDescriptorSetLayout")
		funcs.vkDestroyDescriptorSetLayout = must("vkDestroyDescriptorSetLayout")
		funcs.vkMapMemory = must("vkMapMemory")
		funcs.vkUnmapMemory = must("vkUnmapMemory")
		funcs.vkResetCommandBuffer = must("vkResetCommandBuffer")
		funcs.vkCreateDescriptorPool = must("vkCreateDescriptorPool")
		funcs.vkDestroyDescriptorPool = must("vkDestroyDescriptorPool")
		funcs.vkAllocateDescriptorSets = must("vkAllocateDescriptorSets")
		funcs.vkFreeDescriptorSets = must("vkFreeDescriptorSets")
		funcs.vkUpdateDescriptorSets = must("vkUpdateDescriptorSets")
		funcs.vkResetDescriptorPool = must("vkResetDescriptorPool")
		funcs.vkCmdCopyImage = must("vkCmdCopyImage")
		funcs.vkCreateComputePipelines = must("vkCreateComputePipelines")
		funcs.vkCreateFence = must("vkCreateFence")
		funcs.vkDestroyFence = must("vkDestroyFence")
		funcs.vkWaitForFences = must("vkWaitForFences")
		funcs.vkResetFences = must("vkResetFences")
		funcs.vkGetPhysicalDeviceProperties = must("vkGetPhysicalDeviceProperties")

		funcs.vkGetPhysicalDeviceSurfaceSupportKHR = dlopen("vkGetPhysicalDeviceSurfaceSupportKHR")
		funcs.vkDestroySurfaceKHR = dlopen("vkDestroySurfaceKHR")
		funcs.vkGetPhysicalDeviceSurfaceFormatsKHR = dlopen("vkGetPhysicalDeviceSurfaceFormatsKHR")
		funcs.vkGetPhysicalDeviceSurfacePresentModesKHR = dlopen("vkGetPhysicalDeviceSurfacePresentModesKHR")
		funcs.vkGetPhysicalDeviceSurfaceCapabilitiesKHR = dlopen("vkGetPhysicalDeviceSurfaceCapabilitiesKHR")

		funcs.vkCreateSwapchainKHR = dlopen("vkCreateSwapchainKHR")
		funcs.vkDestroySwapchainKHR = dlopen("vkDestroySwapchainKHR")
		funcs.vkGetSwapchainImagesKHR = dlopen("vkGetSwapchainImagesKHR")
		funcs.vkAcquireNextImageKHR = dlopen("vkAcquireNextImageKHR")
		funcs.vkQueuePresentKHR = dlopen("vkQueuePresentKHR")

		for _, f := range loadFuncs {
			f(dlopen)
		}
	})
	return loadErr
}

func CreateInstance(exts ...string) (Instance, error) {
	if err := vkInit(); err != nil {
		return nil, err
	}
	inf := C.VkInstanceCreateInfo{
		sType: C.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
	}
	if len(exts) > 0 {
		cexts := mallocCStringArr(exts)
		defer freeCStringArr(cexts)
		inf.enabledExtensionCount = C.uint32_t(len(exts))
		inf.ppEnabledExtensionNames = &cexts[0]
	}
	var inst Instance
	if err := vkErr(C.vkCreateInstance(funcs.vkCreateInstance, inf, nil, &inst)); err != nil {
		return nil, fmt.Errorf("vulkan: vkCreateInstance: %w", err)
	}
	return inst, nil
}

func mallocCStringArr(s []string) []*C.char {
	carr := make([]*C.char, len(s))
	for i, ext := range s {
		carr[i] = C.CString(ext)
	}
	return carr
}

func freeCStringArr(s []*C.char) {
	for i := range s {
		C.free(unsafe.Pointer(s[i]))
		s[i] = nil
	}
}

func DestroyInstance(inst Instance) {
	C.vkDestroyInstance(funcs.vkDestroyInstance, inst, nil)
}

func GetPhysicalDeviceQueueFamilyProperties(pd PhysicalDevice) []QueueFamilyProperties {
	var count C.uint32_t
	C.vkGetPhysicalDeviceQueueFamilyProperties(funcs.vkGetPhysicalDeviceQueueFamilyProperties, pd, &count, nil)
	if count == 0 {
		return nil
	}
	queues := make([]C.VkQueueFamilyProperties, count)
	C.vkGetPhysicalDeviceQueueFamilyProperties(funcs.vkGetPhysicalDeviceQueueFamilyProperties, pd, &count, &queues[0])
	return queues
}

func EnumeratePhysicalDevices(inst Instance) ([]PhysicalDevice, error) {
	var count C.uint32_t
	if err := vkErr(C.vkEnumeratePhysicalDevices(funcs.vkEnumeratePhysicalDevices, inst, &count, nil)); err != nil {
		return nil, fmt.Errorf("vulkan: vkEnumeratePhysicalDevices: %w", err)
	}
	if count == 0 {
		return nil, nil
	}
	devs := make([]C.VkPhysicalDevice, count)
	if err := vkErr(C.vkEnumeratePhysicalDevices(funcs.vkEnumeratePhysicalDevices, inst, &count, &devs[0])); err != nil {
		return nil, fmt.Errorf("vulkan: vkEnumeratePhysicalDevices: %w", err)
	}
	return devs, nil
}

func ChoosePhysicalDevice(inst Instance, surf Surface) (PhysicalDevice, int, error) {
	devs, err := EnumeratePhysicalDevices(inst)
	if err != nil {
		return nil, 0, err
	}
	for _, pd := range devs {
		var props C.VkPhysicalDeviceProperties
		C.vkGetPhysicalDeviceProperties(funcs.vkGetPhysicalDeviceProperties, pd, &props)
		// The lavapipe software implementation doesn't work well rendering to a surface.
		// See https://gitlab.freedesktop.org/mesa/mesa/-/issues/5473.
		if surf != 0 && props.deviceType == C.VK_PHYSICAL_DEVICE_TYPE_CPU {
			continue
		}
		const caps = C.VK_QUEUE_GRAPHICS_BIT | C.VK_QUEUE_COMPUTE_BIT
		queueIdx, ok, err := chooseQueue(pd, surf, caps)
		if err != nil {
			return nil, 0, err
		}
		if !ok {
			continue
		}
		if surf != nilSurface {
			_, fmtFound, err := chooseFormat(pd, surf)
			if err != nil {
				return nil, 0, err
			}
			_, modFound, err := choosePresentMode(pd, surf)
			if err != nil {
				return nil, 0, err
			}
			if !fmtFound || !modFound {
				continue
			}
		}
		return pd, queueIdx, nil
	}
	return nil, 0, errors.New("vulkan: no suitable device found")
}

func CreateDeviceAndQueue(pd C.VkPhysicalDevice, queueIdx int, exts ...string) (Device, error) {
	priority := C.float(1.0)
	qinf := C.VkDeviceQueueCreateInfo{
		sType:            C.VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
		queueCount:       1,
		queueFamilyIndex: C.uint32_t(queueIdx),
		pQueuePriorities: &priority,
	}
	inf := C.VkDeviceCreateInfo{
		sType:                 C.VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
		queueCreateInfoCount:  1,
		enabledExtensionCount: C.uint32_t(len(exts)),
	}
	if len(exts) > 0 {
		cexts := mallocCStringArr(exts)
		defer freeCStringArr(cexts)
		inf.ppEnabledExtensionNames = &cexts[0]
	}
	var dev Device
	if err := vkErr(C.vkCreateDevice(funcs.vkCreateDevice, pd, inf, qinf, nil, &dev)); err != nil {
		return nil, fmt.Errorf("vulkan: vkCreateDevice: %w", err)
	}
	return dev, nil
}

func GetDeviceQueue(d Device, queueFamily, queueIndex int) Queue {
	var queue Queue
	C.vkGetDeviceQueue(funcs.vkGetDeviceQueue, d, C.uint32_t(queueFamily), C.uint32_t(queueIndex), &queue)
	return queue
}

func GetPhysicalDeviceSurfaceCapabilities(pd PhysicalDevice, surf Surface) (SurfaceCapabilities, error) {
	var caps C.VkSurfaceCapabilitiesKHR
	err := vkErr(C.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(funcs.vkGetPhysicalDeviceSurfaceCapabilitiesKHR, pd, surf, &caps))
	if err != nil {
		return SurfaceCapabilities{}, fmt.Errorf("vulkan: vkGetPhysicalDeviceSurfaceCapabilitiesKHR: %w", err)
	}
	return caps, nil
}

func CreateSwapchain(pd PhysicalDevice, d Device, surf Surface, width, height int, old Swapchain) (Swapchain, []Image, Format, error) {
	caps, err := GetPhysicalDeviceSurfaceCapabilities(pd, surf)
	if err != nil {
		return nilSwapchain, nil, 0, err
	}
	mode, modeOK, err := choosePresentMode(pd, surf)
	if err != nil {
		return nilSwapchain, nil, 0, err
	}
	format, fmtOK, err := chooseFormat(pd, surf)
	if err != nil {
		return nilSwapchain, nil, 0, err
	}
	if !modeOK || !fmtOK {
		// This shouldn't happen because CreateDeviceAndQueue found at least
		// one valid format and present mode.
		return nilSwapchain, nil, 0, errors.New("vulkan: no valid format and present mode found")
	}
	// Find supported alpha composite mode. It doesn't matter which one, because rendering is
	// always opaque.
	alphaComp := C.VkCompositeAlphaFlagBitsKHR(1)
	for caps.supportedCompositeAlpha&C.VkCompositeAlphaFlagsKHR(alphaComp) == 0 {
		alphaComp <<= 1
	}
	trans := C.VkSurfaceTransformFlagBitsKHR(C.VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
	if caps.supportedTransforms&C.VkSurfaceTransformFlagsKHR(trans) == 0 {
		return nilSwapchain, nil, 0, errors.New("vulkan: VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR not supported")
	}
	inf := C.VkSwapchainCreateInfoKHR{
		sType:            C.VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
		surface:          surf,
		minImageCount:    caps.minImageCount,
		imageFormat:      format.format,
		imageColorSpace:  format.colorSpace,
		imageExtent:      C.VkExtent2D{width: C.uint32_t(width), height: C.uint32_t(height)},
		imageArrayLayers: 1,
		imageUsage:       C.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
		imageSharingMode: C.VK_SHARING_MODE_EXCLUSIVE,
		preTransform:     trans,
		presentMode:      mode,
		compositeAlpha:   C.VkCompositeAlphaFlagBitsKHR(alphaComp),
		clipped:          C.VK_TRUE,
		oldSwapchain:     old,
	}
	var swchain Swapchain
	if err := vkErr(C.vkCreateSwapchainKHR(funcs.vkCreateSwapchainKHR, d, &inf, nil, &swchain)); err != nil {
		return nilSwapchain, nil, 0, fmt.Errorf("vulkan: vkCreateSwapchainKHR: %w", err)
	}
	var count C.uint32_t
	if err := vkErr(C.vkGetSwapchainImagesKHR(funcs.vkGetSwapchainImagesKHR, d, swchain, &count, nil)); err != nil {
		DestroySwapchain(d, swchain)
		return nilSwapchain, nil, 0, fmt.Errorf("vulkan: vkGetSwapchainImagesKHR: %w", err)
	}
	if count == 0 {
		DestroySwapchain(d, swchain)
		return nilSwapchain, nil, 0, errors.New("vulkan: vkGetSwapchainImagesKHR returned no images")
	}
	imgs := make([]Image, count)
	if err := vkErr(C.vkGetSwapchainImagesKHR(funcs.vkGetSwapchainImagesKHR, d, swchain, &count, &imgs[0])); err != nil {
		DestroySwapchain(d, swchain)
		return nilSwapchain, nil, 0, fmt.Errorf("vulkan: vkGetSwapchainImagesKHR: %w", err)
	}
	return swchain, imgs, format.format, nil
}

func DestroySwapchain(d Device, swchain Swapchain) {
	C.vkDestroySwapchainKHR(funcs.vkDestroySwapchainKHR, d, swchain, nil)
}

func AcquireNextImage(d Device, swchain Swapchain, sem Semaphore, fence Fence) (int, error) {
	res := C.vkAcquireNextImageKHR(funcs.vkAcquireNextImageKHR, d, swchain, math.MaxUint64, sem, fence)
	if err := vkErr(res.res); err != nil {
		return 0, fmt.Errorf("vulkan: vkAcquireNextImageKHR: %w", err)
	}
	return int(res.uint), nil
}

func PresentQueue(q Queue, swchain Swapchain, sem Semaphore, imgIdx int) error {
	cidx := C.uint32_t(imgIdx)
	inf := C.VkPresentInfoKHR{
		sType:          C.VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
		swapchainCount: 1,
		pSwapchains:    &swchain,
		pImageIndices:  &cidx,
	}
	if sem != nilSemaphore {
		inf.waitSemaphoreCount = 1
		inf.pWaitSemaphores = &sem
	}
	if err := vkErr(C.vkQueuePresentKHR(funcs.vkQueuePresentKHR, q, inf)); err != nil {
		return fmt.Errorf("vulkan: vkQueuePresentKHR: %w", err)
	}
	return nil
}

func CreateImageView(d Device, img Image, format Format) (ImageView, error) {
	inf := C.VkImageViewCreateInfo{
		sType:    C.VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
		image:    img,
		viewType: C.VK_IMAGE_VIEW_TYPE_2D,
		format:   format,
		subresourceRange: C.VkImageSubresourceRange{
			aspectMask: C.VK_IMAGE_ASPECT_COLOR_BIT,
			levelCount: C.VK_REMAINING_MIP_LEVELS,
			layerCount: C.VK_REMAINING_ARRAY_LAYERS,
		},
	}
	var view C.VkImageView
	if err := vkErr(C.vkCreateImageView(funcs.vkCreateImageView, d, &inf, nil, &view)); err != nil {
		return nilImageView, fmt.Errorf("vulkan: vkCreateImageView: %w", err)
	}
	return view, nil
}

func DestroyImageView(d Device, view ImageView) {
	C.vkDestroyImageView(funcs.vkDestroyImageView, d, view, nil)
}

func CreateRenderPass(d Device, format Format, loadOp AttachmentLoadOp, initialLayout, finalLayout ImageLayout, passDeps []SubpassDependency) (RenderPass, error) {
	att := C.VkAttachmentDescription{
		format:         format,
		samples:        C.VK_SAMPLE_COUNT_1_BIT,
		loadOp:         loadOp,
		storeOp:        C.VK_ATTACHMENT_STORE_OP_STORE,
		stencilLoadOp:  C.VK_ATTACHMENT_LOAD_OP_DONT_CARE,
		stencilStoreOp: C.VK_ATTACHMENT_STORE_OP_DONT_CARE,
		initialLayout:  initialLayout,
		finalLayout:    finalLayout,
	}

	ref := C.VkAttachmentReference{
		attachment: 0,
		layout:     C.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
	}

	sub := C.VkSubpassDescription{
		pipelineBindPoint:    C.VK_PIPELINE_BIND_POINT_GRAPHICS,
		colorAttachmentCount: 1,
		pColorAttachments:    &ref,
	}

	inf := C.VkRenderPassCreateInfo{
		sType:           C.VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
		attachmentCount: 1,
		pAttachments:    &att,
		subpassCount:    1,
	}
	if n := len(passDeps); n > 0 {
		inf.dependencyCount = C.uint32_t(n)
		inf.pDependencies = &passDeps[0]
	}

	var pass RenderPass
	if err := vkErr(C.vkCreateRenderPass(funcs.vkCreateRenderPass, d, inf, sub, nil, &pass)); err != nil {
		return nilRenderPass, fmt.Errorf("vulkan: vkCreateRenderPass: %w", err)
	}
	return pass, nil
}

func DestroyRenderPass(d Device, r RenderPass) {
	C.vkDestroyRenderPass(funcs.vkDestroyRenderPass, d, r, nil)
}

func CreateFramebuffer(d Device, rp RenderPass, view ImageView, width, height int) (Framebuffer, error) {
	inf := C.VkFramebufferCreateInfo{
		sType:           C.VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
		renderPass:      rp,
		attachmentCount: 1,
		pAttachments:    &view,
		width:           C.uint32_t(width),
		height:          C.uint32_t(height),
		layers:          1,
	}
	var fbo Framebuffer
	if err := vkErr(C.vkCreateFramebuffer(funcs.vkCreateFramebuffer, d, inf, nil, &fbo)); err != nil {
		return nilFramebuffer, fmt.Errorf("vulkan: vkCreateFramebuffer: %w", err)
	}
	return fbo, nil

}

func DestroyFramebuffer(d Device, f Framebuffer) {
	C.vkDestroyFramebuffer(funcs.vkDestroyFramebuffer, d, f, nil)
}

func DeviceWaitIdle(d Device) error {
	if err := vkErr(C.vkDeviceWaitIdle(funcs.vkDeviceWaitIdle, d)); err != nil {
		return fmt.Errorf("vulkan: vkDeviceWaitIdle: %w", err)
	}
	return nil
}

func QueueWaitIdle(q Queue) error {
	if err := vkErr(C.vkQueueWaitIdle(funcs.vkQueueWaitIdle, q)); err != nil {
		return fmt.Errorf("vulkan: vkQueueWaitIdle: %w", err)
	}
	return nil
}

func CreateSemaphore(d Device) (Semaphore, error) {
	inf := C.VkSemaphoreCreateInfo{
		sType: C.VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
	}
	var sem Semaphore
	err := vkErr(C.vkCreateSemaphore(funcs.vkCreateSemaphore, d, &inf, nil, &sem))
	if err != nil {
		return nilSemaphore, fmt.Errorf("vulkan: vkCreateSemaphore: %w", err)
	}
	return sem, err
}

func DestroySemaphore(d Device, sem Semaphore) {
	C.vkDestroySemaphore(funcs.vkDestroySemaphore, d, sem, nil)
}

func DestroyDevice(dev Device) {
	C.vkDestroyDevice(funcs.vkDestroyDevice, dev, nil)
}

func DestroySurface(inst Instance, s Surface) {
	C.vkDestroySurfaceKHR(funcs.vkDestroySurfaceKHR, inst, s, nil)
}

func CreateCommandPool(d Device, queueIndex int) (CommandPool, error) {
	inf := C.VkCommandPoolCreateInfo{
		sType:            C.VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
		queueFamilyIndex: C.uint32_t(queueIndex),
		flags:            C.VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | C.VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
	}

	var pool CommandPool
	if err := vkErr(C.vkCreateCommandPool(funcs.vkCreateCommandPool, d, &inf, nil, &pool)); err != nil {
		return nilCommandPool, fmt.Errorf("vulkan: vkCreateCommandPool: %w", err)
	}
	return pool, nil
}

func DestroyCommandPool(d Device, pool CommandPool) {
	C.vkDestroyCommandPool(funcs.vkDestroyCommandPool, d, pool, nil)
}

func AllocateCommandBuffer(d Device, pool CommandPool) (CommandBuffer, error) {
	inf := C.VkCommandBufferAllocateInfo{
		sType:              C.VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
		commandPool:        pool,
		level:              C.VK_COMMAND_BUFFER_LEVEL_PRIMARY,
		commandBufferCount: 1,
	}

	var buf CommandBuffer
	if err := vkErr(C.vkAllocateCommandBuffers(funcs.vkAllocateCommandBuffers, d, &inf, &buf)); err != nil {
		return nil, fmt.Errorf("vulkan: vkAllocateCommandBuffers: %w", err)
	}
	return buf, nil
}

func FreeCommandBuffers(d Device, pool CommandPool, bufs ...CommandBuffer) {
	if len(bufs) == 0 {
		return
	}
	C.vkFreeCommandBuffers(funcs.vkFreeCommandBuffers, d, pool, C.uint32_t(len(bufs)), &bufs[0])
}

func BeginCommandBuffer(buf CommandBuffer) error {
	inf := C.VkCommandBufferBeginInfo{
		sType: C.VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
		flags: C.VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
	}
	if err := vkErr(C.vkBeginCommandBuffer(funcs.vkBeginCommandBuffer, buf, inf)); err != nil {
		return fmt.Errorf("vulkan: vkBeginCommandBuffer: %w", err)
	}
	return nil
}

func EndCommandBuffer(buf CommandBuffer) error {
	if err := vkErr(C.vkEndCommandBuffer(funcs.vkEndCommandBuffer, buf)); err != nil {
		return fmt.Errorf("vulkan: vkEndCommandBuffer: %w", err)
	}
	return nil
}

func QueueSubmit(q Queue, buf CommandBuffer, waitSems []Semaphore, waitStages []PipelineStageFlags, sigSems []Semaphore, fence Fence) error {
	inf := C.VkSubmitInfo{
		sType:              C.VK_STRUCTURE_TYPE_SUBMIT_INFO,
		commandBufferCount: 1,
		pCommandBuffers:    &buf,
	}
	if len(waitSems) > 0 {
		if len(waitSems) != len(waitStages) {
			panic("len(waitSems) != len(waitStages)")
		}
		inf.waitSemaphoreCount = C.uint32_t(len(waitSems))
		inf.pWaitSemaphores = &waitSems[0]
		inf.pWaitDstStageMask = &waitStages[0]
	}
	if len(sigSems) > 0 {
		inf.signalSemaphoreCount = C.uint32_t(len(sigSems))
		inf.pSignalSemaphores = &sigSems[0]
	}
	if err := vkErr(C.vkQueueSubmit(funcs.vkQueueSubmit, q, inf, fence)); err != nil {
		return fmt.Errorf("vulkan: vkQueueSubmit: %w", err)
	}
	return nil
}

func CmdBeginRenderPass(buf CommandBuffer, rp RenderPass, fbo Framebuffer, width, height int, clearCol [4]float32) {
	cclearCol := [4]C.float{C.float(clearCol[0]), C.float(clearCol[1]), C.float(clearCol[2]), C.float(clearCol[3])}
	inf := C.VkRenderPassBeginInfo{
		sType:           C.VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
		renderPass:      rp,
		framebuffer:     fbo,
		renderArea:      C.VkRect2D{extent: C.VkExtent2D{width: C.uint32_t(width), height: C.uint32_t(height)}},
		clearValueCount: 1,
		pClearValues:    (*C.VkClearValue)(unsafe.Pointer(&cclearCol)),
	}
	C.vkCmdBeginRenderPass(funcs.vkCmdBeginRenderPass, buf, inf, C.VK_SUBPASS_CONTENTS_INLINE)
}

func CmdEndRenderPass(buf CommandBuffer) {
	C.vkCmdEndRenderPass(funcs.vkCmdEndRenderPass, buf)
}

func CmdCopyBuffer(cmdBuf CommandBuffer, src, dst Buffer, srcOff, dstOff, size int) {
	C.vkCmdCopyBuffer(funcs.vkCmdCopyBuffer, cmdBuf, src, dst, 1, &C.VkBufferCopy{
		srcOffset: C.VkDeviceSize(srcOff),
		dstOffset: C.VkDeviceSize(dstOff),
		size:      C.VkDeviceSize(size),
	})
}

func CmdCopyBufferToImage(cmdBuf CommandBuffer, src Buffer, dst Image, layout ImageLayout, copy BufferImageCopy) {
	C.vkCmdCopyBufferToImage(funcs.vkCmdCopyBufferToImage, cmdBuf, src, dst, layout, 1, &copy)
}

func CmdPipelineBarrier(cmdBuf CommandBuffer, srcStage, dstStage PipelineStageFlags, flags DependencyFlags, memBarriers []MemoryBarrier, bufBarriers []BufferMemoryBarrier, imgBarriers []ImageMemoryBarrier) {
	var memPtr *MemoryBarrier
	if len(memBarriers) > 0 {
		memPtr = &memBarriers[0]
	}
	var bufPtr *BufferMemoryBarrier
	if len(bufBarriers) > 0 {
		bufPtr = &bufBarriers[0]
	}
	var imgPtr *ImageMemoryBarrier
	if len(imgBarriers) > 0 {
		imgPtr = &imgBarriers[0]
	}
	C.vkCmdPipelineBarrier(funcs.vkCmdPipelineBarrier, cmdBuf, srcStage, dstStage, flags,
		C.uint32_t(len(memBarriers)), memPtr,
		C.uint32_t(len(bufBarriers)), bufPtr,
		C.uint32_t(len(imgBarriers)), imgPtr)
}

func CmdPushConstants(cmdBuf CommandBuffer, layout PipelineLayout, stages ShaderStageFlags, offset int, data []byte) {
	if len(data) == 0 {
		return
	}
	C.vkCmdPushConstants(funcs.vkCmdPushConstants, cmdBuf, layout, stages, C.uint32_t(offset), C.uint32_t(len(data)), unsafe.Pointer(&data[0]))
}

func CmdBindPipeline(cmdBuf CommandBuffer, bindPoint PipelineBindPoint, pipe Pipeline) {
	C.vkCmdBindPipeline(funcs.vkCmdBindPipeline, cmdBuf, bindPoint, pipe)
}

func CmdBindVertexBuffers(cmdBuf CommandBuffer, first int, buffers []Buffer, sizes []DeviceSize) {
	if len(buffers) == 0 {
		return
	}
	C.vkCmdBindVertexBuffers(funcs.vkCmdBindVertexBuffers, cmdBuf, C.uint32_t(first), C.uint32_t(len(buffers)), &buffers[0], &sizes[0])
}

func CmdSetViewport(cmdBuf CommandBuffer, first int, viewports ...Viewport) {
	if len(viewports) == 0 {
		return
	}
	C.vkCmdSetViewport(funcs.vkCmdSetViewport, cmdBuf, C.uint32_t(first), C.uint32_t(len(viewports)), &viewports[0])
}

func CmdBindIndexBuffer(cmdBuf CommandBuffer, buffer Buffer, offset int, typ IndexType) {
	C.vkCmdBindIndexBuffer(funcs.vkCmdBindIndexBuffer, cmdBuf, buffer, C.VkDeviceSize(offset), typ)
}

func CmdDraw(cmdBuf CommandBuffer, vertCount, instCount, firstVert, firstInst int) {
	C.vkCmdDraw(funcs.vkCmdDraw, cmdBuf, C.uint32_t(vertCount), C.uint32_t(instCount), C.uint32_t(firstVert), C.uint32_t(firstInst))
}

func CmdDrawIndexed(cmdBuf CommandBuffer, idxCount, instCount, firstIdx, vertOff, firstInst int) {
	C.vkCmdDrawIndexed(funcs.vkCmdDrawIndexed, cmdBuf, C.uint32_t(idxCount), C.uint32_t(instCount), C.uint32_t(firstIdx), C.int32_t(vertOff), C.uint32_t(firstInst))
}

func GetPhysicalDeviceFormatProperties(physDev PhysicalDevice, format Format) FormatFeatureFlags {
	var props C.VkFormatProperties
	C.vkGetPhysicalDeviceFormatProperties(funcs.vkGetPhysicalDeviceFormatProperties, physDev, format, &props)
	return FormatFeatureFlags(props.optimalTilingFeatures)
}

func CmdBindDescriptorSets(cmdBuf CommandBuffer, point PipelineBindPoint, layout PipelineLayout, firstSet int, sets []DescriptorSet) {
	C.vkCmdBindDescriptorSets(funcs.vkCmdBindDescriptorSets, cmdBuf, point, layout, C.uint32_t(firstSet), C.uint32_t(len(sets)), &sets[0], 0, nil)
}

func CmdCopyImage(cmdBuf CommandBuffer, src Image, srcLayout ImageLayout, dst Image, dstLayout ImageLayout, regions []ImageCopy) {
	if len(regions) == 0 {
		return
	}
	C.vkCmdCopyImage(funcs.vkCmdCopyImage, cmdBuf, src, srcLayout, dst, dstLayout, C.uint32_t(len(regions)), &regions[0])
}

func CmdCopyImageToBuffer(cmdBuf CommandBuffer, src Image, srcLayout ImageLayout, dst Buffer, regions []BufferImageCopy) {
	if len(regions) == 0 {
		return
	}
	C.vkCmdCopyImageToBuffer(funcs.vkCmdCopyImageToBuffer, cmdBuf, src, srcLayout, dst, C.uint32_t(len(regions)), &regions[0])
}

func CmdDispatch(cmdBuf CommandBuffer, x, y, z int) {
	C.vkCmdDispatch(funcs.vkCmdDispatch, cmdBuf, C.uint32_t(x), C.uint32_t(y), C.uint32_t(z))
}

func CreateImage(pd PhysicalDevice, d Device, format Format, width, height int, usage ImageUsageFlags) (Image, DeviceMemory, error) {
	inf := C.VkImageCreateInfo{
		sType:     C.VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
		imageType: C.VK_IMAGE_TYPE_2D,
		format:    format,
		extent: C.VkExtent3D{
			width:  C.uint32_t(width),
			height: C.uint32_t(height),
			depth:  1,
		},
		mipLevels:     1,
		arrayLayers:   1,
		samples:       C.VK_SAMPLE_COUNT_1_BIT,
		tiling:        C.VK_IMAGE_TILING_OPTIMAL,
		usage:         usage,
		initialLayout: C.VK_IMAGE_LAYOUT_UNDEFINED,
	}
	var img C.VkImage
	if err := vkErr(C.vkCreateImage(funcs.vkCreateImage, d, &inf, nil, &img)); err != nil {
		return nilImage, nilDeviceMemory, fmt.Errorf("vulkan: vkCreateImage: %w", err)
	}
	var memReqs C.VkMemoryRequirements
	C.vkGetImageMemoryRequirements(funcs.vkGetImageMemoryRequirements, d, img, &memReqs)

	memIdx, found := findMemoryTypeIndex(pd, memReqs.memoryTypeBits, C.VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
	if !found {
		DestroyImage(d, img)
		return nilImage, nilDeviceMemory, errors.New("vulkan: no memory type suitable for images found")
	}

	memInf := C.VkMemoryAllocateInfo{
		sType:           C.VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
		allocationSize:  memReqs.size,
		memoryTypeIndex: C.uint32_t(memIdx),
	}
	var imgMem C.VkDeviceMemory
	if err := vkErr(C.vkAllocateMemory(funcs.vkAllocateMemory, d, &memInf, nil, &imgMem)); err != nil {
		DestroyImage(d, img)
		return nilImage, nilDeviceMemory, fmt.Errorf("vulkan: vkAllocateMemory: %w", err)
	}

	if err := vkErr(C.vkBindImageMemory(funcs.vkBindImageMemory, d, img, imgMem, 0)); err != nil {
		FreeMemory(d, imgMem)
		DestroyImage(d, img)
		return nilImage, nilDeviceMemory, fmt.Errorf("vulkan: vkBindImageMemory: %w", err)
	}
	return img, imgMem, nil
}

func DestroyImage(d Device, img Image) {
	C.vkDestroyImage(funcs.vkDestroyImage, d, img, nil)
}

func FreeMemory(d Device, mem DeviceMemory) {
	C.vkFreeMemory(funcs.vkFreeMemory, d, mem, nil)
}

func CreateSampler(d Device, minFilter, magFilter Filter) (Sampler, error) {
	inf := C.VkSamplerCreateInfo{
		sType:        C.VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
		minFilter:    minFilter,
		magFilter:    magFilter,
		addressModeU: C.VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
		addressModeV: C.VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
	}
	var s C.VkSampler
	if err := vkErr(C.vkCreateSampler(funcs.vkCreateSampler, d, &inf, nil, &s)); err != nil {
		return nilSampler, fmt.Errorf("vulkan: vkCreateSampler: %w", err)
	}
	return s, nil
}

func DestroySampler(d Device, sampler Sampler) {
	C.vkDestroySampler(funcs.vkDestroySampler, d, sampler, nil)
}

func CreateBuffer(pd PhysicalDevice, d Device, size int, usage BufferUsageFlags, props MemoryPropertyFlags) (Buffer, DeviceMemory, error) {
	inf := C.VkBufferCreateInfo{
		sType: C.VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
		size:  C.VkDeviceSize(size),
		usage: usage,
	}
	var buf C.VkBuffer
	if err := vkErr(C.vkCreateBuffer(funcs.vkCreateBuffer, d, &inf, nil, &buf)); err != nil {
		return nilBuffer, nilDeviceMemory, fmt.Errorf("vulkan: vkCreateBuffer: %w", err)
	}

	var memReqs C.VkMemoryRequirements
	C.vkGetBufferMemoryRequirements(funcs.vkGetBufferMemoryRequirements, d, buf, &memReqs)

	memIdx, found := findMemoryTypeIndex(pd, memReqs.memoryTypeBits, props)
	if !found {
		DestroyBuffer(d, buf)
		return nilBuffer, nilDeviceMemory, errors.New("vulkan: no memory suitable for buffers found")
	}
	memInf := C.VkMemoryAllocateInfo{
		sType:           C.VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
		allocationSize:  memReqs.size,
		memoryTypeIndex: C.uint32_t(memIdx),
	}

	var mem C.VkDeviceMemory
	if err := vkErr(C.vkAllocateMemory(funcs.vkAllocateMemory, d, &memInf, nil, &mem)); err != nil {
		DestroyBuffer(d, buf)
		return nilBuffer, nilDeviceMemory, fmt.Errorf("vulkan: vkAllocateMemory: %w", err)
	}

	if err := vkErr(C.vkBindBufferMemory(funcs.vkBindBufferMemory, d, buf, mem, 0)); err != nil {
		FreeMemory(d, mem)
		DestroyBuffer(d, buf)
		return nilBuffer, nilDeviceMemory, fmt.Errorf("vulkan: vkBindBufferMemory: %w", err)
	}
	return buf, mem, nil
}

func DestroyBuffer(d Device, buf Buffer) {
	C.vkDestroyBuffer(funcs.vkDestroyBuffer, d, buf, nil)
}

func CreateShaderModule(d Device, spirv string) (ShaderModule, error) {
	ptr := unsafe.Pointer((*reflect.StringHeader)(unsafe.Pointer(&spirv)).Data)
	inf := C.VkShaderModuleCreateInfo{
		sType:    C.VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
		codeSize: C.size_t(len(spirv)),
		pCode:    (*C.uint32_t)(ptr),
	}

	var mod C.VkShaderModule
	if err := vkErr(C.vkCreateShaderModule(funcs.vkCreateShaderModule, d, inf, nil, &mod)); err != nil {
		return nilShaderModule, fmt.Errorf("vulkan: vkCreateShaderModule: %w", err)
	}
	return mod, nil
}

func DestroyShaderModule(d Device, mod ShaderModule) {
	C.vkDestroyShaderModule(funcs.vkDestroyShaderModule, d, mod, nil)
}

func CreateGraphicsPipeline(d Device, pass RenderPass, vmod, fmod ShaderModule, blend bool, srcFactor, dstFactor BlendFactor, topology PrimitiveTopology, bindings []VertexInputBindingDescription, attrs []VertexInputAttributeDescription, layout PipelineLayout) (Pipeline, error) {
	main := C.CString("main")
	defer C.free(unsafe.Pointer(main))
	stages := []C.VkPipelineShaderStageCreateInfo{
		{
			sType:  C.VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
			stage:  C.VK_SHADER_STAGE_VERTEX_BIT,
			module: vmod,
			pName:  main,
		},
		{
			sType:  C.VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
			stage:  C.VK_SHADER_STAGE_FRAGMENT_BIT,
			module: fmod,
			pName:  main,
		},
	}
	dynStates := []C.VkDynamicState{C.VK_DYNAMIC_STATE_VIEWPORT}
	dynInf := C.VkPipelineDynamicStateCreateInfo{
		sType:             C.VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
		dynamicStateCount: C.uint32_t(len(dynStates)),
		pDynamicStates:    &dynStates[0],
	}
	const maxDim = 0x7fffffff
	scissors := []C.VkRect2D{{extent: C.VkExtent2D{width: maxDim, height: maxDim}}}
	viewportInf := C.VkPipelineViewportStateCreateInfo{
		sType:         C.VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
		viewportCount: 1,
		scissorCount:  C.uint32_t(len(scissors)),
		pScissors:     &scissors[0],
	}
	enable := C.VkBool32(0)
	if blend {
		enable = 1
	}
	attBlendInf := C.VkPipelineColorBlendAttachmentState{
		blendEnable:         enable,
		srcColorBlendFactor: srcFactor,
		srcAlphaBlendFactor: srcFactor,
		dstColorBlendFactor: dstFactor,
		dstAlphaBlendFactor: dstFactor,
		colorBlendOp:        C.VK_BLEND_OP_ADD,
		alphaBlendOp:        C.VK_BLEND_OP_ADD,
		colorWriteMask:      C.VK_COLOR_COMPONENT_R_BIT | C.VK_COLOR_COMPONENT_G_BIT | C.VK_COLOR_COMPONENT_B_BIT | C.VK_COLOR_COMPONENT_A_BIT,
	}
	blendInf := C.VkPipelineColorBlendStateCreateInfo{
		sType:           C.VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
		attachmentCount: 1,
		pAttachments:    &attBlendInf,
	}
	var vkBinds []C.VkVertexInputBindingDescription
	var vkAttrs []C.VkVertexInputAttributeDescription
	for _, b := range bindings {
		vkBinds = append(vkBinds, C.VkVertexInputBindingDescription{
			binding: C.uint32_t(b.Binding),
			stride:  C.uint32_t(b.Stride),
		})
	}
	for _, a := range attrs {
		vkAttrs = append(vkAttrs, C.VkVertexInputAttributeDescription{
			location: C.uint32_t(a.Location),
			binding:  C.uint32_t(a.Binding),
			format:   a.Format,
			offset:   C.uint32_t(a.Offset),
		})
	}
	vertexInf := C.VkPipelineVertexInputStateCreateInfo{
		sType: C.VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
	}
	if n := len(vkBinds); n > 0 {
		vertexInf.vertexBindingDescriptionCount = C.uint32_t(n)
		vertexInf.pVertexBindingDescriptions = &vkBinds[0]
	}
	if n := len(vkAttrs); n > 0 {
		vertexInf.vertexAttributeDescriptionCount = C.uint32_t(n)
		vertexInf.pVertexAttributeDescriptions = &vkAttrs[0]
	}
	inf := C.VkGraphicsPipelineCreateInfo{
		sType:      C.VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
		stageCount: C.uint32_t(len(stages)),
		pStages:    &stages[0],
		renderPass: pass,
		layout:     layout,
		pRasterizationState: &C.VkPipelineRasterizationStateCreateInfo{
			sType:     C.VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
			lineWidth: 1.0,
		},
		pMultisampleState: &C.VkPipelineMultisampleStateCreateInfo{
			sType:                C.VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
			rasterizationSamples: C.VK_SAMPLE_COUNT_1_BIT,
		},
		pInputAssemblyState: &C.VkPipelineInputAssemblyStateCreateInfo{
			sType:    C.VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
			topology: topology,
		},
	}

	var pipe C.VkPipeline
	if err := vkErr(C.vkCreateGraphicsPipelines(funcs.vkCreateGraphicsPipelines, d, nilPipelineCache, inf, dynInf, blendInf, vertexInf, viewportInf, nil, &pipe)); err != nil {
		return nilPipeline, fmt.Errorf("vulkan: vkCreateGraphicsPipelines: %w", err)
	}
	return pipe, nil
}

func DestroyPipeline(d Device, p Pipeline) {
	C.vkDestroyPipeline(funcs.vkDestroyPipeline, d, p, nil)
}

func CreatePipelineLayout(d Device, pushRanges []PushConstantRange, sets []DescriptorSetLayout) (PipelineLayout, error) {
	inf := C.VkPipelineLayoutCreateInfo{
		sType: C.VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
	}
	if n := len(sets); n > 0 {
		inf.setLayoutCount = C.uint32_t(n)
		inf.pSetLayouts = &sets[0]
	}
	if n := len(pushRanges); n > 0 {
		inf.pushConstantRangeCount = C.uint32_t(n)
		inf.pPushConstantRanges = &pushRanges[0]
	}
	var l C.VkPipelineLayout
	if err := vkErr(C.vkCreatePipelineLayout(funcs.vkCreatePipelineLayout, d, inf, nil, &l)); err != nil {
		return nilPipelineLayout, fmt.Errorf("vulkan: vkCreatePipelineLayout: %w", err)
	}
	return l, nil
}

func DestroyPipelineLayout(d Device, l PipelineLayout) {
	C.vkDestroyPipelineLayout(funcs.vkDestroyPipelineLayout, d, l, nil)
}

func CreateDescriptorSetLayout(d Device, bindings []DescriptorSetLayoutBinding) (DescriptorSetLayout, error) {
	var vkbinds []C.VkDescriptorSetLayoutBinding
	for _, b := range bindings {
		vkbinds = append(vkbinds, C.VkDescriptorSetLayoutBinding{
			binding:         C.uint32_t(b.Binding),
			descriptorType:  b.DescriptorType,
			descriptorCount: 1,
			stageFlags:      b.StageFlags,
		})
	}
	inf := C.VkDescriptorSetLayoutCreateInfo{
		sType: C.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
	}
	if n := len(vkbinds); n > 0 {
		inf.bindingCount = C.uint32_t(len(vkbinds))
		inf.pBindings = &vkbinds[0]
	}
	var l C.VkDescriptorSetLayout
	if err := vkErr(C.vkCreateDescriptorSetLayout(funcs.vkCreateDescriptorSetLayout, d, inf, nil, &l)); err != nil {
		return nilDescriptorSetLayout, fmt.Errorf("vulkan: vkCreateDescriptorSetLayout: %w", err)
	}
	return l, nil
}

func DestroyDescriptorSetLayout(d Device, l DescriptorSetLayout) {
	C.vkDestroyDescriptorSetLayout(funcs.vkDestroyDescriptorSetLayout, d, l, nil)
}

func MapMemory(d Device, mem DeviceMemory, offset, size int) ([]byte, error) {
	var ptr unsafe.Pointer
	if err := vkErr(C.vkMapMemory(funcs.vkMapMemory, d, mem, C.VkDeviceSize(offset), C.VkDeviceSize(size), 0, &ptr)); err != nil {
		return nil, fmt.Errorf("vulkan: vkMapMemory: %w", err)
	}
	return ((*[1 << 30]byte)(ptr))[:size:size], nil
}

func UnmapMemory(d Device, mem DeviceMemory) {
	C.vkUnmapMemory(funcs.vkUnmapMemory, d, mem)
}

func ResetCommandBuffer(buf CommandBuffer) error {
	if err := vkErr(C.vkResetCommandBuffer(funcs.vkResetCommandBuffer, buf, 0)); err != nil {
		return fmt.Errorf("vulkan: vkResetCommandBuffer. %w", err)
	}
	return nil
}

func CreateDescriptorPool(d Device, maxSets int, sizes []DescriptorPoolSize) (DescriptorPool, error) {
	inf := C.VkDescriptorPoolCreateInfo{
		sType:         C.VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
		maxSets:       C.uint32_t(maxSets),
		poolSizeCount: C.uint32_t(len(sizes)),
		pPoolSizes:    &sizes[0],
	}
	var pool C.VkDescriptorPool
	if err := vkErr(C.vkCreateDescriptorPool(funcs.vkCreateDescriptorPool, d, inf, nil, &pool)); err != nil {
		return nilDescriptorPool, fmt.Errorf("vulkan: vkCreateDescriptorPool: %w", err)
	}
	return pool, nil
}

func DestroyDescriptorPool(d Device, pool DescriptorPool) {
	C.vkDestroyDescriptorPool(funcs.vkDestroyDescriptorPool, d, pool, nil)
}

func ResetDescriptorPool(d Device, pool DescriptorPool) error {
	if err := vkErr(C.vkResetDescriptorPool(funcs.vkResetDescriptorPool, d, pool, 0)); err != nil {
		return fmt.Errorf("vulkan: vkResetDescriptorPool: %w", err)
	}
	return nil
}

func UpdateDescriptorSet(d Device, write WriteDescriptorSet) {
	C.vkUpdateDescriptorSets(funcs.vkUpdateDescriptorSets, d, write, 0, nil)
}

func AllocateDescriptorSet(d Device, pool DescriptorPool, layout DescriptorSetLayout) (DescriptorSet, error) {
	inf := C.VkDescriptorSetAllocateInfo{
		sType:              C.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
		descriptorPool:     pool,
		descriptorSetCount: 1,
		pSetLayouts:        &layout,
	}
	var set C.VkDescriptorSet
	if err := vkErr(C.vkAllocateDescriptorSets(funcs.vkAllocateDescriptorSets, d, inf, &set)); err != nil {
		return nilDescriptorSet, fmt.Errorf("vulkan: vkAllocateDescriptorSets: %w", err)
	}
	return set, nil
}

func CreateComputePipeline(d Device, mod ShaderModule, layout PipelineLayout) (Pipeline, error) {
	main := C.CString("main")
	defer C.free(unsafe.Pointer(main))
	inf := C.VkComputePipelineCreateInfo{
		sType: C.VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
		stage: C.VkPipelineShaderStageCreateInfo{
			sType:  C.VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
			stage:  C.VK_SHADER_STAGE_COMPUTE_BIT,
			module: mod,
			pName:  main,
		},
		layout: layout,
	}
	var pipe C.VkPipeline
	if err := vkErr(C.vkCreateComputePipelines(funcs.vkCreateComputePipelines, d, nilPipelineCache, 1, &inf, nil, &pipe)); err != nil {
		return nilPipeline, fmt.Errorf("vulkan: vkCreateComputePipelines: %w", err)
	}
	return pipe, nil
}

func CreateFence(d Device) (Fence, error) {
	inf := C.VkFenceCreateInfo{
		sType: C.VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
	}
	var f C.VkFence
	if err := vkErr(C.vkCreateFence(funcs.vkCreateFence, d, &inf, nil, &f)); err != nil {
		return nilFence, fmt.Errorf("vulkan: vkCreateFence: %w", err)
	}
	return f, nil
}

func DestroyFence(d Device, f Fence) {
	C.vkDestroyFence(funcs.vkDestroyFence, d, f, nil)
}

func WaitForFences(d Device, fences ...Fence) error {
	if len(fences) == 0 {
		return nil
	}
	err := vkErr(C.vkWaitForFences(funcs.vkWaitForFences, d, C.uint32_t(len(fences)), &fences[0], C.VK_TRUE, 0xffffffffffffffff))
	if err != nil {
		return fmt.Errorf("vulkan: vkWaitForFences: %w", err)
	}
	return nil
}

func ResetFences(d Device, fences ...Fence) error {
	if len(fences) == 0 {
		return nil
	}
	err := vkErr(C.vkResetFences(funcs.vkResetFences, d, C.uint32_t(len(fences)), &fences[0]))
	if err != nil {
		return fmt.Errorf("vulkan: vkResetFences: %w", err)
	}
	return nil
}

func BuildSubpassDependency(srcStage, dstStage PipelineStageFlags, srcMask, dstMask AccessFlags, flags DependencyFlags) SubpassDependency {
	return C.VkSubpassDependency{
		srcSubpass:      C.VK_SUBPASS_EXTERNAL,
		srcStageMask:    srcStage,
		srcAccessMask:   srcMask,
		dstSubpass:      0,
		dstStageMask:    dstStage,
		dstAccessMask:   dstMask,
		dependencyFlags: flags,
	}
}

func BuildPushConstantRange(stages ShaderStageFlags, offset, size int) PushConstantRange {
	return C.VkPushConstantRange{
		stageFlags: stages,
		offset:     C.uint32_t(offset),
		size:       C.uint32_t(size),
	}
}

func BuildDescriptorPoolSize(typ DescriptorType, count int) DescriptorPoolSize {
	return C.VkDescriptorPoolSize{
		_type:           typ,
		descriptorCount: C.uint32_t(count),
	}
}

func BuildWriteDescriptorSetImage(set DescriptorSet, binding int, typ DescriptorType, sampler Sampler, view ImageView, layout ImageLayout) WriteDescriptorSet {
	return C.VkWriteDescriptorSet{
		sType:           C.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
		dstSet:          set,
		dstBinding:      C.uint32_t(binding),
		descriptorCount: 1,
		descriptorType:  typ,
		pImageInfo: &C.VkDescriptorImageInfo{
			sampler:     sampler,
			imageView:   view,
			imageLayout: layout,
		},
	}
}

func BuildWriteDescriptorSetBuffer(set DescriptorSet, binding int, typ DescriptorType, buf Buffer) WriteDescriptorSet {
	return C.VkWriteDescriptorSet{
		sType:           C.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
		dstSet:          set,
		dstBinding:      C.uint32_t(binding),
		descriptorCount: 1,
		descriptorType:  typ,
		pBufferInfo: &C.VkDescriptorBufferInfo{
			buffer: buf,
			_range: C.VK_WHOLE_SIZE,
		},
	}
}

func (r PushConstantRange) StageFlags() ShaderStageFlags {
	return r.stageFlags
}

func (r PushConstantRange) Offset() int {
	return int(r.offset)
}

func (r PushConstantRange) Size() int {
	return int(r.size)
}

func (p QueueFamilyProperties) Flags() QueueFlags {
	return p.queueFlags
}

func (c SurfaceCapabilities) MinExtent() image.Point {
	return image.Pt(int(c.minImageExtent.width), int(c.minImageExtent.height))
}

func (c SurfaceCapabilities) MaxExtent() image.Point {
	return image.Pt(int(c.maxImageExtent.width), int(c.maxImageExtent.height))
}

func BuildViewport(x, y, width, height float32) Viewport {
	return C.VkViewport{
		x:        C.float(x),
		y:        C.float(y),
		width:    C.float(width),
		height:   C.float(height),
		maxDepth: 1.0,
	}
}

func BuildImageMemoryBarrier(img Image, srcMask, dstMask AccessFlags, oldLayout, newLayout ImageLayout) ImageMemoryBarrier {
	return C.VkImageMemoryBarrier{
		sType:         C.VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
		srcAccessMask: srcMask,
		dstAccessMask: dstMask,
		oldLayout:     oldLayout,
		newLayout:     newLayout,
		image:         img,
		subresourceRange: C.VkImageSubresourceRange{
			aspectMask: C.VK_IMAGE_ASPECT_COLOR_BIT,
			levelCount: C.VK_REMAINING_MIP_LEVELS,
			layerCount: C.VK_REMAINING_ARRAY_LAYERS,
		},
	}
}

func BuildBufferMemoryBarrier(buf Buffer, srcMask, dstMask AccessFlags) BufferMemoryBarrier {
	return C.VkBufferMemoryBarrier{
		sType:         C.VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
		srcAccessMask: srcMask,
		dstAccessMask: dstMask,
		buffer:        buf,
		size:          C.VK_WHOLE_SIZE,
	}
}

func BuildMemoryBarrier(srcMask, dstMask AccessFlags) MemoryBarrier {
	return C.VkMemoryBarrier{
		sType:         C.VK_STRUCTURE_TYPE_MEMORY_BARRIER,
		srcAccessMask: srcMask,
		dstAccessMask: dstMask,
	}
}

func BuildBufferImageCopy(bufOff, bufStride, x, y, width, height int) BufferImageCopy {
	return C.VkBufferImageCopy{
		bufferOffset:    C.VkDeviceSize(bufOff),
		bufferRowLength: C.uint32_t(bufStride),
		imageSubresource: C.VkImageSubresourceLayers{
			aspectMask: C.VK_IMAGE_ASPECT_COLOR_BIT,
			layerCount: 1,
		},
		imageOffset: C.VkOffset3D{
			x: C.int32_t(x), y: C.int32_t(y), z: 0,
		},
		imageExtent: C.VkExtent3D{
			width: C.uint32_t(width), height: C.uint32_t(height), depth: 1,
		},
	}
}

func BuildImageCopy(srcX, srcY, dstX, dstY, width, height int) ImageCopy {
	return C.VkImageCopy{
		srcSubresource: C.VkImageSubresourceLayers{
			aspectMask: C.VK_IMAGE_ASPECT_COLOR_BIT,
			layerCount: 1,
		},
		srcOffset: C.VkOffset3D{
			x: C.int32_t(srcX),
			y: C.int32_t(srcY),
		},
		dstSubresource: C.VkImageSubresourceLayers{
			aspectMask: C.VK_IMAGE_ASPECT_COLOR_BIT,
			layerCount: 1,
		},
		dstOffset: C.VkOffset3D{
			x: C.int32_t(dstX),
			y: C.int32_t(dstY),
		},
		extent: C.VkExtent3D{
			width:  C.uint32_t(width),
			height: C.uint32_t(height),
			depth:  1,
		},
	}
}

func findMemoryTypeIndex(pd C.VkPhysicalDevice, constraints C.uint32_t, wantProps C.VkMemoryPropertyFlags) (int, bool) {
	var memProps C.VkPhysicalDeviceMemoryProperties
	C.vkGetPhysicalDeviceMemoryProperties(funcs.vkGetPhysicalDeviceMemoryProperties, pd, &memProps)

	for i := 0; i < int(memProps.memoryTypeCount); i++ {
		if (constraints & (1 << i)) == 0 {
			continue
		}
		if (memProps.memoryTypes[i].propertyFlags & wantProps) != wantProps {
			continue
		}
		return i, true
	}

	return 0, false
}

func choosePresentMode(pd C.VkPhysicalDevice, surf Surface) (C.VkPresentModeKHR, bool, error) {
	var count C.uint32_t
	err := vkErr(C.vkGetPhysicalDeviceSurfacePresentModesKHR(funcs.vkGetPhysicalDeviceSurfacePresentModesKHR, pd, surf, &count, nil))
	if err != nil {
		return 0, false, fmt.Errorf("vulkan: vkGetPhysicalDeviceSurfacePresentModesKHR: %w", err)
	}
	if count == 0 {
		return 0, false, nil
	}
	modes := make([]C.VkPresentModeKHR, count)
	err = vkErr(C.vkGetPhysicalDeviceSurfacePresentModesKHR(funcs.vkGetPhysicalDeviceSurfacePresentModesKHR, pd, surf, &count, &modes[0]))
	if err != nil {
		return 0, false, fmt.Errorf("vulkan: kGetPhysicalDeviceSurfacePresentModesKHR: %w", err)
	}
	for _, m := range modes {
		if m == C.VK_PRESENT_MODE_MAILBOX_KHR || m == C.VK_PRESENT_MODE_FIFO_KHR {
			return m, true, nil
		}
	}
	return 0, false, nil
}

func chooseFormat(pd C.VkPhysicalDevice, surf Surface) (C.VkSurfaceFormatKHR, bool, error) {
	var count C.uint32_t
	err := vkErr(C.vkGetPhysicalDeviceSurfaceFormatsKHR(funcs.vkGetPhysicalDeviceSurfaceFormatsKHR, pd, surf, &count, nil))
	if err != nil {
		return C.VkSurfaceFormatKHR{}, false, fmt.Errorf("vulkan: vkGetPhysicalDeviceSurfaceFormatsKHR: %w", err)
	}
	if count == 0 {
		return C.VkSurfaceFormatKHR{}, false, nil
	}
	formats := make([]C.VkSurfaceFormatKHR, count)
	err = vkErr(C.vkGetPhysicalDeviceSurfaceFormatsKHR(funcs.vkGetPhysicalDeviceSurfaceFormatsKHR, pd, surf, &count, &formats[0]))
	if err != nil {
		return C.VkSurfaceFormatKHR{}, false, fmt.Errorf("vulkan: vkGetPhysicalDeviceSurfaceFormatsKHR: %w", err)
	}
	// Query for format with sRGB support.
	// TODO: Support devices without sRGB.
	for _, f := range formats {
		if f.colorSpace != C.VK_COLOR_SPACE_SRGB_NONLINEAR_KHR {
			continue
		}
		switch f.format {
		case C.VK_FORMAT_B8G8R8A8_SRGB, C.VK_FORMAT_R8G8B8A8_SRGB:
			return f, true, nil
		}
	}
	return C.VkSurfaceFormatKHR{}, false, nil
}

func chooseQueue(pd C.VkPhysicalDevice, surf Surface, flags C.VkQueueFlags) (int, bool, error) {
	queues := GetPhysicalDeviceQueueFamilyProperties(pd)
	for i, q := range queues {
		// Check for presentation and feature support.
		if q.queueFlags&flags != flags {
			continue
		}
		if surf != nilSurface {
			// Check for presentation support. It is possible that a device has no
			// queue with both rendering and presentation support, but not in reality.
			// See https://github.com/KhronosGroup/Vulkan-Docs/issues/1234.
			var support C.VkBool32
			if err := vkErr(C.vkGetPhysicalDeviceSurfaceSupportKHR(funcs.vkGetPhysicalDeviceSurfaceSupportKHR, pd, C.uint32_t(i), surf, &support)); err != nil {
				return 0, false, fmt.Errorf("vulkan: vkGetPhysicalDeviceSurfaceSupportKHR: %w", err)
			}
			if support != C.VK_TRUE {
				continue
			}
		}
		return i, true, nil
	}
	return 0, false, nil
}

func dlsym(handle unsafe.Pointer, s string) unsafe.Pointer {
	cs := C.CString(s)
	defer C.free(unsafe.Pointer(cs))
	return C.dlsym(handle, cs)
}

func dlopen(lib string) unsafe.Pointer {
	clib := C.CString(lib)
	defer C.free(unsafe.Pointer(clib))
	return C.dlopen(clib, C.RTLD_NOW|C.RTLD_LOCAL)
}

func vkErr(res C.VkResult) error {
	switch res {
	case C.VK_SUCCESS:
		return nil
	default:
		return Error(res)
	}
}

func (e Error) Error() string {
	return fmt.Sprintf("error %d", e)
}
