swapchain.cpp

cross platform rendering playground

src/backend/vulkan/swapchain.cpp

12.59 KB
#include <cstdio>
#include <volk.h>

#include <GLFW/glfw3.h>

#include "backend/rhi.h"
#include "backend/vulkan/debug.h"
#include "backend/vulkan/utils.h"
#include "backend/vulkan/vk_api.h"
#include "backend/vulkan/vk_conversion.h"
#include "core/globals.h"
#include "core/logger.h"

namespace rhi {
using namespace vk;

VkSurfaceFormat2KHR eval_surface_format(const std::vector<VkSurfaceFormat2KHR> &available_formats) {
    if (available_formats.size() == 1 && available_formats[0].surfaceFormat.format == VK_FORMAT_UNDEFINED) {
        VkSurfaceFormat2KHR result{
            .sType = VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR,
            .surfaceFormat = {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}
        };
        return result;
    }

    const VkSurfaceFormat2KHR preferred_formats[] = {
        {.sType = VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR,
         .surfaceFormat = {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}},
        {.sType = VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR,
         .surfaceFormat = {VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}}
    };

    for (const auto &chosen_preferred_format : preferred_formats) {
        for (const auto &chosen_available_format : available_formats) {
            if (chosen_available_format.surfaceFormat.format == chosen_preferred_format.surfaceFormat.format &&
                chosen_available_format.surfaceFormat.colorSpace == chosen_preferred_format.surfaceFormat.colorSpace) {
                return chosen_available_format;
            }
        }
    }

    return available_formats[0];
}

VkPresentModeKHR eval_present_mode(const std::vector<VkPresentModeKHR> &availablePresentModes, bool vSync = true) {
    if (vSync) {
        return VK_PRESENT_MODE_FIFO_KHR;
    }

    bool mailbox_supported = false;
    bool immediate_supported = false;

    for (VkPresentModeKHR mode : availablePresentModes) {
        if (mode == VK_PRESENT_MODE_MAILBOX_KHR) {
            mailbox_supported = true;
        }
        if (mode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
            immediate_supported = true;
        }
    }

    if (mailbox_supported) {
        return VK_PRESENT_MODE_MAILBOX_KHR;
    }

    if (immediate_supported) {
        return VK_PRESENT_MODE_IMMEDIATE_KHR;
    }

    return VK_PRESENT_MODE_FIFO_KHR;
}

void create_swapchain(Device &device, const rhi::SwapchainDesc &desc, Swapchain &swapchain) {
    VEL_INFO("Creating Swapchain...");

    VK_CHECK(glfwCreateWindowSurface(
        device.instance, static_cast<GLFWwindow *>(desc.native_window), nullptr, &device.surface
    ));
    VK_NAME(device, device.surface);

    const VkPhysicalDeviceSurfaceInfo2KHR surface_info_2{
        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR, .surface = device.surface
    };
    VkSurfaceCapabilities2KHR capabilities2{.sType = VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR};
    VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilities2KHR(device.physical, &surface_info_2, &capabilities2));

    u32 format_count = 0;
    VK_CHECK(vkGetPhysicalDeviceSurfaceFormats2KHR(device.physical, &surface_info_2, &format_count, nullptr));
    std::vector<VkSurfaceFormat2KHR> formats(format_count, {.sType = VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR});
    VK_CHECK(vkGetPhysicalDeviceSurfaceFormats2KHR(device.physical, &surface_info_2, &format_count, formats.data()));

    u32 present_mode_count = 0;
    VK_CHECK(vkGetPhysicalDeviceSurfacePresentModesKHR(device.physical, device.surface, &present_mode_count, nullptr));
    std::vector<VkPresentModeKHR> present_modes(present_mode_count);
    VK_CHECK(vkGetPhysicalDeviceSurfacePresentModesKHR(
        device.physical, device.surface, &present_mode_count, present_modes.data()
    ));

    const VkSurfaceFormat2KHR surface_format = eval_surface_format(formats);
    const VkPresentModeKHR present_mode = eval_present_mode(present_modes, desc.vsync_enabled);

    swapchain.format = vk::image_format_from_vk(surface_format.surfaceFormat.format);

    const VkExtent2D &ce = capabilities2.surfaceCapabilities.currentExtent;
    int fb_width = 0;
    int fb_height = 0;
    glfwGetFramebufferSize(static_cast<GLFWwindow *>(desc.native_window), &fb_width, &fb_height);
    u32 w;
    if (desc.width > 0) {
        w = desc.width;
    } else if (ce.width > 0) {
        w = ce.width;
    } else {
        w = (u32)fb_width;
    }
    u32 h;
    if (desc.height > 0) {
        h = desc.height;
    } else if (ce.height > 0) {
        h = ce.height;
    } else {
        h = (u32)fb_height;
    }
    swapchain.extent = rhi::Extent2D{w, h};

