Add mipmaps to texture creation pipeline

This commit is contained in:
Lillian Salehi 2024-10-18 11:57:07 -05:00
parent 6e8e75d5b3
commit 0797c418da
7 changed files with 119 additions and 17 deletions

View File

@ -285,7 +285,7 @@ namespace device_libs {
vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr); vkDestroySwapchainKHR(Global::device, Global::swapChain, nullptr);
if(Global::enableValidationLayers) std::cout << "Destroyed Swap Chain safely\n" << std::endl; if(Global::enableValidationLayers) std::cout << "Destroyed Swap Chain safely\n" << std::endl;
} }
VkImageView DeviceControl::createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags) { VkImageView DeviceControl::createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags, uint32_t mipLevels) {
// This defines the parameters of a newly created image object! // This defines the parameters of a newly created image object!
VkImageViewCreateInfo viewInfo{}; VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
@ -297,6 +297,7 @@ namespace device_libs {
viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0; viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1; viewInfo.subresourceRange.layerCount = 1;
viewInfo.subresourceRange.levelCount = mipLevels;
VkImageView imageView; VkImageView imageView;
if (vkCreateImageView(Global::device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) { if (vkCreateImageView(Global::device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) {
@ -309,7 +310,7 @@ namespace device_libs {
swapChainImageViews.resize(swapChainImages.size()); swapChainImageViews.resize(swapChainImages.size());
for (uint32_t i = 0; i < swapChainImages.size(); i++) { for (uint32_t i = 0; i < swapChainImages.size(); i++) {
swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT); swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1);
} }
} }
void DeviceControl::destroyImageViews() { void DeviceControl::destroyImageViews() {

View File

@ -14,7 +14,7 @@ class DeviceControl {
static void destroySurface(VkInstance& instance); static void destroySurface(VkInstance& instance);
static void createSwapChain(GLFWwindow* window); static void createSwapChain(GLFWwindow* window);
static void destroySwapChain(); static void destroySwapChain();
static VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags); static VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags flags, uint32_t mipLevels);
static void createImageViews(); static void createImageViews();
static void destroyImageViews(); static void destroyImageViews();
static void createCommandPool(); static void createCommandPool();

View File

@ -203,9 +203,9 @@ namespace buffers_libs {
ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(30.0f), glm::vec3(0.0f, 0.0f, 1.0f)); ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(30.0f), glm::vec3(0.0f, 0.0f, 1.0f));
// Modify the view transformation to look at the object from above at a 45 degree angle. // Modify the view transformation to look at the object from above at a 45 degree angle.
// This takes the eye position, center position, and the up direction. // This takes the eye position, center position, and the up direction.
ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 3.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f*time, 2.0f), glm::vec3(0.0f, 0.0f, 0.5f), glm::vec3(0.0f, 0.0f, 1.0f));
// 45 degree field of view, set aspect ratio, and near and far clipping range. // 45 degree field of view, set aspect ratio, and near and far clipping range.
ubo.proj = glm::perspective(glm::radians(45.0f), device_libs::DeviceControl::getSwapChainExtent().width / (float) device_libs::DeviceControl::getSwapChainExtent().height, 0.1f, 10.0f); ubo.proj = glm::perspective(glm::radians(45.0f), device_libs::DeviceControl::getSwapChainExtent().width / (float) device_libs::DeviceControl::getSwapChainExtent().height, 0.1f, 100.0f);
// GLM was created for OpenGL, where the Y coordinate was inverted. This simply flips the sign. // GLM was created for OpenGL, where the Y coordinate was inverted. This simply flips the sign.
ubo.proj[1][1] *= -1; ubo.proj[1][1] *= -1;
@ -285,3 +285,4 @@ namespace buffers_libs {
vkDestroyDescriptorPool(Global::device, descriptorPool, nullptr); vkDestroyDescriptorPool(Global::device, descriptorPool, nullptr);
} }
} }

View File

