Program Listing for File VulkanImage.cpp

Program Listing for File VulkanImage.cpp#

Return to documentation for file (Src/GraphicsEngineVulkan/vulkan_base/VulkanImage.cpp)

module;

#include <cstdint>
#include <tuple>
#include <utility>
#include <vulkan/vulkan.hpp>

#include "common/MemoryHelper.hpp"
#include "common/Utilities.hpp"

module kataglyphis.vulkan.image;

import kataglyphis.vulkan.device;
import kataglyphis.vulkan.command_buffer_manager;

Kataglyphis::VulkanImage::VulkanImage() = default;

Kataglyphis::VulkanImage::VulkanImage(VulkanImage &&other) noexcept
  : device(other.device), image(other.image), imageMemory(other.imageMemory)
{
    other.device = nullptr;
    other.image = nullptr;
    other.imageMemory = nullptr;
}

auto Kataglyphis::VulkanImage::operator=(VulkanImage &&other) noexcept -> VulkanImage &
{
    if (this != &other) {
        cleanUp();

        device = other.device;
        image = other.image;
        imageMemory = other.imageMemory;

        other.device = nullptr;
        other.image = nullptr;
        other.imageMemory = nullptr;
    }

    return *this;
}

void Kataglyphis::VulkanImage::create(VulkanDevice *in_device,
  uint32_t width,
  uint32_t height,
  uint32_t mip_levels,
  vk::Format format,
  vk::ImageTiling tiling,
  vk::ImageUsageFlags use_flags,
  vk::MemoryPropertyFlags prop_flags)
{
    this->device = in_device;
    // CREATE image
    // image creation info
    vk::ImageCreateInfo image_create_info{};
    image_create_info.imageType = vk::ImageType::e2D;// type of image (1D, 2D, 3D)
    image_create_info.extent.width = width;// width if image extent
    image_create_info.extent.height = height;// height if image extent
    image_create_info.extent.depth = 1;// height if image extent
    image_create_info.mipLevels = mip_levels;// number of mipmap levels
    image_create_info.arrayLayers = 1;// number of levels in image array
    image_create_info.format = format;// format type of image
    image_create_info.tiling = tiling;// tiling of image ("arranged" for optimal reading)
    image_create_info.initialLayout = vk::ImageLayout::eUndefined;// layout of image data on creation
    image_create_info.usage = use_flags;// bit flags defining what image will be used for
    image_create_info.samples = vk::SampleCountFlagBits::e1;// number of samples for multisampling
    image_create_info.sharingMode = vk::SharingMode::eExclusive;// whether image can be shared between queues

    image = device->getLogicalDevice().createImage(image_create_info).value;

    // CREATE memory for image
    // get memory requirements for a type of image
    vk::MemoryRequirements memory_requirements = device->getLogicalDevice().getImageMemoryRequirements(image);

    // allocate memory using image requirements and user defined properties
    vk::MemoryAllocateInfo memory_alloc_info{};
    memory_alloc_info.allocationSize = memory_requirements.size;
    memory_alloc_info.memoryTypeIndex =
      Kataglyphis::find_memory_type_index(device->getPhysicalDevice(), memory_requirements.memoryTypeBits, prop_flags);

    imageMemory = device->getLogicalDevice().allocateMemory(memory_alloc_info).value;

    // connect memory to image
    std::ignore = device->getLogicalDevice().bindImageMemory(image, imageMemory, 0);
}

void Kataglyphis::VulkanImage::transitionImageLayout(vk::Device in_logical_device,
  vk::Queue queue,
  vk::CommandPool command_pool,
  vk::ImageLayout old_layout,
  vk::ImageLayout new_layout,
  vk::ImageAspectFlags aspectMask,
  uint32_t mip_levels)
{
    vk::CommandBuffer command_buffer =
      Kataglyphis::VulkanRendererInternals::CommandBufferManager::beginCommandBuffer(in_logical_device, command_pool);

    // vk::ImageAspectFlagBits::eColor
    vk::ImageMemoryBarrier memory_barrier{};
    memory_barrier.oldLayout = old_layout;
    memory_barrier.newLayout = new_layout;
    memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;// Queue family to transition from
    memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;// Queue family to transition to
    memory_barrier.image = image;// image being accessed and modified as part of barrier
    memory_barrier.subresourceRange.aspectMask = aspectMask;// aspect of image being altered
    memory_barrier.subresourceRange.baseMipLevel = 0;// first mip level to start alterations on
    memory_barrier.subresourceRange.levelCount = mip_levels;// number of mip levels to alter starting from baseMipLevel
    memory_barrier.subresourceRange.baseArrayLayer = 0;// first layer to start alterations on
    memory_barrier.subresourceRange.layerCount = 1;// number of layers to alter starting from baseArrayLayer

    // if transitioning from new image to image ready to receive data
    memory_barrier.srcAccessMask = accessFlagsForImageLayout(old_layout);
    memory_barrier.dstAccessMask = accessFlagsForImageLayout(new_layout);

    vk::PipelineStageFlags const src_stage = pipelineStageForLayout(old_layout);
    vk::PipelineStageFlags const dst_stage = pipelineStageForLayout(new_layout);

    command_buffer.pipelineBarrier(src_stage,
      dst_stage,// pipeline stages (match to src and dst accessmask)
      {},// no dependency flags
      nullptr,// memory barriers
      nullptr,// buffer memory barriers
      memory_barrier// image memory barriers
    );

    Kataglyphis::VulkanRendererInternals::CommandBufferManager::endAndSubmitCommandBuffer(
      in_logical_device, command_pool, queue, command_buffer);
}

