Program Listing for File Texture.cpp

Return to documentation for file (Src/GraphicsEngineVulkan/scene/Texture.cpp)

#include "scene/Texture.hpp"

#include "common/Utilities.hpp"
#include "spdlog/spdlog.h"
#include <cmath>
#include <stdexcept>

using namespace Kataglyphis;

Kataglyphis::Texture::Texture() {}

void Kataglyphis::Texture::createFromFile(VulkanDevice *device, VkCommandPool commandPool, const std::string &fileName)
{
    int width, height;
    VkDeviceSize size;
    stbi_uc *image_data = loadTextureData(fileName, &width, &height, &size);

    mip_levels = static_cast<uint32_t>(std::floor(std::log2(std::max(width, height)))) + 1;

    // create staging buffer to hold loaded data, ready to copy to device
    VulkanBuffer stagingBuffer;
    stagingBuffer.create(device,
      size,
      VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
      VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);

    // copy image data to staging buffer
    void *data;
    vkMapMemory(device->getLogicalDevice(), stagingBuffer.getBufferMemory(), 0, size, 0, &data);
    memcpy(data, image_data, static_cast<size_t>(size));
    vkUnmapMemory(device->getLogicalDevice(), stagingBuffer.getBufferMemory());

    // free original image data
    stbi_image_free(image_data);

    createImage(device,
      width,
      height,
      mip_levels,
      VK_FORMAT_R8G8B8A8_UNORM,
      VK_IMAGE_TILING_OPTIMAL,
      VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
      VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

    // copy data to image
    // transition image to be DST for copy operation
    vulkanImage.transitionImageLayout(device->getLogicalDevice(),
      device->getGraphicsQueue(),
      commandPool,
      VK_IMAGE_LAYOUT_UNDEFINED,
      VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
      VK_IMAGE_ASPECT_COLOR_BIT,
      mip_levels);

    // copy data to image
    vulkanBufferManager.copyImageBuffer(device->getLogicalDevice(),
      device->getGraphicsQueue(),
      commandPool,
      stagingBuffer.getBuffer(),
      vulkanImage.getImage(),
      width,
      height);

    // generate mipmaps
    generateMipMaps(device->getPhysicalDevice(),
      device->getLogicalDevice(),
      commandPool,
      device->getGraphicsQueue(),
      vulkanImage.getImage(),
      VK_FORMAT_R8G8B8A8_SRGB,
      width,
      height,
      mip_levels);

    stagingBuffer.cleanUp();

    createImageView(device, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT, mip_levels);
}

void Kataglyphis::Texture::setImage(VkImage image) { vulkanImage.setImage(image); }

void Kataglyphis::Texture::setImageView(VkImageView imageView) { vulkanImageView.setImageView(imageView); }

void Kataglyphis::Texture::createImage(VulkanDevice *device,
  uint32_t width,
  uint32_t height,
  uint32_t mip_levels,
  VkFormat format,
  VkImageTiling tiling,
  VkImageUsageFlags use_flags,
  VkMemoryPropertyFlags prop_flags)
{
    vulkanImage.create(device, width, height, mip_levels, format, tiling, use_flags, prop_flags);
}

void Kataglyphis::Texture::createImageView(VulkanDevice *device,
  VkFormat format,
  VkImageAspectFlags aspect_flags,
  uint32_t mip_levels)
{
    vulkanImageView.create(device, vulkanImage.getImage(), format, aspect_flags, mip_levels);
}

void Kataglyphis::Texture::cleanUp()
{
    vulkanImageView.cleanUp();
    vulkanImage.cleanUp();
}

Kataglyphis::Texture::~Texture() {}

stbi_uc *
  Kataglyphis::Texture::loadTextureData(const std::string &file_name, int *width, int *height, VkDeviceSize *image_size)
{
    // number of channels image uses
    int channels;
    // load pixel data for image
    // std::string file_loc = "../Resources/Textures/" + file_name;
    stbi_uc *image = stbi_load(file_name.c_str(), width, height, &channels, STBI_rgb_alpha);

    if (!image) { spdlog::error("Failed to load a texture file! (" + file_name + ")"); }

    // calculate image size using given and known data
    *image_size = *width * *height * 4;

    return image;
}

void Kataglyphis::Texture::generateMipMaps(VkPhysicalDevice physical_device,
  VkDevice device,
  VkCommandPool command_pool,
  VkQueue queue,
  VkImage image,
  VkFormat image_format,
  int32_t width,
  int32_t height,
  uint32_t mip_levels)
{
    // Check if image format supports linear blitting
    VkFormatProperties formatProperties;
    vkGetPhysicalDeviceFormatProperties(physical_device, image_format, &formatProperties);

    if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) {
        spdlog::error("Texture image format does not support linear blitting!");
    }

    VkCommandBuffer command_buffer = commandBufferManager.beginCommandBuffer(device, command_pool);

    VkImageMemoryBarrier barrier{};
    barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    barrier.image = image;
    barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    barrier.subresourceRange.baseArrayLayer = 0;
    barrier.subresourceRange.layerCount = 1;
    barrier.subresourceRange.levelCount = 1;

    // TEMP VARS needed for decreasing step by step for factor 2
    int32_t tmp_width = width;
    int32_t tmp_height = height;

    // -- WE START AT 1 !
    for (uint32_t i = 1; i < mip_levels; i++) {
        // WAIT for previous mip map level for being ready
        barrier.subresourceRange.baseMipLevel = i - 1;
        // HERE we TRANSITION for having a SRC format now
        barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
        barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
        barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
        barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;

        vkCmdPipelineBarrier(command_buffer,
          VK_PIPELINE_STAGE_TRANSFER_BIT,
          VK_PIPELINE_STAGE_TRANSFER_BIT,
          0,
          0,
          nullptr,
          0,
          nullptr,
          1,
          &barrier);

        // when barrier over we can now blit :)
        VkImageBlit blit{};

        // -- OFFSETS describing the 3D-dimesnion of the region
        blit.srcOffsets[0] = { 0, 0, 0 };
        blit.srcOffsets[1] = { tmp_width, tmp_height, 1 };
        blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        // copy from previous level
        blit.srcSubresource.mipLevel = i - 1;
        blit.srcSubresource.baseArrayLayer = 0;
        blit.srcSubresource.layerCount = 1;
        // -- OFFSETS describing the 3D-dimesnion of the region
        blit.dstOffsets[0] = { 0, 0, 0 };
        blit.dstOffsets[1] = { tmp_width > 1 ? tmp_width / 2 : 1, tmp_height > 1 ? tmp_height / 2 : 1, 1 };
        blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        // -- COPY to next mipmap level
        blit.dstSubresource.mipLevel = i;
        blit.dstSubresource.baseArrayLayer = 0;
        blit.dstSubresource.layerCount = 1;

        vkCmdBlitImage(command_buffer,
          image,
          VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
          image,
          VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
          1,
          &blit,
          VK_FILTER_LINEAR);

        // REARRANGE image formats for having the correct image formats again
        barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
        barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
        barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
        barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;

        vkCmdPipelineBarrier(command_buffer,
          VK_PIPELINE_STAGE_TRANSFER_BIT,
          VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
          0,
          0,
          nullptr,
          0,
          nullptr,
          1,
          &barrier);

        if (tmp_width > 1) tmp_width /= 2;
        if (tmp_height > 1) tmp_height /= 2;
    }

    barrier.subresourceRange.baseMipLevel = mip_levels - 1;
    barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
    barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
    barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
    barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;

    vkCmdPipelineBarrier(command_buffer,
      VK_PIPELINE_STAGE_TRANSFER_BIT,
      VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
      0,
      0,
      nullptr,
      0,
      nullptr,
      1,
      &barrier);

    commandBufferManager.endAndSubmitCommandBuffer(device, command_pool, queue, command_buffer);
}