@ -1,7 +1,10 @@
#include <cstdint>
#include <vulkan/vulkan_core.h>
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#include <stb/stb_image.h> #include <stb/stb_image.h>
#include "texture.h" #include "texture.h"
uint32_t mipLevels;
VkImage textureImage; VkImage textureImage;
VkDeviceMemory textureImageMemory; VkDeviceMemory textureImageMemory;
@ -9,7 +12,7 @@ VkPipelineStageFlags sourceStage;
VkPipelineStageFlags destinationStage; VkPipelineStageFlags destinationStage;
namespace texture_libs { namespace texture_libs {
void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) { void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory) {
// This function specifies all the data in an image object, this is called directly after the creation of an image object. // This function specifies all the data in an image object, this is called directly after the creation of an image object.
VkImageCreateInfo imageInfo{}; VkImageCreateInfo imageInfo{};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
@ -25,6 +28,7 @@ namespace texture_libs {
imageInfo.usage = usage; imageInfo.usage = usage;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.mipLevels = mipLevels;
if (vkCreateImage(Global::device, &imageInfo, nullptr, &image) != VK_SUCCESS) { if (vkCreateImage(Global::device, &imageInfo, nullptr, &image) != VK_SUCCESS) {
throw std::runtime_error("failed to create image!"); throw std::runtime_error("failed to create image!");
@ -90,7 +94,7 @@ namespace texture_libs {
endSingleTimeCommands(commandBuffer); endSingleTimeCommands(commandBuffer);
} }
void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) { void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels) {
// This function handles transitioning image layout data from one layout to another. // This function handles transitioning image layout data from one layout to another.
VkCommandBuffer commandBuffer = beginSingleTimeCommands(); VkCommandBuffer commandBuffer = beginSingleTimeCommands();
@ -104,7 +108,7 @@ namespace texture_libs {
barrier.image = image; barrier.image = image;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0; barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1; barrier.subresourceRange.levelCount = mipLevels;
barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1; barrier.subresourceRange.layerCount = 1;
@ -175,16 +179,102 @@ void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t
} }
throw std::runtime_error("failed to find supported depth buffering format!"); throw std::runtime_error("failed to find supported depth buffering format!");
} }
bool hasStencilComponent(VkFormat format) { bool hasStencilComponent(VkFormat format) {
return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT;
} }
void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t textureWidth, int32_t textureHeight, uint32_t mipLevels) {
// Check if image format supports linear blitting
VkFormatProperties formatProperties;
vkGetPhysicalDeviceFormatProperties(Global::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 = beginSingleTimeCommands();
// Specify the parameters of an image memory barrier
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 = textureWidth;
int32_t mipHeight = textureHeight;
for(uint32_t mip = 1; mip < mipLevels; mip++) {
barrier.subresourceRange.baseMipLevel = mip - 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 = mip - 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 = mip;
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);
endSingleTimeCommands(commandBuffer);
}
// -------------------------------- Image Libraries ------------------------------- // // -------------------------------- Image Libraries ------------------------------- //
void Texture::createTextureImage() { void Texture::createTextureImage() {
// Import pixels from image with data on color channels, width and height, and colorspace! // Import pixels from image with data on color channels, width and height, and colorspace!
// Its a lot of kind of complicated memory calls to bring it from a file -> to a buffer -> to a image object. // Its a lot of kind of complicated memory calls to bring it from a file -> to a buffer -> to a image object.
int textureWidth, textureHeight, textureChannels; int textureWidth, textureHeight, textureChannels;
stbi_uc* pixels = stbi_load(Global::TEXTURE_PATH.c_str(), &textureWidth, &textureHeight, &textureChannels, STBI_rgb_alpha); stbi_uc* pixels = stbi_load(Global::TEXTURE_PATH.c_str(), &textureWidth, &textureHeight, &textureChannels, STBI_rgb_alpha);
mipLevels = static_cast<uint32_t>(std::floor(std::log2(std::max(textureWidth, textureHeight)))) + 1;
VkDeviceSize imageSize = textureWidth * textureHeight * 4; VkDeviceSize imageSize = textureWidth * textureHeight * 4;
@ -202,18 +292,19 @@ void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t
stbi_image_free(pixels); stbi_image_free(pixels);
createImage(textureWidth, textureHeight, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory); createImage(textureWidth, textureHeight, mipLevels, VK_FORMAT_R8G8B8A8_SRGB, 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, textureImage, textureImageMemory);
transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels);
copyBufferToImage(stagingBuffer, textureImage, static_cast<uint32_t>(textureWidth), static_cast<uint32_t>(textureHeight)); copyBufferToImage(stagingBuffer, textureImage, static_cast<uint32_t>(textureWidth), static_cast<uint32_t>(textureHeight));
transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
vkDestroyBuffer(Global::device, stagingBuffer, nullptr); vkDestroyBuffer(Global::device, stagingBuffer, nullptr);
vkFreeMemory(Global::device, stagingBufferMemory, nullptr); vkFreeMemory(Global::device, stagingBufferMemory, nullptr);
generateMipmaps(textureImage, VK_FORMAT_R8G8B8A8_SRGB, textureWidth, textureHeight, mipLevels);
} }
void Texture::createTextureImageView() { void Texture::createTextureImageView() {
// Create a texture image view, which is a struct of information about the image. // Create a texture image view, which is a struct of information about the image.
Global::textureImageView = device_libs::DeviceControl::createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT); Global::textureImageView = device_libs::DeviceControl::createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT, mipLevels);
} }
void Texture::createTextureSampler() { void Texture::createTextureSampler() {
// Create a sampler to access and parse the texture object. // Create a sampler to access and parse the texture object.
@ -251,7 +342,7 @@ void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerInfo.mipLodBias = 0.0f; samplerInfo.mipLodBias = 0.0f;
samplerInfo.minLod = 0.0f; samplerInfo.minLod = 0.0f;
samplerInfo.maxLod = 0.0f; samplerInfo.maxLod = VK_LOD_CLAMP_NONE;
if (vkCreateSampler(Global::device, &samplerInfo, nullptr, &Global::textureSampler) != VK_SUCCESS) { if (vkCreateSampler(Global::device, &samplerInfo, nullptr, &Global::textureSampler) != VK_SUCCESS) {
throw std::runtime_error("failed to create texture sampler!"); throw std::runtime_error("failed to create texture sampler!");
@ -275,11 +366,15 @@ void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t
void Texture::createDepthResources() { void Texture::createDepthResources() {
VkFormat depthFormat = findDepthFormat(); VkFormat depthFormat = findDepthFormat();
VkExtent2D swapChainExtent = device_libs::DeviceControl::getSwapChainExtent(); VkExtent2D swapChainExtent = device_libs::DeviceControl::getSwapChainExtent();
createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, Global::depthImage, Global::depthImageMemory); createImage(swapChainExtent.width, swapChainExtent.height, 1, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, Global::depthImage, Global::depthImageMemory);
Global::depthImageView = device_libs::DeviceControl::createImageView(Global::depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); Global::depthImageView = device_libs::DeviceControl::createImageView(Global::depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, 1);
// Explicit transition from the layout of the image to the depth attachment is unnecessary here, since that will be handled in the render pass! // Explicit transition from the layout of the image to the depth attachment is unnecessary here, since that will be handled in the render pass!
}
// ---------------------------- Getters & Setters ---------------------------------//
uint32_t Texture::getMipLevels() {
return mipLevels;
} }
} }

View File

@ -13,5 +13,8 @@ namespace texture_libs {
static void destroyTextureSampler(); static void destroyTextureSampler();
static VkFormat findDepthFormat(); static VkFormat findDepthFormat();
static void createDepthResources(); static void createDepthResources();
// ------------ Getters & Setters ------------ //
static uint32_t getMipLevels();
}; };
} }

View File

@ -7,5 +7,6 @@ layout(location = 1) in vec2 fragTexCoord;
layout(location = 0) out vec4 outColor; layout(location = 0) out vec4 outColor;
void main() { void main() {
outColor = vec4(texture(texSampler, fragTexCoord).rgb, 1.0); outColor = vec4(texture(texSampler, fragTexCoord).rgb, 1.0);
} }

View File

@ -18,6 +18,7 @@ layout(location = 0) out vec3 fragColor;
layout(location = 1) out vec2 fragTexCoord; layout(location = 1) out vec2 fragTexCoord;
void main() { void main() {
gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0);
fragColor = inColor; fragColor = inColor;
fragTexCoord = inTexCoord; fragTexCoord = inTexCoord;