do not edit — generated by btf.
git.druid.rocksindexmilitantprogrammerastral_canvassrc/render/vk.c

src/render/vk.c


#include "render/vk.h"
 
#include <stdlib.h>
#include <string.h>
 
static const char *khrSurfaceExtensionName = VK_KHR_SURFACE_EXTENSION_NAME;
static const char *khrSwapchainExtensionName = VK_KHR_SWAPCHAIN_EXTENSION_NAME;
static const char *debugUtilsExtensionName = VK_EXT_DEBUG_UTILS_EXTENSION_NAME;
static const char *validationLayerName = "VK_LAYER_KHRONOS_validation";
 
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
    VkDebugUtilsMessageSeverityFlagBitsEXT severity,
    VkDebugUtilsMessageTypeFlagsEXT types,
    const VkDebugUtilsMessengerCallbackDataEXT *callbackData,
    void *userData)
{
    (void)types;
    (void)userData;
    if (severity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT)
        fprintf(stderr, "vulkan: %s\n", callbackData->pMessage);
    return VK_FALSE;
}
 
static bool hasExtension(const VkExtensionProperties *extensions, uint32_t extensionCount, const char *name)
{
    for (uint32_t i = 0; i < extensionCount; i++) {
        if (strcmp(extensions[i].extensionName, name) == 0)
            return true;
    }
    return false;
}
 
static bool hasLayer(const VkLayerProperties *layers, uint32_t layerCount, const char *name)
{
    for (uint32_t i = 0; i < layerCount; i++) {
        if (strcmp(layers[i].layerName, name) == 0)
            return true;
    }
    return false;
}
 
int vk_find_memory_type(VkPhysicalDevice physicalDevice, uint32_t filter, VkMemoryPropertyFlags properties)
{
    VkPhysicalDeviceMemoryProperties memoryProperties;
    vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
    for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; i++) {
        if ((filter & (1u << i)) && (memoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
            return (int)i;
    }
    return -1;
}
 
int vk_create_host_buffer(VkContext *vulkan, VkDeviceSize size, VkBufferUsageFlags usage,
                            VkBuffer *buffer, VkDeviceMemory *memory, void **mapped)
{
    VkBufferCreateInfo bufferCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
        .size = size,
        .usage = usage,
        .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
    };
 
    VK_TRY(vkCreateBuffer(vulkan->device, &bufferCreateInfo, NULL, buffer));
 
    VkMemoryRequirements memoryRequirements;
    vkGetBufferMemoryRequirements(vulkan->device, *buffer, &memoryRequirements);
    int memoryTypeIndex = vk_find_memory_type(vulkan->physicalDevice, memoryRequirements.memoryTypeBits,
                                         VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
 
    if (memoryTypeIndex < 0)
        return -1;
 
    VkMemoryAllocateInfo allocateInfo = {
        .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
        .allocationSize = memoryRequirements.size,
        .memoryTypeIndex = (uint32_t)memoryTypeIndex,
    };
    VK_TRY(vkAllocateMemory(vulkan->device, &allocateInfo, NULL, memory));
    VK_TRY(vkBindBufferMemory(vulkan->device, *buffer, *memory, 0));
    if (mapped)
        VK_TRY(vkMapMemory(vulkan->device, *memory, 0, memoryRequirements.size, 0, mapped));
    return 0;
}
 
void vk_image_barrier(VkCommandBuffer commandBuffer, VkImage image,
                         VkImageLayout oldLayout, VkImageLayout newLayout,
                         VkAccessFlags sourceAccess, VkAccessFlags destinationAccess,
                         VkPipelineStageFlags sourceStage, VkPipelineStageFlags destinationStage)
{
    VkImageMemoryBarrier barrier = {
        .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
        .srcAccessMask = sourceAccess,
        .dstAccessMask = destinationAccess,
        .oldLayout = oldLayout,
        .newLayout = newLayout,
        .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
        .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
        .image = image,
        .subresourceRange = {
            .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
            .levelCount = 1,
            .layerCount = 1,
        },
    };
    vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0, NULL, 0, NULL, 1, &barrier);
}
 
