| git.druid.rocks | index | militantprogrammer | astral_canvas | src/ | 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));
}