    VkSwapchainCreateInfoKHR swapchain_ci{};
    swapchain_ci.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
    swapchain_ci.surface = device.surface;
    swapchain_ci.minImageCount = desc.image_count;
    swapchain_ci.imageFormat = vk::image_format_to_vk(swapchain.format);
    swapchain_ci.imageColorSpace = surface_format.surfaceFormat.colorSpace;
    VkExtent2D vk_extent = {swapchain.extent.width, swapchain.extent.height};
    swapchain_ci.imageExtent = vk_extent;
    swapchain_ci.imageArrayLayers = 1;
    swapchain_ci.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
    swapchain_ci.preTransform = capabilities2.surfaceCapabilities.currentTransform;
    swapchain_ci.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
    swapchain_ci.presentMode = present_mode;
    swapchain_ci.clipped = VK_TRUE;
    swapchain_ci.oldSwapchain = VK_NULL_HANDLE;
    VkSwapchainKHR vk_swapchain;
    VK_CHECK(vkCreateSwapchainKHR(device.logical, &swapchain_ci, nullptr, &vk_swapchain));
    swapchain.handle = vk_swapchain;
    VK_NAME_EX(device, swapchain.handle, "swapchain");

    u32 image_count = 0;
    VK_CHECK(vkGetSwapchainImagesKHR(device.logical, swapchain.handle, &image_count, nullptr));
    if (desc.image_count <= image_count) {
        image_count = desc.image_count;
    }
    std::vector<VkImage> vk_images(image_count);
    VK_CHECK(vkGetSwapchainImagesKHR(device.logical, swapchain.handle, &image_count, vk_images.data()));

    VkSemaphoreCreateInfo sem_ci{};
    sem_ci.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;

    VkFenceCreateInfo fence_ci{};
    fence_ci.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;

    VkDevice vk_dev = device.logical;

    swapchain.backbuffers.resize(image_count);
    swapchain.present.resize(image_count);

    VK_CHECK(vkCreateFence(vk_dev, &fence_ci, nullptr, &swapchain.acquire));

    for (u32 i = 0; i < image_count; i++) {
        Image &backbuffer = swapchain.backbuffers[i];

        backbuffer._handle = vk_images[i];
        backbuffer.desc.format = swapchain.format;
        backbuffer.desc.width = swapchain.extent.width;
        backbuffer.desc.height = swapchain.extent.height;
        backbuffer.desc.mip_levels = 1;
        backbuffer.desc.layers = 1;
        backbuffer.desc.sample_count = rhi::SampleCount::Sample1;

        backbuffer.state = rhi::ResourceState::Idle;

        // per-image, safe to reuse
        VkSemaphore vk_present = VK_NULL_HANDLE;
        VK_CHECK(vkCreateSemaphore(vk_dev, &sem_ci, nullptr, &vk_present));
        swapchain.present[i] = vk_present;

        char name[64];
        (void)sprintf(name, "swapchain_image[%u]", i);
        set_name(device, backbuffer._handle, name);
    }

    swapchain.recreate = false;
    const char *present_mode_str = "UNKNOWN";
    switch (present_mode) {
    case VK_PRESENT_MODE_IMMEDIATE_KHR:
        present_mode_str = "IMMEDIATE";
        break;
    case VK_PRESENT_MODE_MAILBOX_KHR:
        present_mode_str = "MAILBOX";
        break;
    case VK_PRESENT_MODE_FIFO_KHR:
        present_mode_str = "FIFO";
        break;
    case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
        present_mode_str = "FIFO_RELAXED";
        break;
    default:
        break;
    }
    VEL_INFO(
        "Swapchain created: {}x{} (format: {}, present: {}, vsync: {})",
        swapchain.extent.width,
        swapchain.extent.height,
        static_cast<int>(swapchain.format),
        present_mode_str,
        desc.vsync_enabled
    );
};