static int loadInstanceExtensions(Platform *platform, bool *hasDebugUtils)
{
    uint32_t extensionCount = 0;
    vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, NULL);
    VkExtensionProperties *extensions = extensionCount
        ? (VkExtensionProperties *)calloc(extensionCount, sizeof(*extensions))
        : NULL;
    if (extensionCount)
        vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, extensions);
 
    if (!hasExtension(extensions, extensionCount, khrSurfaceExtensionName) ||
        !hasExtension(extensions, extensionCount, platform->vulkanSurfaceExtension)) {
        fprintf(stderr, "vulkan: missing %s or %s\n", khrSurfaceExtensionName, platform->vulkanSurfaceExtension);
        free(extensions);
        return -1;
    }
    *hasDebugUtils = hasExtension(extensions, extensionCount, debugUtilsExtensionName);
    free(extensions);
    return 0;
}
 
static bool loadValidationLayer(void)
{
    uint32_t layerCount = 0;
    vkEnumerateInstanceLayerProperties(&layerCount, NULL);
    VkLayerProperties *layers = layerCount
        ? (VkLayerProperties *)calloc(layerCount, sizeof(*layers))
        : NULL;
    if (layerCount)
        vkEnumerateInstanceLayerProperties(&layerCount, layers);
    bool hasValidationLayer = hasLayer(layers, layerCount, validationLayerName);
    free(layers);
    return hasValidationLayer;
}
 
static int createInstance(VkContext *vulkan, Platform *platform)
{
    bool hasValidationLayer = loadValidationLayer();
 
    bool hasDebugUtils = false;
    if (loadInstanceExtensions(platform, &hasDebugUtils) < 0)
        return -1;
 
    const char *instanceExtensions[4];
    uint32_t extensionIndex = 0;
    instanceExtensions[extensionIndex++] = khrSurfaceExtensionName;
    instanceExtensions[extensionIndex++] = platform->vulkanSurfaceExtension;
    if (hasDebugUtils)
        instanceExtensions[extensionIndex++] = debugUtilsExtensionName;
 
    const char *instanceLayers[1];
    uint32_t layerIndex = 0;
    if (hasValidationLayer)
        instanceLayers[layerIndex++] = validationLayerName;
 
    VkApplicationInfo applicationInfo = {
        .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
        .pApplicationName = "terminal",
        .applicationVersion = VK_MAKE_VERSION(0, 1, 0),
        .pEngineName = "terminal",
        .engineVersion = VK_MAKE_VERSION(0, 1, 0),
        .apiVersion = VK_API_VERSION_1_0,
    };
    VkDebugUtilsMessengerCreateInfoEXT debugMessengerCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
        .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
                           VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
        .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
                       VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
                       VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
        .pfnUserCallback = debugCallback,
    };
    VkInstanceCreateInfo instanceCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
        .pNext = hasDebugUtils ? &debugMessengerCreateInfo : NULL,
        .pApplicationInfo = &applicationInfo,
        .enabledLayerCount = layerIndex,
        .ppEnabledLayerNames = layerIndex ? instanceLayers : NULL,
        .enabledExtensionCount = extensionIndex,
        .ppEnabledExtensionNames = instanceExtensions,
    };
 
    VK_TRY(vkCreateInstance(&instanceCreateInfo, NULL, &vulkan->instance));
 
    if (hasDebugUtils) {
        PFN_vkCreateDebugUtilsMessengerEXT createDebugMessenger =
            (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
                vulkan->instance, "vkCreateDebugUtilsMessengerEXT");
        vulkan->destroyDebug =
            (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
                vulkan->instance, "vkDestroyDebugUtilsMessengerEXT");
        if (createDebugMessenger)
            createDebugMessenger(vulkan->instance, &debugMessengerCreateInfo, NULL, &vulkan->debug);
    }
    return 0;
}
 
