vulkan-playground/vulkan_utl.h

548 lines
18 KiB
C

/*
* 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 <vulkan/vulkan.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
// 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
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
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;
}
VkResult Create(const char *engine, const char *app, bool enableValidationLayers)
{
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[16];
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";
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 = vkCreateInstance(&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;
}
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;
// Returns the index of a queue family that supports compute operations.
uint32_t GetQueueFamilyIndex(VkQueueFlags bits) {
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 = 0;
for (; i < queueFamilyCount; ++i) {
VkQueueFamilyProperties props = queueFamilies[i];
if (props.queueCount > 0 && (props.queueFlags & bits)) {
// found a queue with compute. We're done!
break;
}
}
if (i == queueFamilyCount) {
printf("could not find a queue family that supports operations\n");
return 0;
}
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);
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", modifiers[i].drmFormatModifier, modifiers[i].drmFormatModifierPlaneCount, (int)modifiers[i].drmFormatModifierTilingFeatures);
}
return count;
}
VkResult CreateDevice(VulkanContext &ctx)
{
const float defaultQueuePriority(1.0f);
VkDeviceQueueCreateInfo queue_info {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = defaultFamilyIndex,
.queueCount = 1,
.pQueuePriorities = &defaultQueuePriority,
};
const char *deviceExtensions[16];
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;
}
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);
VkPhysicalDeviceFeatures enabled_features = {
.samplerAnisotropy = VK_TRUE
};
VkPhysicalDeviceSamplerYcbcrConversionFeatures ycbcrFeat = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES, .samplerYcbcrConversion = VK_TRUE};
VkDeviceCreateInfo device_info = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pNext = &ycbcrFeat,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &queue_info,
.enabledLayerCount = ctx.enabledLayersCount,
.ppEnabledLayerNames = ctx.enabledLayers,
.enabledExtensionCount = deviceExtensionsCount,
.ppEnabledExtensionNames = (const char *const *)deviceExtensions,
.pEnabledFeatures = &enabled_features,
};
VkResult result =
vkCreateDevice(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);
return result;
}
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 = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
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