Program Listing for File Raytracing.cpp

Program Listing for File Raytracing.cpp#

Return to documentation for file (Src/GraphicsEngineVulkan/renderer/Raytracing.cpp)

module;

#include <array>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <sstream>
#include <string>
#include <vector>
#include <vulkan/vulkan.hpp>

#include "common/MemoryHelper.hpp"
#include "common/Utilities.hpp"
#include "renderer/pushConstants/PushConstantRayTracing.hpp"

module kataglyphis.vulkan.raytracing;

import kataglyphis.vulkan.file;
import kataglyphis.vulkan.shader_helper;

Kataglyphis::VulkanRendererInternals::Raytracing::Raytracing() = default;

void Kataglyphis::VulkanRendererInternals::Raytracing::init(VulkanDevice *in_device,
  const std::vector<vk::DescriptorSetLayout> &descriptorSetLayouts)
{
    this->device = in_device;

    createPCRange();
    createGraphicsPipeline(descriptorSetLayouts);
    createSBT();
}

void Kataglyphis::VulkanRendererInternals::Raytracing::shaderHotReload(
  const std::vector<vk::DescriptorSetLayout> &descriptor_set_layouts)
{
    device->getLogicalDevice().destroyPipeline(graphicsPipeline);
    createGraphicsPipeline(descriptor_set_layouts);
}

void Kataglyphis::VulkanRendererInternals::Raytracing::recordCommands(vk::CommandBuffer &commandBuffer,
  VulkanImage &renderImage,
  [[maybe_unused]] VulkanSwapChain *swapchain,
  const std::vector<vk::DescriptorSet> &descriptorSets)
{
    uint32_t const handle_size = raytracing_properties.shaderGroupHandleSize;
    uint32_t const handle_size_aligned = align_up(handle_size, raytracing_properties.shaderGroupHandleAlignment);

    vk::Device logical_device = device->getLogicalDevice();

    vk::BufferDeviceAddressInfo bufferDeviceAI{};
    bufferDeviceAI.buffer = raygenShaderBindingTableBuffer.getBuffer();

    rgen_region.deviceAddress = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetBufferDeviceAddress(
      static_cast<VkDevice>(logical_device), reinterpret_cast<VkBufferDeviceAddressInfo *>(&bufferDeviceAI));
    rgen_region.stride = handle_size_aligned;
    rgen_region.size = handle_size_aligned;

    bufferDeviceAI.buffer = missShaderBindingTableBuffer.getBuffer();
    miss_region.deviceAddress = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetBufferDeviceAddress(
      static_cast<VkDevice>(logical_device), reinterpret_cast<VkBufferDeviceAddressInfo *>(&bufferDeviceAI));
    miss_region.stride = handle_size_aligned;
    miss_region.size = handle_size_aligned;

    bufferDeviceAI.buffer = hitShaderBindingTableBuffer.getBuffer();
    hit_region.deviceAddress = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetBufferDeviceAddress(
      static_cast<VkDevice>(logical_device), reinterpret_cast<VkBufferDeviceAddressInfo *>(&bufferDeviceAI));
    hit_region.stride = handle_size_aligned;
    hit_region.size = handle_size_aligned;

    pc.clear_color = { 0.2F, 0.65F, 0.4F, 1.0F };
    commandBuffer.pushConstants(pipeline_layout,
      vk::ShaderStageFlagBits::eRaygenKHR | vk::ShaderStageFlagBits::eClosestHitKHR | vk::ShaderStageFlagBits::eMissKHR,
      0,
      sizeof(PushConstantRaytracing),
      reinterpret_cast<void *>(&pc));

    commandBuffer.bindPipeline(vk::PipelineBindPoint::eRayTracingKHR, graphicsPipeline);

    vk::ImageSubresourceRange subresourceRange{};
    subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
    subresourceRange.baseMipLevel = 0;
    subresourceRange.levelCount = 1;
    subresourceRange.baseArrayLayer = 0;
    subresourceRange.layerCount = 1;

    vk::ImageMemoryBarrier rasterizerToRaytracingImageBarrier{};
    rasterizerToRaytracingImageBarrier.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
    rasterizerToRaytracingImageBarrier.dstAccessMask = vk::AccessFlagBits::eShaderWrite;
    rasterizerToRaytracingImageBarrier.oldLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
    rasterizerToRaytracingImageBarrier.newLayout = vk::ImageLayout::eGeneral;
    rasterizerToRaytracingImageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    rasterizerToRaytracingImageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    rasterizerToRaytracingImageBarrier.image = renderImage.getImage();
    rasterizerToRaytracingImageBarrier.subresourceRange = subresourceRange;

    commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput,
      vk::PipelineStageFlagBits::eRayTracingShaderKHR,
      {},
      {},
      {},
      rasterizerToRaytracingImageBarrier);

    commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eRayTracingKHR, pipeline_layout, 0, descriptorSets, {});

    const vk::Extent2D &swap_chain_extent = vulkanSwapChain->getSwapChainExtent();
    commandBuffer.traceRaysKHR(
      rgen_region, miss_region, hit_region, call_region, swap_chain_extent.width, swap_chain_extent.height, 1);

    vk::ImageMemoryBarrier raytracingToPostImageBarrier{};
    raytracingToPostImageBarrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite;
    raytracingToPostImageBarrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
    raytracingToPostImageBarrier.oldLayout = vk::ImageLayout::eGeneral;
    raytracingToPostImageBarrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
    raytracingToPostImageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    raytracingToPostImageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    raytracingToPostImageBarrier.image = renderImage.getImage();
    raytracingToPostImageBarrier.subresourceRange = subresourceRange;

    commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eRayTracingShaderKHR,
      vk::PipelineStageFlagBits::eFragmentShader,
      {},
      {},
      {},
      raytracingToPostImageBarrier);
}