static int pickQueues(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface,
                      uint32_t *graphicsFamily, uint32_t *presentFamily)
{
    uint32_t familyCount = 0;
    vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, NULL);
    if (!familyCount)
        return -1;
    VkQueueFamilyProperties *familyProperties =
        (VkQueueFamilyProperties *)calloc(familyCount, sizeof(*familyProperties));
    if (!familyProperties)
        return -1;
    vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &familyCount, familyProperties);
 
    *graphicsFamily = UINT32_MAX;
    *presentFamily = UINT32_MAX;
    for (uint32_t i = 0; i < familyCount; i++) {
        VkBool32 canPresent = VK_FALSE;
        vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, surface, &canPresent);
        bool canGraphics = (familyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0;
        if (canGraphics && canPresent) {
            *graphicsFamily = *presentFamily = i;
            free(familyProperties);
            return 0;
        }
        if (canGraphics && *graphicsFamily == UINT32_MAX)
            *graphicsFamily = i;
        if (canPresent && *presentFamily == UINT32_MAX)
            *presentFamily = i;
    }
    free(familyProperties);
    return (*graphicsFamily != UINT32_MAX && *presentFamily != UINT32_MAX) ? 0 : -1;
}
 
static bool deviceHasSwapchain(VkPhysicalDevice physicalDevice)
{
    uint32_t extensionCount = 0;
    vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &extensionCount, NULL);
    VkExtensionProperties *extensions = extensionCount
        ? (VkExtensionProperties *)calloc(extensionCount, sizeof(*extensions))
        : NULL;
    if (extensionCount)
        vkEnumerateDeviceExtensionProperties(physicalDevice, NULL, &extensionCount, extensions);
    bool ok = hasExtension(extensions, extensionCount, khrSwapchainExtensionName);
    free(extensions);
    return ok;
}
 
static int pickDevice(VkContext *vulkan)
{
    uint32_t deviceCount = 0;
    VK_TRY(vkEnumeratePhysicalDevices(vulkan->instance, &deviceCount, NULL));
    if (!deviceCount) {
        fprintf(stderr, "vulkan: no physical devices\n");
        return -1;
    }
    VkPhysicalDevice *devices = (VkPhysicalDevice *)calloc(deviceCount, sizeof(*devices));
    if (!devices)
        return -1;
    vkEnumeratePhysicalDevices(vulkan->instance, &deviceCount, devices);
 
    VkPhysicalDevice bestDevice = VK_NULL_HANDLE;
    uint32_t bestGraphicsFamily = 0, bestPresentFamily = 0;
    int bestScore = -1;
    for (uint32_t i = 0; i < deviceCount; i++) {
        if (!deviceHasSwapchain(devices[i]))
            continue;
        uint32_t graphicsFamily, presentFamily;
        if (pickQueues(devices[i], vulkan->surface, &graphicsFamily, &presentFamily) < 0)
            continue;
        VkPhysicalDeviceProperties deviceProperties;
        vkGetPhysicalDeviceProperties(devices[i], &deviceProperties);
        int score = 1;
        if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
            score = 3;
        else if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU)
            score = 2;
        if (score > bestScore) {
            bestScore = score;
            bestDevice = devices[i];
            bestGraphicsFamily = graphicsFamily;
            bestPresentFamily = presentFamily;
        }
    }
    free(devices);
    if (!bestDevice) {
        fprintf(stderr, "vulkan: no GPU can present to this surface\n");
        return -1;
    }
    vulkan->physicalDevice = bestDevice;
    vulkan->graphicsFamily = bestGraphicsFamily;
    vulkan->presentFamily = bestPresentFamily;
    return 0;
}
 
