Program Listing for File textureLoader.cpp
↰ Return to documentation for file (PrismEngine/src/textureLoader.cpp)
#include "textureLoader.h"
#include "bufferWrapper.h"
#include "resourcesCreater.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include <stdexcept>
#include "pathes.h"
prism::PGC::Texture prism::PGC::L2::TextureLoader::loadTexture(prism::assets::TexturePath texturePath)
{
texturePath.path = basePath / texturesDir / texturePath.path;
PGC::Texture texture;
texture.assetSpec = texturePath;
texture.format = getFormatFromType(texturePath.type);
int texWidth, texHeight, texChannels;
stbi_uc* pixels = stbi_load(std::get<assets::TexturePath>(texture.assetSpec).path.generic_string().c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
if (!pixels) throw std::runtime_error("failed to load texture image!");
VkDeviceSize imageSize = static_cast<VkDeviceSize>(texWidth) * texHeight * 4;
texture.width = texWidth;
texture.height = texHeight;
texture.channels = texChannels;
texture.mipLevels = static_cast<uint32_t>(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1;
bool hasMipmaps = texture.mipLevels > 1;
createTextureImageFromData(texture, pixels, imageSize, !hasMipmaps);
createTextureImageView(texture);
createTextureSampler(texture);
stbi_image_free(pixels);
if (hasMipmaps) {
generateMipmaps(texture.image, texture.format, texWidth, texHeight, texture.mipLevels);
}
return texture;
}
prism::PGC::Texture prism::PGC::L2::TextureLoader::loadColor(prism::assets::TextureColor textureColor)
{
PGC::Texture texture;
texture.assetSpec = textureColor;
texture.format = getFormatFromType(textureColor.type);
texture.width = 1;
texture.height = 1;
texture.mipLevels = 1;
texture.channels = 4;
createTextureImageFromData(texture, textureColor.color.data(), textureColor.color.size() * sizeof(textureColor.color[0]), true);
createTextureImageView(texture);
createTextureSampler(texture);
return texture;
}
void prism::PGC::L2::TextureLoader::cleanup(PGC::Texture* texture)
{
vkDestroySampler(context->device, texture->sampler, nullptr);
texture->sampler = VK_NULL_HANDLE;
vkDestroyImageView(context->device, texture->imageView, nullptr);
texture->imageView = VK_NULL_HANDLE;
vkDestroyImage(context->device, texture->image, nullptr);
texture->image = VK_NULL_HANDLE;
vkFreeMemory(context->device, texture->imageMemory, nullptr);
texture->imageMemory = VK_NULL_HANDLE;
}
void prism::PGC::L2::TextureLoader::createTextureImageFromData(PGC::Texture& texture, const void* pixelData, VkDeviceSize dataSize, bool finalizeLayout)
{
VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;
PGC::L3::BufferWrapper::createBuffer(context, dataSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
void* mapped;
vkMapMemory(context->device, stagingBufferMemory, 0, dataSize, 0, &mapped);
memcpy(mapped, pixelData, static_cast<size_t>(dataSize));
vkUnmapMemory(context->device, stagingBufferMemory);
PGC::L3::ResourcesCreater::createImage(context, texture.width, texture.height, texture.mipLevels,
VK_SAMPLE_COUNT_1_BIT, texture.format,
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,
texture.image, texture.imageMemory);
PGC::L3::BufferWrapper::transitionImageLayout(context, texture.image, texture.format, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, texture.mipLevels);
PGC::L3::BufferWrapper::copyBufferToImage(context, stagingBuffer, texture.image, static_cast<uint32_t>(texture.width), static_cast<uint32_t>(texture.height));
if (finalizeLayout) {
PGC::L3::BufferWrapper::transitionImageLayout(context, texture.image, texture.format, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, texture.mipLevels);
}
vkDestroyBuffer(context->device, stagingBuffer, nullptr);
vkFreeMemory(context->device, stagingBufferMemory, nullptr);
}
void prism::PGC::L2::TextureLoader::createTextureImageView(PGC::Texture& texture)
{
texture.imageView = PGC::L3::ResourcesCreater::createImageView(context->device, texture.image, texture.format, VK_IMAGE_ASPECT_COLOR_BIT, texture.mipLevels);
}
void prism::PGC::L2::TextureLoader::createTextureSampler(PGC::Texture& texture)
{
PGC::L3::ResourcesCreater::createTextureSampler(context, &texture.sampler, texture.height * texture.width == 1);
}
void prism::PGC::L2::TextureLoader::generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels)
{
VkFormatProperties formatProperties;
vkGetPhysicalDeviceFormatProperties(context->physicalDevice, imageFormat, &formatProperties);
if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) {
throw std::runtime_error("texture image format does not support linear blitting!");
}
VkCommandBuffer commandBuffer = PGC::L3::BufferWrapper::beginSingleTimeCommands(context);
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;
int32_t mipWidth = texWidth;
int32_t mipHeight = texHeight;
for (uint32_t i = 1; i < mipLevels; i++) {
barrier.subresourceRange.baseMipLevel = i - 1;
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(commandBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
0, nullptr,
0, nullptr,
1, &barrier);
VkImageBlit blit{};
blit.srcOffsets[0] = { 0, 0, 0 };
blit.srcOffsets[1] = { mipWidth, mipHeight, 1 };
blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blit.srcSubresource.mipLevel = i - 1;
blit.srcSubresource.baseArrayLayer = 0;
blit.srcSubresource.layerCount = 1;
blit.dstOffsets[0] = { 0, 0, 0 };
blit.dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 };
blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
blit.dstSubresource.mipLevel = i;
blit.dstSubresource.baseArrayLayer = 0;
blit.dstSubresource.layerCount = 1;
vkCmdBlitImage(commandBuffer,
image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1, &blit,
VK_FILTER_LINEAR);
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(commandBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
0, nullptr,
0, nullptr,
1, &barrier);
if (mipWidth > 1) mipWidth /= 2;
if (mipHeight > 1) mipHeight /= 2;
}
barrier.subresourceRange.baseMipLevel = mipLevels - 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(commandBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0,
0, nullptr,
0, nullptr,
1, &barrier);
PGC::L3::BufferWrapper::endSingleTimeCommands(context, commandBuffer);
}