void destroy_swapchain(Device &device, Swapchain &swapchain) {
    VEL_INFO("Destroying Swapchain...");
    VkDevice vk_dev = device.logical;
    for (u32 i = 0; i < (u32)swapchain.backbuffers.size(); ++i) {
        vkDestroySemaphore(vk_dev, swapchain.present[i], nullptr);
        swapchain.present[i] = VK_NULL_HANDLE;
        rhi::destroy_image_views(device, swapchain.backbuffers[i]);
    }
    vkDestroyFence(vk_dev, swapchain.acquire, nullptr);
    swapchain.acquire = VK_NULL_HANDLE;
    swapchain.backbuffers.clear();
    swapchain.present.clear();
    vkDestroySwapchainKHR(vk_dev, swapchain.handle, nullptr);

    if (device.surface != VK_NULL_HANDLE) {
        vkDestroySurfaceKHR(device.instance, device.surface, nullptr);
        device.surface = VK_NULL_HANDLE;
    }
}

bool acquire_swapchain_image(Device &device, Swapchain &swapchain, u32 &out_image_index, CmdBuffer &cmd) {
    VkResult res = vkAcquireNextImageKHR(
        device.logical, swapchain.handle, UINT64_MAX, VK_NULL_HANDLE, swapchain.acquire, &out_image_index
    );

    if (res == VK_ERROR_OUT_OF_DATE_KHR) {
        swapchain.recreate = true;
        return false;
    }

    if (res == VK_ERROR_SURFACE_LOST_KHR || res == VK_ERROR_DEVICE_LOST) {
        swapchain.recreate = true;
        return false;
    }

    if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) {
        VK_CHECK(res);
    }

    VK_CHECK(vkWaitForFences(device.logical, 1, &swapchain.acquire, VK_TRUE, UINT64_MAX));
    VK_CHECK(vkResetFences(device.logical, 1, &swapchain.acquire));

    Image &img = swapchain.backbuffers[out_image_index];
    VkImageMemoryBarrier2 barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2};
    barrier.srcStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT;
    barrier.srcAccessMask = VK_ACCESS_2_NONE;
    barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
    barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT;
    barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
    barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.image = img._handle;
    barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
    VkDependencyInfo dep{VK_STRUCTURE_TYPE_DEPENDENCY_INFO};
    dep.imageMemoryBarrierCount = 1;
    dep.pImageMemoryBarriers = &barrier;
    vkCmdPipelineBarrier2(cmd.handle, &dep);
    img.state = rhi::ResourceState::ColorDraw;

    return true;
}

void present_swapchain(Device &device, Swapchain &swapchain, u32 image_index, SyncPoint frame_done) {
    // timeline to binary for present
    VkSemaphoreSubmitInfo convert_wait{
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
        .semaphore = frame_done.sync->timeline,
        .value = frame_done.value,
        .stageMask = vk::pipeline_stage_flags_to_vk(rhi::PipelineStages::ALL_COMMANDS),
        .deviceIndex = 0,
    };
    VkSemaphoreSubmitInfo convert_signal{
        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
        .semaphore = swapchain.present[image_index],
        .value = 0, // binary, unused
        .stageMask = vk::pipeline_stage_flags_to_vk(rhi::PipelineStages::ALL_COMMANDS),
        .deviceIndex = 0,
    };
    VkSubmitInfo2 convert_info{
        .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2,
        .waitSemaphoreInfoCount = 1,
        .pWaitSemaphoreInfos = &convert_wait,
        .commandBufferInfoCount = 0,
        .pCommandBufferInfos = nullptr,
        .signalSemaphoreInfoCount = 1,
        .pSignalSemaphoreInfos = &convert_signal,
    };
    VK_CHECK(vkQueueSubmit2(device.graphics.handle, 1, &convert_info, VK_NULL_HANDLE));

    VkSemaphore vk_sem = swapchain.present[image_index];
    VkSwapchainKHR vk_swap = swapchain.handle;
    VkPresentInfoKHR present_info{
        .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
        .waitSemaphoreCount = 1,
        .pWaitSemaphores = &vk_sem,
        .swapchainCount = 1,
        .pSwapchains = &vk_swap,
        .pImageIndices = &image_index
    };

    VkResult res = vkQueuePresentKHR(device.graphics.handle, &present_info);

    if (res == VK_ERROR_OUT_OF_DATE_KHR || res == VK_SUBOPTIMAL_KHR) {
        swapchain.recreate = true;
    }
}

rhi::ImageFormat swapchain_format(const Swapchain &swapchain) {
    return swapchain.format;
}

rhi::Extent2D swapchain_extent(const Swapchain &swapchain) {
    return swapchain.extent;
}

Image *swapchain_image(Swapchain &swapchain, u32 index) {
    if (index >= swapchain.backbuffers.size()) {
        return nullptr;
    }
    return &swapchain.backbuffers[index];
}

bool swapchain_needs_recreate(const Swapchain &swapchain) {
    return swapchain.recreate;
}

} // namespace rhi