static int createDevice(VkContext *vulkan)
{
    float priority = 1.0f;
    VkDeviceQueueCreateInfo queueCreateInfos[2];
    uint32_t queueCreateCount = 0;
    queueCreateInfos[queueCreateCount++] = (VkDeviceQueueCreateInfo){
        .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
        .queueFamilyIndex = vulkan->graphicsFamily,
        .queueCount = 1,
        .pQueuePriorities = &priority,
    };
    if (vulkan->presentFamily != vulkan->graphicsFamily) {
        queueCreateInfos[queueCreateCount++] = (VkDeviceQueueCreateInfo){
            .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
            .queueFamilyIndex = vulkan->presentFamily,
            .queueCount = 1,
            .pQueuePriorities = &priority,
        };
    }
    VkPhysicalDeviceScalarBlockLayoutFeatures scalar = {
    .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES,
    .scalarBlockLayout = VK_TRUE,
      };
    VkDeviceCreateInfo deviceCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
        .pNext = &scalar,
        .queueCreateInfoCount = queueCreateCount,
        .pQueueCreateInfos = queueCreateInfos,
        .enabledExtensionCount = 1,
        .ppEnabledExtensionNames = &khrSwapchainExtensionName,
    };
    VK_TRY(vkCreateDevice(vulkan->physicalDevice, &deviceCreateInfo, NULL, &vulkan->device));
    vkGetDeviceQueue(vulkan->device, vulkan->graphicsFamily, 0, &vulkan->graphicsQueue);
    vkGetDeviceQueue(vulkan->device, vulkan->presentFamily, 0, &vulkan->presentQueue);
    return 0;
}
 
static VkSurfaceFormatKHR pickFormat(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface)
{
    uint32_t formatCount = 0;
    vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, NULL);
    VkSurfaceFormatKHR fallback = {
        .format = VK_FORMAT_B8G8R8A8_UNORM,
        .colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
    };
    if (!formatCount)
        return fallback;
    VkSurfaceFormatKHR *formats = (VkSurfaceFormatKHR *)calloc(formatCount, sizeof(*formats));
    if (!formats)
        return fallback;
    vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, formats);
 
    VkSurfaceFormatKHR pick = formats[0];
    if (formatCount == 1 && formats[0].format == VK_FORMAT_UNDEFINED) {
        pick = fallback;
    } else {
        for (uint32_t i = 0; i < formatCount; i++) {
            if (formats[i].format == VK_FORMAT_B8G8R8A8_UNORM &&
                formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
                pick = formats[i];
                break;
            }
        }
    }
    free(formats);
    return pick;
}
 
static VkPresentModeKHR pickPresentMode(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface)
{
    uint32_t modeCount = 0;
    vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &modeCount, NULL);
    VkPresentModeKHR *presentModes = modeCount
        ? (VkPresentModeKHR *)calloc(modeCount, sizeof(*presentModes))
        : NULL;
    if (modeCount)
        vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &modeCount, presentModes);
    VkPresentModeKHR pick = VK_PRESENT_MODE_FIFO_KHR;
    for (uint32_t i = 0; i < modeCount; i++) {
        if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR) {
            pick = presentModes[i];
            break;
        }
    }
    free(presentModes);
    return pick;
}
 
static void destroySwapchain(VkContext *vulkan)
{
    if (!vulkan->device)
        return;
    for (uint32_t i = 0; i < vulkan->swapchainImageCount; i++) {
        if (vulkan->swapchainFramebuffers[i])
            vkDestroyFramebuffer(vulkan->device, vulkan->swapchainFramebuffers[i], NULL);
        if (vulkan->swapchainViews[i])
            vkDestroyImageView(vulkan->device, vulkan->swapchainViews[i], NULL);
        vulkan->swapchainFramebuffers[i] = VK_NULL_HANDLE;
        vulkan->swapchainViews[i] = VK_NULL_HANDLE;
        vulkan->swapchainImages[i] = VK_NULL_HANDLE;
        vulkan->swapchainBusyFences[i] = VK_NULL_HANDLE;
    }
    vulkan->swapchainImageCount = 0;
    if (vulkan->renderPass) {
        vkDestroyRenderPass(vulkan->device, vulkan->renderPass, NULL);
        vulkan->renderPass = VK_NULL_HANDLE;
    }
    if (vulkan->swapchain) {
        vkDestroySwapchainKHR(vulkan->device, vulkan->swapchain, NULL);
        vulkan->swapchain = VK_NULL_HANDLE;
    }
}
 