void Kataglyphis::VulkanRendererInternals::Raytracing::cleanUp()
{
    shaderBindingTableBuffer.cleanUp();
    raygenShaderBindingTableBuffer.cleanUp();
    missShaderBindingTableBuffer.cleanUp();
    hitShaderBindingTableBuffer.cleanUp();

    device->getLogicalDevice().destroyPipeline(graphicsPipeline);
    device->getLogicalDevice().destroyPipelineLayout(pipeline_layout);
}

Kataglyphis::VulkanRendererInternals::Raytracing::~Raytracing() = default;

void Kataglyphis::VulkanRendererInternals::Raytracing::createPCRange()
{
    pc_ranges.stageFlags =
      vk::ShaderStageFlagBits::eRaygenKHR | vk::ShaderStageFlagBits::eClosestHitKHR | vk::ShaderStageFlagBits::eMissKHR;
    pc_ranges.offset = 0;
    pc_ranges.size = sizeof(PushConstantRaytracing);
}

void Kataglyphis::VulkanRendererInternals::Raytracing::createGraphicsPipeline(
  const std::vector<vk::DescriptorSetLayout> &descriptorSetLayouts)
{
    std::stringstream raytracing_shader_dir;
    std::filesystem::path const cwd = std::filesystem::current_path();
    raytracing_shader_dir << cwd.string();
    raytracing_shader_dir << RELATIVE_RESOURCE_PATH;
    raytracing_shader_dir << "Shaders/raytracing/";

    std::string const raygen_shader = "raytrace.rgen";
    std::string const chit_shader = "raytrace.rchit";
    std::string const miss_shader = "raytrace.rmiss";
    std::string const shadow_shader = "shadow.rmiss";

    ShaderHelper shaderHelper;
    shaderHelper.compileShader(raytracing_shader_dir.str(), raygen_shader);
    shaderHelper.compileShader(raytracing_shader_dir.str(), chit_shader);
    shaderHelper.compileShader(raytracing_shader_dir.str(), miss_shader);
    shaderHelper.compileShader(raytracing_shader_dir.str(), shadow_shader);

    File raygenFile(shaderHelper.getShaderSpvDir(raytracing_shader_dir.str(), raygen_shader));
    File raychitFile(shaderHelper.getShaderSpvDir(raytracing_shader_dir.str(), chit_shader));
    File raymissFile(shaderHelper.getShaderSpvDir(raytracing_shader_dir.str(), miss_shader));
    File shadowFile(shaderHelper.getShaderSpvDir(raytracing_shader_dir.str(), shadow_shader));

    std::vector<char> const raygen_shader_code = raygenFile.readCharSequence();
    std::vector<char> const raychit_shader_code = raychitFile.readCharSequence();
    std::vector<char> const raymiss_shader_code = raymissFile.readCharSequence();
    std::vector<char> const shadow_shader_code = shadowFile.readCharSequence();

    vk::ShaderModule raygen_shader_module = shaderHelper.createShaderModule(device, raygen_shader_code);
    vk::ShaderModule raychit_shader_module = shaderHelper.createShaderModule(device, raychit_shader_code);
    vk::ShaderModule raymiss_shader_module = shaderHelper.createShaderModule(device, raymiss_shader_code);
    vk::ShaderModule shadow_shader_module = shaderHelper.createShaderModule(device, shadow_shader_code);

    vk::PipelineShaderStageCreateInfo rgen_shader_stage_info{};
    rgen_shader_stage_info.stage = vk::ShaderStageFlagBits::eRaygenKHR;
    rgen_shader_stage_info.module = raygen_shader_module;
    rgen_shader_stage_info.pName = "main";

    vk::PipelineShaderStageCreateInfo rmiss_shader_stage_info{};
    rmiss_shader_stage_info.stage = vk::ShaderStageFlagBits::eMissKHR;
    rmiss_shader_stage_info.module = raymiss_shader_module;
    rmiss_shader_stage_info.pName = "main";

    vk::PipelineShaderStageCreateInfo shadow_shader_stage_info{};
    shadow_shader_stage_info.stage = vk::ShaderStageFlagBits::eMissKHR;
    shadow_shader_stage_info.module = shadow_shader_module;
    shadow_shader_stage_info.pName = "main";

    vk::PipelineShaderStageCreateInfo rchit_shader_stage_info{};
    rchit_shader_stage_info.stage = vk::ShaderStageFlagBits::eClosestHitKHR;
    rchit_shader_stage_info.module = raychit_shader_module;
    rchit_shader_stage_info.pName = "main";

    std::array<vk::PipelineShaderStageCreateInfo, 4> shader_stages = {
        rgen_shader_stage_info, rmiss_shader_stage_info, shadow_shader_stage_info, rchit_shader_stage_info
    };

    enum StageIndices { eRaygen, eMiss, eMiss2, eClosestHit, eShaderGroupCount };

    shader_groups.reserve(4);
    vk::RayTracingShaderGroupCreateInfoKHR shader_group_create_infos[4];

    shader_group_create_infos[0].type = vk::RayTracingShaderGroupTypeKHR::eGeneral;
    shader_group_create_infos[0].generalShader = eRaygen;
    shader_group_create_infos[0].closestHitShader = VK_SHADER_UNUSED_KHR;
    shader_group_create_infos[0].anyHitShader = VK_SHADER_UNUSED_KHR;
    shader_group_create_infos[0].intersectionShader = VK_SHADER_UNUSED_KHR;

    shader_groups.push_back(shader_group_create_infos[0]);

    shader_group_create_infos[1].type = vk::RayTracingShaderGroupTypeKHR::eGeneral;
    shader_group_create_infos[1].generalShader = eMiss;
    shader_group_create_infos[1].closestHitShader = VK_SHADER_UNUSED_KHR;
    shader_group_create_infos[1].anyHitShader = VK_SHADER_UNUSED_KHR;
    shader_group_create_infos[1].intersectionShader = VK_SHADER_UNUSED_KHR;

    shader_groups.push_back(shader_group_create_infos[1]);

    shader_group_create_infos[2].type = vk::RayTracingShaderGroupTypeKHR::eGeneral;
    shader_group_create_infos[2].generalShader = eMiss2;
    shader_group_create_infos[2].closestHitShader = VK_SHADER_UNUSED_KHR;
    shader_group_create_infos[2].anyHitShader = VK_SHADER_UNUSED_KHR;
    shader_group_create_infos[2].intersectionShader = VK_SHADER_UNUSED_KHR;

    shader_groups.push_back(shader_group_create_infos[2]);

    shader_group_create_infos[3].type = vk::RayTracingShaderGroupTypeKHR::eTrianglesHitGroup;
    shader_group_create_infos[3].generalShader = VK_SHADER_UNUSED_KHR;
    shader_group_create_infos[3].closestHitShader = eClosestHit;
    shader_group_create_infos[3].anyHitShader = VK_SHADER_UNUSED_KHR;
    shader_group_create_infos[3].intersectionShader = VK_SHADER_UNUSED_KHR;

    shader_groups.push_back(shader_group_create_infos[3]);

    vk::PipelineLayoutCreateInfo pipeline_layout_create_info{};
    pipeline_layout_create_info.setLayoutCount = static_cast<uint32_t>(descriptorSetLayouts.size());
    pipeline_layout_create_info.pSetLayouts = descriptorSetLayouts.data();
    pipeline_layout_create_info.pushConstantRangeCount = 1;
    pipeline_layout_create_info.pPushConstantRanges = &pc_ranges;

    vk::Result result =
      device->getLogicalDevice().createPipelineLayout(&pipeline_layout_create_info, nullptr, &pipeline_layout);
    ASSERT_VULKAN(result, "Failed to create raytracing pipeline layout!")

    vk::PipelineLibraryCreateInfoKHR pipeline_library_create_info{};
    pipeline_library_create_info.libraryCount = 0;
    pipeline_library_create_info.pLibraries = nullptr;

    vk::RayTracingPipelineCreateInfoKHR raytracing_pipeline_create_info{};
    raytracing_pipeline_create_info.flags = {};
    raytracing_pipeline_create_info.stageCount = static_cast<uint32_t>(shader_stages.size());
    raytracing_pipeline_create_info.pStages = shader_stages.data();
    raytracing_pipeline_create_info.groupCount = static_cast<uint32_t>(shader_groups.size());
    raytracing_pipeline_create_info.pGroups = shader_groups.data();
    raytracing_pipeline_create_info.maxPipelineRayRecursionDepth = 2;
    raytracing_pipeline_create_info.layout = pipeline_layout;

    vk::Result result2 = device->getLogicalDevice().createRayTracingPipelinesKHR(
      nullptr, nullptr, 1, &raytracing_pipeline_create_info, nullptr, &graphicsPipeline, VULKAN_HPP_DEFAULT_DISPATCHER);
    ASSERT_VULKAN(result2, "Failed to create raytracing pipeline!")

    device->getLogicalDevice().destroyShaderModule(raygen_shader_module);
    device->getLogicalDevice().destroyShaderModule(raymiss_shader_module);
    device->getLogicalDevice().destroyShaderModule(raychit_shader_module);
    device->getLogicalDevice().destroyShaderModule(shadow_shader_module);
}

