/* * Copyright (c) 2017 Eric Arnebäck * Copyright (c) 2017-2019 Collabora Ltd. * Copyright (c) 2024 mittorn * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sub license, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial portions * of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. * IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef VULKAN_UTL_H #define VULKAN_UTL_H #include #include #include #include #include #include #include // Used for validating return values of Vulkan API calls. #ifndef VK_CHECK_RESULT #define VK_CHECK_RESULT(f) \ { \ VkResult res = (f); \ if (res != VK_SUCCESS) \ { \ printf("Fatal : VkResult is %d in %s at line %d\n", res, __FILE__, __LINE__); \ assert(res == VK_SUCCESS); \ } \ } #endif #ifndef ARRAY_SIZE #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) #endif struct VulkanContext { VkInstance instance = NULL; const char * enabledLayers[16]; uint32_t enabledLayersCount = 0; VkDebugReportCallbackEXT debugCallbackHandle = NULL; static VKAPI_ATTR VkBool32 VKAPI_CALL DebugReportCallbackFn( VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData) { printf("Debug Report: %s: %s\n", pLayerPrefix, pMessage); //_exit(1); return VK_FALSE; } template VkResult CallCreateInstance(const char *engine, const char *app, bool enableValidationLayers, const char **pExtensions, Func instanceCallback) { VkApplicationInfo app_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = app, .applicationVersion = 0, .pEngineName = engine, .engineVersion = 0, .apiVersion = VK_MAKE_VERSION(1, 0, 2), }; const char * enabledExtensions[256]; uint32_t enabledExtensionsCount = 0; enabledExtensions[enabledExtensionsCount++] = VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME; enabledExtensions[enabledExtensionsCount++] = VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME; enabledExtensions[enabledExtensionsCount++] = VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME; enabledExtensions[enabledExtensionsCount++] = VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; enabledExtensions[enabledExtensionsCount++] = VK_KHR_SURFACE_EXTENSION_NAME; enabledExtensions[enabledExtensionsCount++] = "VK_KHR_xlib_surface"; while(pExtensions && *pExtensions) enabledExtensions[enabledExtensionsCount++] = *pExtensions++; if (enableValidationLayers) { /* We get all supported layers with vkEnumerateInstanceLayerProperties. */ uint32_t layerCount; vkEnumerateInstanceLayerProperties(&layerCount, NULL); VkLayerProperties layerProperties[layerCount]; vkEnumerateInstanceLayerProperties(&layerCount, layerProperties); /* And then we simply check if VK_LAYER_LUNARG_standard_validation is among the supported layers. */ bool foundLayer = false; for (VkLayerProperties prop : layerProperties) { if (strcmp("VK_LAYER_KHRONOS_validation", prop.layerName) == 0) { foundLayer = true; break; } } if (!foundLayer) { printf("Layer VK_LAYER_LUNARG_standard_validation not supported\n"); } else enabledLayers[enabledLayersCount++] = "VK_LAYER_KHRONOS_validation"; // Alright, we can use this layer. /* We need to enable an extension named VK_EXT_DEBUG_REPORT_EXTENSION_NAME, in order to be able to print the warnings emitted by the validation layer. So again, we just check if the extension is among the supported extensions. */ uint32_t extensionCount; vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, NULL); VkExtensionProperties extensionProperties[extensionCount]; vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, extensionProperties); bool foundExtension = false; for (VkExtensionProperties prop : extensionProperties) { if (strcmp(VK_EXT_DEBUG_REPORT_EXTENSION_NAME, prop.extensionName) == 0) { foundExtension = true; break; } } if (!foundExtension) { printf("Extension VK_EXT_DEBUG_REPORT_EXTENSION_NAME not supported\n"); } else enabledExtensions[enabledExtensionsCount++] = VK_EXT_DEBUG_REPORT_EXTENSION_NAME; } VkInstanceCreateInfo instance_info = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app_info, .enabledLayerCount = enabledLayersCount, .ppEnabledLayerNames = enabledLayers, .enabledExtensionCount = enabledExtensionsCount, .ppEnabledExtensionNames = (const char* const*)enabledExtensions, }; VkResult res1 = instanceCallback(&instance_info, NULL, &instance); VK_CHECK_RESULT(res1); if (enableValidationLayers) { VkDebugReportCallbackCreateInfoEXT createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT; createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT; createInfo.pfnCallback = &DebugReportCallbackFn; // We have to explicitly load this function. auto vkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); if (vkCreateDebugReportCallbackEXT != nullptr) { if(vkCreateDebugReportCallbackEXT(instance, &createInfo, NULL, &debugCallbackHandle) != VK_SUCCESS) printf("Failed to register debug callback\n"); } else printf("Failed to register debug callback: null function\n"); } return res1; } VkResult Create(const char *engine, const char *app, bool enableValidationLayers, const char **ppExtensions = NULL) { return CallCreateInstance(engine, app, enableValidationLayers, ppExtensions, vkCreateInstance); } VkPhysicalDevice FindPhysicalDevice() { uint32_t deviceCount; vkEnumeratePhysicalDevices(instance, &deviceCount, NULL); if (deviceCount == 0) { printf("could not find a device with vulkan support\n"); return NULL; } VkPhysicalDevice devices[deviceCount]; vkEnumeratePhysicalDevices(instance, &deviceCount, devices); for (VkPhysicalDevice device : devices) { if (true) { // accept any device for now return device; } } return NULL; } void Destroy() { if (debugCallbackHandle) { // destroy callback. auto func = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); if (func == nullptr) { printf("Could not load vkDestroyDebugReportCallbackEXT\n"); //return; } else func(instance, debugCallbackHandle, NULL); debugCallbackHandle = NULL; } vkDestroyInstance(instance, NULL); } }; struct VulkanBuffer { VkDevice device; VkBuffer buffer = NULL; VkDeviceMemory memory = NULL; VkDescriptorBufferInfo descriptor; VkDeviceSize size; VkDeviceSize alignment; void *mapped = NULL; VkBufferUsageFlags usage_flags; VkMemoryPropertyFlags memory_property_flags; VkResult Map() { VkDeviceSize size = VK_WHOLE_SIZE; VkDeviceSize offset = 0; return vkMapMemory(device, memory, offset, size, 0,&mapped); } void Unmap() { if (mapped) { vkUnmapMemory(device, memory); mapped = NULL; } } VkResult Bind() { return vkBindBufferMemory(device, buffer, memory, 0); } void SetupDescriptor() { descriptor = VkDescriptorBufferInfo{ .buffer = buffer, .offset = 0, .range = VK_WHOLE_SIZE, }; } void Destroy() { if (buffer) vkDestroyBuffer(device, buffer, NULL); if (memory) vkFreeMemory(device, memory, NULL); } }; struct VulkanDevice { VkPhysicalDevice physicalDevice = NULL; VkDevice device = NULL; VkPhysicalDeviceProperties properties; VkPhysicalDeviceFeatures features; VkPhysicalDeviceMemoryProperties memoryProperties; VkCommandPool cmd_pool = NULL; uint32_t defaultFamilyIndex; VkQueue defaultQueue; int defaultQueueIndex = -1; // Returns the index of a queue family that supports compute operations. uint32_t GetQueueFamilyIndex(VkQueueFlags bits, bool ignoreDefault = false, int ignoreFamilyIndex = -1, int ignoreQueueIndex = -1, uint32_t skipQueues = 0, uint32_t *pQueueIndex = NULL) { uint32_t queueFamilyCount; vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, NULL); // Retrieve all queue families. VkQueueFamilyProperties queueFamilies[queueFamilyCount]; vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilies); // Now find a family that supports compute. uint32_t i; int j; for ( i = 0; i < queueFamilyCount; ++i) { VkQueueFamilyProperties &props = queueFamilies[i]; if (props.queueCount > 0 && (props.queueFlags & bits)) { for(j = 0; j < props.queueCount; ++j) { if(ignoreDefault && i == defaultFamilyIndex && j == defaultQueueIndex) continue; if(i == ignoreFamilyIndex && j == ignoreQueueIndex) continue; if(skipQueues && (--skipQueues)) continue; break; } if(j == props.queueCount) continue; break; } } if (i == queueFamilyCount) { printf("could not find a queue family that supports operations\n"); if(pQueueIndex) *pQueueIndex = 0; return defaultFamilyIndex; } if(pQueueIndex) *pQueueIndex = j; return i; } bool Create(VkPhysicalDevice physical_device_, VkQueueFlags defaultQueueBits) { physicalDevice = physical_device_; assert(physicalDevice); vkGetPhysicalDeviceProperties(physicalDevice, &properties); vkGetPhysicalDeviceFeatures(physicalDevice, &features); vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties); defaultFamilyIndex = GetQueueFamilyIndex(defaultQueueBits); defaultQueueIndex = 0; // todo: add some invalid index value if(!defaultFamilyIndex) return false; cmd_pool = NULL; return true; } void Destroy() { if (cmd_pool) vkDestroyCommandPool(device, cmd_pool, NULL); cmd_pool = NULL; if (device) vkDestroyDevice(device, NULL); device = NULL; } bool GetMemoryType(uint32_t typeBits, VkMemoryPropertyFlags properties, uint32_t *out_index) { for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; i++) { if ((typeBits & 1) == 1) { if ((memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) { *out_index = i; return true; } } typeBits >>= 1; } *out_index = 0; return false; } int GetAvailiableModifiersList(uint64_t *modifiers2, size_t len, VkFormat fmt) { VkDrmFormatModifierPropertiesEXT modifiers[len]; VkDrmFormatModifierPropertiesListEXT formatList = {VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT}; VkFormatProperties2 prop = {VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2}; prop.pNext = &formatList; formatList.drmFormatModifierCount = len; formatList.pDrmFormatModifierProperties = modifiers; int count = 0; vkGetPhysicalDeviceFormatProperties2(physicalDevice, fmt, &prop); for(int i = 0; i < formatList.drmFormatModifierCount; i++) { modifiers2[count++] = modifiers[i].drmFormatModifier; printf("mod %llx %d %d\n", (unsigned long long)modifiers[i].drmFormatModifier, modifiers[i].drmFormatModifierPlaneCount, (int)modifiers[i].drmFormatModifierTilingFeatures); } return count; } template VkResult CallCreateDeviceCallback(VulkanContext &ctx, VkDeviceQueueCreateInfo *pQueueInfo, uint32_t queueInfoCount, const char **pExtensions, Func createCallback) { const float defaultQueuePriority(1.0f); VkDeviceQueueCreateInfo queue_info { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = defaultFamilyIndex, .queueCount = 1, .pQueuePriorities = &defaultQueuePriority, }; if(!pQueueInfo) { pQueueInfo = &queue_info; queueInfoCount = 1; } const char *deviceExtensions[256]; uint32_t deviceExtensionsCount = 0; uint32_t avCount = 0; vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &avCount, NULL); VkExtensionProperties props[avCount]; vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &avCount, props); auto addExtension = [&](const char *Name){ int j; for(j = 0; j < avCount; j++) { if(!strcmp(Name,props[j].extensionName)) break; } printf("Adding %s\n", Name); if(j == avCount) printf("Extension %s not availaible\n", Name); else deviceExtensions[deviceExtensionsCount++] = Name; }; addExtension(VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME); //addExtension(VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME); addExtension(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME); addExtension(VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME); addExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME); addExtension(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME); addExtension(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME); addExtension(VK_KHR_MAINTENANCE1_EXTENSION_NAME); addExtension(VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME); addExtension(VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME); addExtension(VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME); addExtension(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME); addExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME); addExtension(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME); while(pExtensions && *pExtensions) addExtension(*pExtensions++); VkPhysicalDeviceFeatures enabled_features = { .samplerAnisotropy = VK_TRUE }; VkPhysicalDeviceSamplerYcbcrConversionFeatures ycbcrFeat = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, NULL, VK_TRUE}; VkPhysicalDeviceTimelineSemaphoreFeaturesKHR featTimelineSem{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES_KHR, &ycbcrFeat, VK_TRUE}; VkDeviceCreateInfo device_info = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pNext = &featTimelineSem, .queueCreateInfoCount = queueInfoCount, .pQueueCreateInfos = pQueueInfo, .enabledLayerCount = ctx.enabledLayersCount, .ppEnabledLayerNames = ctx.enabledLayers, .enabledExtensionCount = deviceExtensionsCount, .ppEnabledExtensionNames = (const char *const *)deviceExtensions, .pEnabledFeatures = &enabled_features, }; VkResult result = createCallback(physicalDevice, &device_info, NULL, &device); if (result != VK_SUCCESS) { printf("Could not create device.\n"); return result; } // Get a handle to the only member of the queue family. vkGetDeviceQueue(device, defaultFamilyIndex, 0, &defaultQueue); defaultQueueIndex = 0; return result; } VkResult CreateDevice(VulkanContext &ctx, VkDeviceQueueCreateInfo *pQueueInfo = NULL, uint32_t queueInfoCount = 0, const char** pExtensions = NULL) { return CallCreateDeviceCallback(ctx, pQueueInfo, queueInfoCount, pExtensions, vkCreateDevice); } VkResult CreateBuffer(VulkanBuffer &buffer, VkBufferUsageFlags usage, VkMemoryPropertyFlags memory_flags, VkDeviceSize size, const void *data = NULL) { buffer.device = device; // Create the buffer handle VkBufferCreateInfo bufferCreateInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; bufferCreateInfo.size = size; bufferCreateInfo.usage = usage; VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, NULL, &buffer.buffer)); // Create the memory backing up the buffer handle VkMemoryRequirements memReqs; vkGetBufferMemoryRequirements(device, buffer.buffer, &memReqs); VkMemoryAllocateInfo memAlloc = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; memAlloc.allocationSize = memReqs.size; // Find a memory type index that fits the properties of the buffer if (!GetMemoryType(memReqs.memoryTypeBits, memory_flags, &memAlloc.memoryTypeIndex)) printf("Could not find memory type.\n"); VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, NULL, &buffer.memory)); buffer.alignment = memReqs.alignment; buffer.size = memAlloc.allocationSize; buffer.usage_flags = usage; buffer.memory_property_flags = memory_flags; // If a pointer to the buffer data has been passed, map the buffer and copy // over the data if (data != NULL) { VK_CHECK_RESULT(buffer.Map()); memcpy(buffer.mapped, data, size); buffer.Unmap(); } // Initialize a default descriptor that covers the whole buffer size buffer.SetupDescriptor(); // Attach the memory to the buffer object return buffer.Bind(); } void CreateAndMap(VulkanBuffer &buffer, VkDeviceSize size) { VkMemoryPropertyFlags memory_flags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; VK_CHECK_RESULT(CreateBuffer(buffer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, memory_flags, size, NULL)); // Map persistent VK_CHECK_RESULT(buffer.Map()); } VkCommandPool _CreateCommandPool() { VkCommandPoolCreateFlags createFlags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; VkCommandPoolCreateInfo cmdPoolInfo = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; cmdPoolInfo.flags = createFlags; cmdPoolInfo.queueFamilyIndex = defaultFamilyIndex; VkCommandPool cmdPool; VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, NULL, &cmdPool)); return cmdPool; } VkCommandBuffer CreateCommandBuffer() { if (!cmd_pool) { cmd_pool = _CreateCommandPool(); }; VkCommandBufferAllocateInfo cmdBufAllocateInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; cmdBufAllocateInfo.commandPool = cmd_pool; cmdBufAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cmdBufAllocateInfo.commandBufferCount = 1; VkCommandBuffer cmdBuffer; VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &cmdBuffer)); VkCommandBufferBeginInfo cmdBufInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffer, &cmdBufInfo)); return cmdBuffer; } void FlushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue) { if (commandBuffer == VK_NULL_HANDLE) return; if (!cmd_pool) return; VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer)); VkSubmitInfo submitInfo = {VK_STRUCTURE_TYPE_SUBMIT_INFO}; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; // Create fence to ensure that the command buffer has finished executing VkFenceCreateInfo fenceInfo = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; VkFence fence; VK_CHECK_RESULT(vkCreateFence(device, &fenceInfo, NULL, &fence)); // Submit to the queue VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, fence)); // Wait for the fence to signal that command buffer has finished executing VK_CHECK_RESULT(vkWaitForFences(device, 1, &fence, VK_TRUE, INT64_MAX)); vkDestroyFence(device, fence, NULL); vkFreeCommandBuffers(device, cmd_pool, 1, &commandBuffer); } }; #endif // VULKAN_UTL_H