static int createRenderPass(VkContext *vulkan)
{
    VkAttachmentDescription colorAttachment = {
        .format = vulkan->swapchainFormat,
        .samples = VK_SAMPLE_COUNT_1_BIT,
        .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
        .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
        .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
        .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
        .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
        .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
    };
    VkAttachmentReference colorAttachmentReference = {
        .attachment = 0,
        .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
    };
    VkSubpassDescription subpass = {
        .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
        .colorAttachmentCount = 1,
        .pColorAttachments = &colorAttachmentReference,
    };
    VkSubpassDependency dependency = {
        .srcSubpass = VK_SUBPASS_EXTERNAL,
        .dstSubpass = 0,
        .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
        .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
        .srcAccessMask = 0,
        .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
    };
    VkRenderPassCreateInfo renderPassCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
        .attachmentCount = 1,
        .pAttachments = &colorAttachment,
        .subpassCount = 1,
        .pSubpasses = &subpass,
        .dependencyCount = 1,
        .pDependencies = &dependency,
    };
    VK_TRY(vkCreateRenderPass(vulkan->device, &renderPassCreateInfo, NULL, &vulkan->renderPass));
    return 0;
}
 
static int createSwapchain(VkContext *vulkan, Platform *platform)
{
    VkSurfaceCapabilitiesKHR surfaceCapabilities;
    VK_TRY(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
        vulkan->physicalDevice, vulkan->surface, &surfaceCapabilities));
 
    uint32_t wantedWidth = (uint32_t)(platform->bufferWidth > 0 ? platform->bufferWidth : 1);
    uint32_t wantedHeight = (uint32_t)(platform->bufferHeight > 0 ? platform->bufferHeight : 1);
    if (surfaceCapabilities.currentExtent.width != UINT32_MAX) {
        wantedWidth = surfaceCapabilities.currentExtent.width;
        wantedHeight = surfaceCapabilities.currentExtent.height;
    }
    if (wantedWidth < surfaceCapabilities.minImageExtent.width)
        wantedWidth = surfaceCapabilities.minImageExtent.width;
    if (wantedHeight < surfaceCapabilities.minImageExtent.height)
        wantedHeight = surfaceCapabilities.minImageExtent.height;
    if (surfaceCapabilities.maxImageExtent.width > 0 && wantedWidth > surfaceCapabilities.maxImageExtent.width)
        wantedWidth = surfaceCapabilities.maxImageExtent.width;
    if (surfaceCapabilities.maxImageExtent.height > 0 && wantedHeight > surfaceCapabilities.maxImageExtent.height)
        wantedHeight = surfaceCapabilities.maxImageExtent.height;
    if (wantedWidth < 1)
        wantedWidth = 1;
    if (wantedHeight < 1)
        wantedHeight = 1;
 
    uint32_t imageCount = surfaceCapabilities.minImageCount + 1;
    if (surfaceCapabilities.maxImageCount > 0 && imageCount > surfaceCapabilities.maxImageCount)
        imageCount = surfaceCapabilities.maxImageCount;
    if (imageCount > VK_MAX_SWAPCHAIN_IMAGES)
        imageCount = VK_MAX_SWAPCHAIN_IMAGES;
 
    VkSurfaceFormatKHR surfaceFormat = pickFormat(vulkan->physicalDevice, vulkan->surface);
    VkPresentModeKHR presentMode = pickPresentMode(vulkan->physicalDevice, vulkan->surface);
 
    uint32_t queueFamilies[2] = { vulkan->graphicsFamily, vulkan->presentFamily };
    bool separateQueues = vulkan->graphicsFamily != vulkan->presentFamily;
 
    VkSwapchainKHR oldSwapchain = vulkan->swapchain;
    vulkan->swapchain = VK_NULL_HANDLE;
 
    VkSwapchainCreateInfoKHR swapchainCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
        .surface = vulkan->surface,
        .minImageCount = imageCount,
        .imageFormat = surfaceFormat.format,
        .imageColorSpace = surfaceFormat.colorSpace,
        .imageExtent = { wantedWidth, wantedHeight },
        .imageArrayLayers = 1,
        .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
        .imageSharingMode = separateQueues ? VK_SHARING_MODE_CONCURRENT : VK_SHARING_MODE_EXCLUSIVE,
        .queueFamilyIndexCount = separateQueues ? 2 : 0,
        .pQueueFamilyIndices = separateQueues ? queueFamilies : NULL,
        .preTransform = surfaceCapabilities.currentTransform,
        .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
        .presentMode = presentMode,
        .clipped = VK_TRUE,
        .oldSwapchain = oldSwapchain,
    };
    if (!(surfaceCapabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR)) {
        if (surfaceCapabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
            swapchainCreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
        else if (surfaceCapabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR)
            swapchainCreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR;
        else if (surfaceCapabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR)
            swapchainCreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR;
    }
 
    VkResult result = vkCreateSwapchainKHR(
        vulkan->device, &swapchainCreateInfo, NULL, &vulkan->swapchain);
    if (oldSwapchain)
        vkDestroySwapchainKHR(vulkan->device, oldSwapchain, NULL);
    if (result != VK_SUCCESS) {
        fprintf(stderr, "vulkan: vkCreateSwapchainKHR failed (%d)\n", (int)result);
        return -1;
    }
 
    vulkan->swapchainFormat = surfaceFormat.format;
    vulkan->extent = swapchainCreateInfo.imageExtent;
 
    uint32_t gotImageCount = 0;
    VK_TRY(vkGetSwapchainImagesKHR(vulkan->device, vulkan->swapchain, &gotImageCount, NULL));
    if (gotImageCount > VK_MAX_SWAPCHAIN_IMAGES) {
        fprintf(stderr, "vulkan: swapchain image count %u > %d\n", gotImageCount, VK_MAX_SWAPCHAIN_IMAGES);
        return -1;
    }
    VK_TRY(vkGetSwapchainImagesKHR(
        vulkan->device, vulkan->swapchain, &gotImageCount, vulkan->swapchainImages));
    vulkan->swapchainImageCount = gotImageCount;
 
    if (createRenderPass(vulkan) < 0)
        return -1;
 
    for (uint32_t i = 0; i < vulkan->swapchainImageCount; i++) {
        VkImageViewCreateInfo imageViewCreateInfo = {
            .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
            .image = vulkan->swapchainImages[i],
            .viewType = VK_IMAGE_VIEW_TYPE_2D,
            .format = vulkan->swapchainFormat,
            .subresourceRange = {
                .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
                .levelCount = 1,
                .layerCount = 1,
            },
        };
        VK_TRY(vkCreateImageView(
            vulkan->device, &imageViewCreateInfo, NULL, &vulkan->swapchainViews[i]));
        VkFramebufferCreateInfo framebufferCreateInfo = {
            .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
            .renderPass = vulkan->renderPass,
            .attachmentCount = 1,
            .pAttachments = &vulkan->swapchainViews[i],
            .width = vulkan->extent.width,
            .height = vulkan->extent.height,
            .layers = 1,
        };
        VK_TRY(vkCreateFramebuffer(
            vulkan->device, &framebufferCreateInfo, NULL, &vulkan->swapchainFramebuffers[i]));
    }
    return 0;
}
 