void Kataglyphis::VulkanRendererInternals::Raytracing::createSBT()
{
    raytracing_properties = vk::PhysicalDeviceRayTracingPipelinePropertiesKHR{};

    vk::PhysicalDeviceProperties2 properties{};
    properties.pNext = &raytracing_properties;

    device->getPhysicalDevice().getProperties2(&properties);

    uint32_t const handle_size = raytracing_properties.shaderGroupHandleSize;
    uint32_t const handle_size_aligned = align_up(handle_size, raytracing_properties.shaderGroupHandleAlignment);

    auto const group_count = static_cast<uint32_t>(shader_groups.size());
    uint32_t const sbt_size = group_count * handle_size_aligned;

    std::vector<uint8_t> handles(sbt_size);

    vk::Result result = device->getLogicalDevice().getRayTracingShaderGroupHandlesKHR(
      graphicsPipeline, 0, group_count, sbt_size, handles.data(), VULKAN_HPP_DEFAULT_DISPATCHER);
    ASSERT_VULKAN(result, "Failed to get ray tracing shader group handles!")

    const vk::BufferUsageFlags bufferUsageFlags =
      vk::BufferUsageFlagBits::eShaderBindingTableKHR | vk::BufferUsageFlagBits::eShaderDeviceAddress;
    const vk::MemoryPropertyFlags memoryPropertyFlags =
      vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent;
    const vk::MemoryAllocateFlags memoryAllocateFlags = vk::MemoryAllocateFlagBits::eDeviceAddress;

    raygenShaderBindingTableBuffer.create(
      device, handle_size, bufferUsageFlags, memoryPropertyFlags, memoryAllocateFlags);

    missShaderBindingTableBuffer.create(
      device, 2 * handle_size, bufferUsageFlags, memoryPropertyFlags, memoryAllocateFlags);

    hitShaderBindingTableBuffer.create(device, handle_size, bufferUsageFlags, memoryPropertyFlags, memoryAllocateFlags);

    void *mapped_raygen = device->getLogicalDevice()
                            .mapMemory(raygenShaderBindingTableBuffer.getBufferMemory(), 0, VK_WHOLE_SIZE, {})
                            .value;

    void *mapped_miss =
      device->getLogicalDevice().mapMemory(missShaderBindingTableBuffer.getBufferMemory(), 0, VK_WHOLE_SIZE, {}).value;

    void *mapped_rchit =
      device->getLogicalDevice().mapMemory(hitShaderBindingTableBuffer.getBufferMemory(), 0, VK_WHOLE_SIZE, {}).value;

    memcpy(mapped_raygen, handles.data(), handle_size);
    memcpy(mapped_miss, handles.data() + handle_size_aligned, handle_size * 2);
    memcpy(mapped_rchit, handles.data() + (handle_size_aligned * 3), handle_size);
}