void Kataglyphis::VulkanImage::transitionImageLayout(vk::CommandBuffer command_buffer,
  vk::ImageLayout old_layout,
  vk::ImageLayout new_layout,
  uint32_t mip_levels,
  vk::ImageAspectFlags aspectMask)
{
    vk::ImageMemoryBarrier memory_barrier{};
    memory_barrier.oldLayout = old_layout;
    memory_barrier.newLayout = new_layout;
    memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;// Queue family to transition from
    memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;// Queue family to transition to
    memory_barrier.image = image;// image being accessed and modified as part of barrier
    memory_barrier.subresourceRange.aspectMask = aspectMask;// aspect of image being altered
    memory_barrier.subresourceRange.baseMipLevel = 0;// first mip level to start alterations on
    memory_barrier.subresourceRange.levelCount = mip_levels;// number of mip levels to alter starting from baseMipLevel
    memory_barrier.subresourceRange.baseArrayLayer = 0;// first layer to start alterations on
    memory_barrier.subresourceRange.layerCount = 1;// number of layers to alter starting from baseArrayLayer

    memory_barrier.srcAccessMask = accessFlagsForImageLayout(old_layout);
    memory_barrier.dstAccessMask = accessFlagsForImageLayout(new_layout);

    vk::PipelineStageFlags const src_stage = pipelineStageForLayout(old_layout);
    vk::PipelineStageFlags const dst_stage = pipelineStageForLayout(new_layout);

    // if transitioning from new image to image ready to receive data

    command_buffer.pipelineBarrier(src_stage,
      dst_stage,// pipeline stages (match to src and dst accessmask)
      {},// no dependency flags
      nullptr,// memory barriers
      nullptr,// buffer memory barriers
      memory_barrier// image memory barriers
    );
}

void Kataglyphis::VulkanImage::setImage(vk::Image in_image) { this->image = in_image; }

void Kataglyphis::VulkanImage::cleanUp()
{
    if (device != nullptr) {
        if (image) { device->getLogicalDevice().destroyImage(image); }
        if (imageMemory) { device->getLogicalDevice().freeMemory(imageMemory); }
    }

    image = nullptr;
    imageMemory = nullptr;
}

Kataglyphis::VulkanImage::~VulkanImage() = default;

auto Kataglyphis::VulkanImage::accessFlagsForImageLayout(vk::ImageLayout layout) -> vk::AccessFlags
{
    switch (layout) {
    case vk::ImageLayout::ePreinitialized:
        return vk::AccessFlagBits::eHostWrite;
    case vk::ImageLayout::eTransferDstOptimal:
        return vk::AccessFlagBits::eTransferWrite;
    case vk::ImageLayout::eTransferSrcOptimal:
        return vk::AccessFlagBits::eTransferRead;
    case vk::ImageLayout::eColorAttachmentOptimal:
        return vk::AccessFlagBits::eColorAttachmentWrite;
    case vk::ImageLayout::eDepthStencilAttachmentOptimal:
        return vk::AccessFlagBits::eDepthStencilAttachmentWrite;
    case vk::ImageLayout::eShaderReadOnlyOptimal:
        return vk::AccessFlagBits::eShaderRead;
    case vk::ImageLayout::eGeneral:
        return vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite
               | vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eTransferRead
               | vk::AccessFlagBits::eTransferWrite;
    default:
        return vk::AccessFlags();
    }
}

auto Kataglyphis::VulkanImage::pipelineStageForLayout(vk::ImageLayout oldImageLayout) -> vk::PipelineStageFlags
{
    switch (oldImageLayout) {
    case vk::ImageLayout::eTransferDstOptimal:
    case vk::ImageLayout::eTransferSrcOptimal:
        return vk::PipelineStageFlagBits::eTransfer;
    case vk::ImageLayout::eColorAttachmentOptimal:
        return vk::PipelineStageFlagBits::eColorAttachmentOutput;
    case vk::ImageLayout::eDepthStencilAttachmentOptimal:
        return vk::PipelineStageFlagBits::eAllCommands;// We do this to allow queue
                                                       // other than graphic return
                                                       // vk::PipelineStageFlagBits::eEarlyFragmentTests;
    case vk::ImageLayout::eShaderReadOnlyOptimal:
        return vk::PipelineStageFlagBits::eAllCommands;// We do this to allow queue
                                                       // other than graphic return
                                                       // vk::PipelineStageFlagBits::eFragmentShader;
    case vk::ImageLayout::ePreinitialized:
        return vk::PipelineStageFlagBits::eHost;
    case vk::ImageLayout::eUndefined:
        return vk::PipelineStageFlagBits::eTopOfPipe;
    case vk::ImageLayout::eGeneral:
        return vk::PipelineStageFlagBits::eAllCommands;
    default:
        return vk::PipelineStageFlagBits::eBottomOfPipe;
    }
}