int vk_recreate_swapchain(VkContext *vulkan, Platform *platform)
{
    VK_TRY(vkDeviceWaitIdle(vulkan->device));
    destroySwapchain(vulkan);
    return createSwapchain(vulkan, platform);
}
 
 
static int createCommandResources(VkContext *vulkan)
{
    VkCommandPoolCreateInfo commandPoolCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
        .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
        .queueFamilyIndex = vulkan->graphicsFamily,
    };
    VK_TRY(vkCreateCommandPool(vulkan->device, &commandPoolCreateInfo, NULL, &vulkan->commandPool));
 
    VkCommandBufferAllocateInfo commandBufferAllocateInfo = {
        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
        .commandPool = vulkan->commandPool,
        .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
        .commandBufferCount = VK_MAX_FRAMES,
    };
    VkCommandBuffer commandBuffers[VK_MAX_FRAMES];
    VK_TRY(vkAllocateCommandBuffers(vulkan->device, &commandBufferAllocateInfo, commandBuffers));
 
    for (int i = 0; i < VK_MAX_FRAMES; i++) {
        vulkan->frames[i].commandBuffer = commandBuffers[i];
        VkSemaphoreCreateInfo semaphoreCreateInfo = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
        VkFenceCreateInfo fenceCreateInfo = {
            .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
            .flags = VK_FENCE_CREATE_SIGNALED_BIT,
        };
        VK_TRY(vkCreateSemaphore(vulkan->device, &semaphoreCreateInfo, NULL,
                                 &vulkan->frames[i].imageAvailable));
        VK_TRY(vkCreateSemaphore(vulkan->device, &semaphoreCreateInfo, NULL,
                                 &vulkan->frames[i].renderFinished));
        VK_TRY(vkCreateFence(vulkan->device, &fenceCreateInfo, NULL,
                             &vulkan->frames[i].inFlight));
    }
    return 0;
}
 
 
int vk_init(VkContext *vulkan, Platform *platform)
{
    memset(vulkan, 0, sizeof(*vulkan));
    if (!platform || !platform->createVulkanSurface || !platform->vulkanSurfaceExtension) {
        fprintf(stderr, "vulkan: backend did not expose a surface hook\n");
        return -1;
    }
    if (createInstance(vulkan, platform) < 0)
        goto fail;
    if (platform->createVulkanSurface(platform, (void *)vulkan->instance, &vulkan->surface) < 0)
        goto fail;
    if (pickDevice(vulkan) < 0)
        goto fail;
    if (createDevice(vulkan) < 0)
        goto fail;
    if (createCommandResources(vulkan) < 0)
        goto fail;
    if (createSwapchain(vulkan, platform) < 0)
        goto fail;
 
    VkPhysicalDeviceProperties deviceProperties;
    vkGetPhysicalDeviceProperties(vulkan->physicalDevice, &deviceProperties);
    fprintf(stderr, "vulkan: %s  swapchain %ux%u\n",
            deviceProperties.deviceName, vulkan->extent.width, vulkan->extent.height);
 
    vulkan->ready = true;
    platform->gpuPresent = true;
    return 0;
fail:
    vk_destroy(vulkan);
    return -1;
}
 
 
void vk_destroy(VkContext *vulkan)
{
    if (!vulkan)
        return;
    if (vulkan->device) {
        vkDeviceWaitIdle(vulkan->device);
        destroySwapchain(vulkan);
        for (int i = 0; i < VK_MAX_FRAMES; i++) {
            if (vulkan->frames[i].imageAvailable)
                vkDestroySemaphore(vulkan->device, vulkan->frames[i].imageAvailable, NULL);
            if (vulkan->frames[i].renderFinished)
                vkDestroySemaphore(vulkan->device, vulkan->frames[i].renderFinished, NULL);
            if (vulkan->frames[i].inFlight)
                vkDestroyFence(vulkan->device, vulkan->frames[i].inFlight, NULL);
        }
        if (vulkan->commandPool)
            vkDestroyCommandPool(vulkan->device, vulkan->commandPool, NULL);
        vkDestroyDevice(vulkan->device, NULL);
        vulkan->device = VK_NULL_HANDLE;
    }
    if (vulkan->surface && vulkan->instance)
        vkDestroySurfaceKHR(vulkan->instance, vulkan->surface, NULL);
    if (vulkan->debug && vulkan->destroyDebug && vulkan->instance)
        vulkan->destroyDebug(vulkan->instance, vulkan->debug, NULL);
    if (vulkan->instance)
        vkDestroyInstance(vulkan->instance, NULL);
    memset(vulkan, 0, sizeof(*vulkan));
}
powered by btf.