Program Listing for File Clouds.cpp

Program Listing for File Clouds.cpp#

Return to documentation for file (Src/GraphicsEngineVulkan/scene/atmospheric_effects/clouds/Clouds.cpp)

module;
#include <vector>
#include <memory>
#include <array>
#include <vulkan/vulkan.hpp>
#include "common/FormatHelper.hpp"
#include "common/Utilities.hpp"

module kataglyphis.vulkan.clouds;

import kataglyphis.vulkan.device;
import kataglyphis.vulkan.texture;
import kataglyphis.vulkan.shader_helper;
import kataglyphis.vulkan.command_buffer_manager;
import kataglyphis.vulkan.file;

namespace Kataglyphis {

void Clouds::init(std::shared_ptr<VulkanDevice>device, vk::CommandPool commandPool, vk::DescriptorSetLayout sharedLayout, uint32_t width, uint32_t height)
{
    this->device = device;
    this->width = width;
    this->height = height;
    createTextures(commandPool);
    createDescriptorSets();
    createComputePipelines(sharedLayout);
    dispatchNoiseGeneration();
}

void Clouds::createTextures(vk::CommandPool commandPool)
{
    // Create 3D Texture for Noise
    cloudNoiseTexture = std::make_unique<Texture>();
    cloudNoiseTexture->createImage(device, 128, 128, 1, vk::Format::eR16G16B16A16Sfloat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, 1, vk::ImageCreateFlags{}, vk::ImageType::e3D, 128);
    cloudNoiseTexture->createImageView(device, vk::Format::eR16G16B16A16Sfloat, vk::ImageAspectFlagBits::eColor, 1, vk::ImageViewType::e3D, 1);
    cloudNoiseTexture->createTextureSampler(device);

    // Transition noise texture
    vk::CommandBuffer commandBuffer = Kataglyphis::VulkanRendererInternals::CommandBufferManager::beginCommandBuffer(device->getLogicalDevice(), commandPool);
    cloudNoiseTexture->getVulkanImage().transitionImageLayout(commandBuffer, vk::ImageLayout::eUndefined, vk::ImageLayout::eGeneral, 1, vk::ImageAspectFlagBits::eColor);
    Kataglyphis::VulkanRendererInternals::CommandBufferManager::endAndSubmitCommandBuffer(device->getLogicalDevice(), commandPool, device->getGraphicsQueue(), commandBuffer);

    // Create 2D Texture for Cloud Output
    cloudOutputTexture = std::make_unique<Texture>();
    // Assume screen size or half-screen size for performance
    cloudOutputTexture->createImage(device, width, height, 1, vk::Format::eR16G16B16A16Sfloat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, 1, vk::ImageCreateFlags{}, vk::ImageType::e2D, 1);
    cloudOutputTexture->createImageView(device, vk::Format::eR16G16B16A16Sfloat, vk::ImageAspectFlagBits::eColor, 1, vk::ImageViewType::e2D, 1);
    cloudOutputTexture->createTextureSampler(device);

    // Transition output texture
    commandBuffer = Kataglyphis::VulkanRendererInternals::CommandBufferManager::beginCommandBuffer(device->getLogicalDevice(), commandPool);
    cloudOutputTexture->getVulkanImage().transitionImageLayout(commandBuffer, vk::ImageLayout::eUndefined, vk::ImageLayout::eGeneral, 1, vk::ImageAspectFlagBits::eColor);
    Kataglyphis::VulkanRendererInternals::CommandBufferManager::endAndSubmitCommandBuffer(device->getLogicalDevice(), commandPool, device->getGraphicsQueue(), commandBuffer);
}

void Clouds::createDescriptorSets()
{
    // Layout
    std::array<vk::DescriptorSetLayoutBinding, 2> bindings{};
    bindings[0].binding = 0;
    bindings[0].descriptorCount = 1;
    bindings[0].descriptorType = vk::DescriptorType::eStorageImage;
    bindings[0].pImmutableSamplers = nullptr;
    bindings[0].stageFlags = vk::ShaderStageFlagBits::eCompute;

    bindings[1].binding = 1;
    bindings[1].descriptorCount = 1;
    bindings[1].descriptorType = vk::DescriptorType::eCombinedImageSampler;
    bindings[1].pImmutableSamplers = nullptr;
    bindings[1].stageFlags = vk::ShaderStageFlagBits::eCompute;

    vk::DescriptorSetLayoutCreateInfo layoutInfo{};
    layoutInfo.bindingCount = static_cast<uint32_t>(bindings.size());
    layoutInfo.pBindings = bindings.data();

    auto result = device->getLogicalDevice().createDescriptorSetLayout(layoutInfo);
    ASSERT_VULKAN(VkResult(result.result), "Failed to create cloud descriptor set layout!");
    descriptorSetLayout = result.value;

    // Pool
    std::array<vk::DescriptorPoolSize, 2> poolSizes{};
    poolSizes[0].type = vk::DescriptorType::eStorageImage;
    poolSizes[0].descriptorCount = 2; // +1 for noise
    poolSizes[1].type = vk::DescriptorType::eCombinedImageSampler;
    poolSizes[1].descriptorCount = 1;

    vk::DescriptorPoolCreateInfo poolInfo{};
    poolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
    poolInfo.pPoolSizes = poolSizes.data();
    poolInfo.maxSets = 2; // 2 sets total

    auto poolResult = device->getLogicalDevice().createDescriptorPool(poolInfo);
    ASSERT_VULKAN(VkResult(poolResult.result), "Failed to create cloud descriptor pool!");
    descriptorPool = poolResult.value;

    // Allocate Set
    vk::DescriptorSetAllocateInfo allocInfo{};
    allocInfo.descriptorPool = descriptorPool;
    allocInfo.descriptorSetCount = 1;
    allocInfo.pSetLayouts = &descriptorSetLayout;

    auto allocResult = device->getLogicalDevice().allocateDescriptorSets(allocInfo);
    ASSERT_VULKAN(VkResult(allocResult.result), "Failed to allocate cloud descriptor sets!");
    descriptorSet = allocResult.value[0];

    // Noise Layout
    vk::DescriptorSetLayoutBinding noiseBinding{};
    noiseBinding.binding = 0;
    noiseBinding.descriptorCount = 1;
    noiseBinding.descriptorType = vk::DescriptorType::eStorageImage;
    noiseBinding.pImmutableSamplers = nullptr;
    noiseBinding.stageFlags = vk::ShaderStageFlagBits::eCompute;

    vk::DescriptorSetLayoutCreateInfo noiseLayoutInfo{};
    noiseLayoutInfo.bindingCount = 1;
    noiseLayoutInfo.pBindings = &noiseBinding;

    auto noiseLayoutResult = device->getLogicalDevice().createDescriptorSetLayout(noiseLayoutInfo);
    ASSERT_VULKAN(VkResult(noiseLayoutResult.result), "Failed to create noise descriptor set layout!");
    noiseDescriptorSetLayout = noiseLayoutResult.value;

    // Allocate Noise Set
    vk::DescriptorSetAllocateInfo noiseAllocInfo{};
    noiseAllocInfo.descriptorPool = descriptorPool;
    noiseAllocInfo.descriptorSetCount = 1;
    noiseAllocInfo.pSetLayouts = &noiseDescriptorSetLayout;

    auto noiseAllocResult = device->getLogicalDevice().allocateDescriptorSets(noiseAllocInfo);
    ASSERT_VULKAN(VkResult(noiseAllocResult.result), "Failed to allocate noise descriptor sets!");
    noiseDescriptorSet = noiseAllocResult.value[0];

    // Write Descriptor Set
    vk::DescriptorImageInfo outputImageInfo{};
    outputImageInfo.imageLayout = vk::ImageLayout::eGeneral;
    outputImageInfo.imageView = cloudOutputTexture->getImageView();

    vk::DescriptorImageInfo noiseImageInfo{};
    noiseImageInfo.imageLayout = vk::ImageLayout::eGeneral;
    noiseImageInfo.imageView = cloudNoiseTexture->getImageView();
    noiseImageInfo.sampler = cloudNoiseTexture->getSampler();

    std::array<vk::WriteDescriptorSet, 2> descriptorWrites{};
    descriptorWrites[0].dstSet = descriptorSet;
    descriptorWrites[0].dstBinding = 0;
    descriptorWrites[0].dstArrayElement = 0;
    descriptorWrites[0].descriptorType = vk::DescriptorType::eStorageImage;
    descriptorWrites[0].descriptorCount = 1;
    descriptorWrites[0].pImageInfo = &outputImageInfo;

    descriptorWrites[1].dstSet = descriptorSet;
    descriptorWrites[1].dstBinding = 1;
    descriptorWrites[1].dstArrayElement = 0;
    descriptorWrites[1].descriptorType = vk::DescriptorType::eCombinedImageSampler;
    descriptorWrites[1].descriptorCount = 1;
    descriptorWrites[1].pImageInfo = &noiseImageInfo;

    vk::WriteDescriptorSet noiseWrite{};
    noiseWrite.dstSet = noiseDescriptorSet;
    noiseWrite.dstBinding = 0;
    noiseWrite.dstArrayElement = 0;
    noiseWrite.descriptorType = vk::DescriptorType::eStorageImage;
    noiseWrite.descriptorCount = 1;
    noiseWrite.pImageInfo = &noiseImageInfo;

    std::array<vk::WriteDescriptorSet, 3> allWrites = { descriptorWrites[0], descriptorWrites[1], noiseWrite };

    device->getLogicalDevice().updateDescriptorSets(static_cast<uint32_t>(allWrites.size()), allWrites.data(), 0, nullptr);
}

void Clouds::createComputePipelines(vk::DescriptorSetLayout sharedLayout)
{
    ShaderHelper shaderHelper;
    std::vector<char> cloudShaderCode = File("Resources/Shaders/compute/spv/clouds.comp.spv").readCharSequence();
    vk::ShaderModule cloudShaderModule = shaderHelper.createShaderModule(device, cloudShaderCode);

    vk::PipelineShaderStageCreateInfo computeStageInfo{};
    computeStageInfo.stage = vk::ShaderStageFlagBits::eCompute;
    computeStageInfo.module = cloudShaderModule;
    computeStageInfo.pName = "main";

    vk::PipelineLayoutCreateInfo pipelineLayoutInfo{};
    pipelineLayoutInfo.setLayoutCount = 2; // cloud specific set AND sharedRenderDescriptorSet

    std::array<vk::DescriptorSetLayout, 2> layouts = { descriptorSetLayout, sharedLayout };
    pipelineLayoutInfo.pSetLayouts = layouts.data();

    auto result = device->getLogicalDevice().createPipelineLayout(pipelineLayoutInfo);
    ASSERT_VULKAN(VkResult(result.result), "Failed to create cloud compute pipeline layout!");
    cloudPipelineLayout = result.value;

    vk::ComputePipelineCreateInfo pipelineInfo{};
    pipelineInfo.stage = computeStageInfo;
    pipelineInfo.layout = cloudPipelineLayout;

    auto compResult = device->getLogicalDevice().createComputePipeline(nullptr, pipelineInfo);
    ASSERT_VULKAN(VkResult(compResult.result), "Failed to create cloud compute pipeline!");
    cloudComputePipeline = compResult.value;

    device->getLogicalDevice().destroyShaderModule(cloudShaderModule);

    // Noise pipeline
    std::vector<char> noiseShaderCode = File("Resources/Shaders/compute/spv/noise.comp.spv").readCharSequence();
    vk::ShaderModule noiseShaderModule = shaderHelper.createShaderModule(device, noiseShaderCode);

    vk::PipelineShaderStageCreateInfo noiseStageInfo{};
    noiseStageInfo.stage = vk::ShaderStageFlagBits::eCompute;
    noiseStageInfo.module = noiseShaderModule;
    noiseStageInfo.pName = "main";

    vk::PipelineLayoutCreateInfo noiseLayoutInfoCreate{};
    noiseLayoutInfoCreate.setLayoutCount = 1;
    noiseLayoutInfoCreate.pSetLayouts = &noiseDescriptorSetLayout;

    auto noiseLayoutResult = device->getLogicalDevice().createPipelineLayout(noiseLayoutInfoCreate);
    ASSERT_VULKAN(VkResult(noiseLayoutResult.result), "Failed to create noise pipeline layout!");
    noisePipelineLayout = noiseLayoutResult.value;

    vk::ComputePipelineCreateInfo noisePipelineInfo{};
    noisePipelineInfo.stage = noiseStageInfo;
    noisePipelineInfo.layout = noisePipelineLayout;

    auto noiseCompResult = device->getLogicalDevice().createComputePipeline(nullptr, noisePipelineInfo);
    ASSERT_VULKAN(VkResult(noiseCompResult.result), "Failed to create noise pipeline!");
    noiseComputePipeline = noiseCompResult.value;

    device->getLogicalDevice().destroyShaderModule(noiseShaderModule);
}

void Clouds::dispatchNoiseGeneration()
{
    // Dispatch to fill the 3D texture
    auto queueFamilies = device->getQueueFamilies();
    if (queueFamilies.compute_family < 0) {
        spdlog::warn("No compute queue family available, skipping noise generation dispatch");
        return;
    }

    vk::CommandPool commandPool;
    vk::CommandPoolCreateInfo poolInfo{};
    poolInfo.queueFamilyIndex = static_cast<uint32_t>(queueFamilies.compute_family);
    poolInfo.flags = vk::CommandPoolCreateFlagBits::eTransient;
    auto poolRes = device->getLogicalDevice().createCommandPool(poolInfo);
    if (poolRes.result != vk::Result::eSuccess) {
        spdlog::error("Failed to create command pool for noise generation");
        return;
    }
    commandPool = poolRes.value;

    vk::CommandBuffer commandBuffer = Kataglyphis::VulkanRendererInternals::CommandBufferManager::beginCommandBuffer(device->getLogicalDevice(), commandPool);

    commandBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, noiseComputePipeline);
    commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, noisePipelineLayout, 0, 1, &noiseDescriptorSet, 0, nullptr);

    // noise texture is 128x128x128, local_size is 8x8x8
    commandBuffer.dispatch(128 / 8, 128 / 8, 128 / 8);

    Kataglyphis::VulkanRendererInternals::CommandBufferManager::endAndSubmitCommandBuffer(device->getLogicalDevice(), commandPool, device->getComputeQueue(), commandBuffer);

    device->getLogicalDevice().destroyCommandPool(commandPool);
}

void Clouds::recordComputeCommands(vk::CommandBuffer &commandBuffer, uint32_t image_index, const std::vector<vk::DescriptorSet> &descriptorSets)
{
    // Bind cloud compute pipeline and dispatch thread groups based on screen extent
    commandBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, cloudComputePipeline);
    commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, cloudPipelineLayout, 0, 1, &descriptorSet, 0, nullptr);

    // Also bind the shared rendering descriptor set (which was passed to us as layout 1)
    commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, cloudPipelineLayout, 1, 1, &descriptorSets[0], 0, nullptr);

    // Image size is dynamically set to width x height
    commandBuffer.dispatch((width + 15) / 16, (height + 15) / 16, 1);
}

void Clouds::recreateFrameResources(vk::CommandPool commandPool, uint32_t width, uint32_t height)
{
    this->width = width;
    this->height = height;

    if (cloudOutputTexture) {
        cloudOutputTexture->cleanUp();
    }

    cloudOutputTexture = std::make_unique<Texture>();
    cloudOutputTexture->createImage(device, width, height, 1, vk::Format::eR16G16B16A16Sfloat, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, 1, vk::ImageCreateFlags{}, vk::ImageType::e2D, 1);
    cloudOutputTexture->createImageView(device, vk::Format::eR16G16B16A16Sfloat, vk::ImageAspectFlagBits::eColor, 1, vk::ImageViewType::e2D, 1);
    cloudOutputTexture->createTextureSampler(device);

    vk::CommandBuffer commandBuffer = Kataglyphis::VulkanRendererInternals::CommandBufferManager::beginCommandBuffer(device->getLogicalDevice(), commandPool);
    cloudOutputTexture->getVulkanImage().transitionImageLayout(commandBuffer, vk::ImageLayout::eUndefined, vk::ImageLayout::eGeneral, 1, vk::ImageAspectFlagBits::eColor);
    Kataglyphis::VulkanRendererInternals::CommandBufferManager::endAndSubmitCommandBuffer(device->getLogicalDevice(), commandPool, device->getGraphicsQueue(), commandBuffer);

    // Update descriptor set
    vk::DescriptorImageInfo outputImageInfo{};
    outputImageInfo.imageLayout = vk::ImageLayout::eGeneral;
    outputImageInfo.imageView = cloudOutputTexture->getImageView();

    vk::WriteDescriptorSet descriptorWrite{};
    descriptorWrite.dstSet = descriptorSet;
    descriptorWrite.dstBinding = 0;
    descriptorWrite.dstArrayElement = 0;
    descriptorWrite.descriptorType = vk::DescriptorType::eStorageImage;
    descriptorWrite.descriptorCount = 1;
    descriptorWrite.pImageInfo = &outputImageInfo;

    device->getLogicalDevice().updateDescriptorSets(1, &descriptorWrite, 0, nullptr);
}

void Clouds::cleanUp()
{
    if (device) {
        device->getLogicalDevice().destroyDescriptorPool(descriptorPool);
        device->getLogicalDevice().destroyDescriptorSetLayout(descriptorSetLayout);
        device->getLogicalDevice().destroyDescriptorSetLayout(noiseDescriptorSetLayout);
        if (noiseComputePipeline) device->getLogicalDevice().destroyPipeline(noiseComputePipeline);
        if (noisePipelineLayout) device->getLogicalDevice().destroyPipelineLayout(noisePipelineLayout);
        if (cloudComputePipeline) device->getLogicalDevice().destroyPipeline(cloudComputePipeline);
        if (cloudPipelineLayout) device->getLogicalDevice().destroyPipelineLayout(cloudPipelineLayout);
    }
    if (cloudNoiseTexture) { cloudNoiseTexture->cleanUp(); cloudNoiseTexture.reset(); }
    if (cloudOutputTexture) { cloudOutputTexture->cleanUp(); cloudOutputTexture.reset(); }
}

}