#define STB_IMAGE_IMPLEMENTATION #include <stdafx.h> #include "Texture.h" #include "Buffer/Buffer.h" #include "Context.h" #include "Device.h" namespace fpr { vk::UniqueImageView CreateImageView( fpr::Device* device, vk::Image image, vk::Format format, vk::ImageAspectFlags aspect_mask, vk::ImageViewType view_type, uint32_t mip_levels) { vk::ImageViewCreateInfo image_view_create_info{}; image_view_create_info.setImage(image); image_view_create_info.setViewType(view_type); image_view_create_info.format = format; image_view_create_info.components.setR(vk::ComponentSwizzle::eIdentity); image_view_create_info.components.setG(vk::ComponentSwizzle::eIdentity); image_view_create_info.components.setB(vk::ComponentSwizzle::eIdentity); image_view_create_info.components.setA(vk::ComponentSwizzle::eIdentity); image_view_create_info.subresourceRange.aspectMask = aspect_mask; image_view_create_info.subresourceRange.baseMipLevel = 0; image_view_create_info.subresourceRange.levelCount = mip_levels; image_view_create_info.subresourceRange.baseArrayLayer = 0; image_view_create_info.subresourceRange.layerCount = 1; auto [result_signal, image_view] = device->GetLogicalDeviceHandle().createImageViewUnique(image_view_create_info); assert(("Failed to create image view!", result_signal == vk::Result::eSuccess)); return std::move(image_view); } std::pair<vk::DeviceSize, stbi_uc*> Texture::GetImageDetails(std::string_view file_name, int desired_channels) { //Load raw image data and image size. int width; int height; int image_size; stbi_uc* raw_data = stbi_load(file_name.data(), &width, &height, &image_size, desired_channels); assert(raw_data != nullptr); m_width = (uint32_t)width; m_height = (uint32_t)height; m_channels_used = desired_channels; m_mip_levels = static_cast<uint32_t>(std::floor(std::log2(std::max(m_width, m_height)))) + 1; return { vk::DeviceSize((vk::DeviceSize)width * (vk::DeviceSize)height * (vk::DeviceSize)desired_channels), raw_data }; } const vk::DescriptorSet& Texture::GetDescriptorSet() const { return *m_descriptor_set; } void Texture::GenerateMipMaps() { //Run time mip map generation. auto& context = fpr::Context::Get(); auto& cmd_buffer = context.BeginSingleTimeCommands(); auto half_else_one = [=](uint32_t to_half) { return to_half > 1 ? to_half / 2 : 1; }; vk::ImageMemoryBarrier mip_img_barrier; mip_img_barrier.image = *m_image; mip_img_barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; mip_img_barrier.subresourceRange.layerCount = 1; mip_img_barrier.subresourceRange.levelCount = 1; mip_img_barrier.subresourceRange.baseArrayLayer = 0; mip_img_barrier.srcQueueFamilyIndex = 0; mip_img_barrier.dstQueueFamilyIndex = 0; uint32_t mip_width = m_width; uint32_t mip_height = m_height; //For each mip level, blit image and reduce resolution in half. for(uint32_t i = 1; i < m_mip_levels; ++i) { mip_img_barrier.subresourceRange.baseMipLevel = i - 1; mip_img_barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; mip_img_barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; mip_img_barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; mip_img_barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; cmd_buffer.pipelineBarrier( vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, vk::DependencyFlagBits::eByRegion, nullptr, nullptr, mip_img_barrier); vk::ImageBlit blit; blit.srcOffsets[0] = vk::Offset3D(0, 0, 0); blit.srcOffsets[1] = vk::Offset3D(mip_width, mip_height, 1); blit.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; blit.srcSubresource.mipLevel = i - 1; blit.srcSubresource.layerCount = 1; blit.dstOffsets[0] = vk::Offset3D(0, 0, 0); blit.dstOffsets[1] = vk::Offset3D(half_else_one(mip_width), half_else_one(mip_height), 1); blit.dstSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; blit.dstSubresource.mipLevel = i; blit.dstSubresource.layerCount = 1; cmd_buffer.blitImage( *m_image, vk::ImageLayout::eTransferSrcOptimal, *m_image, vk::ImageLayout::eTransferDstOptimal, blit, vk::Filter::eLinear); mip_img_barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; mip_img_barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; mip_img_barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; mip_img_barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; cmd_buffer.pipelineBarrier( vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, vk::DependencyFlagBits::eByRegion, nullptr, nullptr, mip_img_barrier); mip_width = half_else_one(mip_width); mip_height = half_else_one(mip_height); } //pipeline barrier to transition mip image layout. mip_img_barrier.subresourceRange.baseMipLevel = m_mip_levels - 1; mip_img_barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; mip_img_barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; mip_img_barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; mip_img_barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; cmd_buffer.pipelineBarrier( vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, vk::DependencyFlagBits::eByRegion, nullptr, nullptr, mip_img_barrier); context.EndSingleTimeCommands(); } Texture::Texture(std::string_view file_name, int desired_channels, vk::Format format): m_format(format) { auto& context = fpr::Context::Get(); auto device = context.GetDevice(); //Load raw bytes of image and load it into a staging buffer. Textures are generally unchanged throughout a program //so they can be transferred to the GPU with a staging buffer and left untouched. auto [size, raw_data] = GetImageDetails(file_name, desired_channels); vk::MemoryPropertyFlags flags = vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent; fpr::Buffer staging_buffer(size, vk::BufferUsageFlagBits::eTransferSrc, flags); staging_buffer.Update(raw_data); stbi_image_free(raw_data); vk::ImageCreateInfo img_create_info; img_create_info.setExtent({ m_width, m_height, 1 }); img_create_info.setFormat(format); img_create_info.setUsage( vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled); img_create_info.setImageType(vk::ImageType::e2D); img_create_info.setMipLevels(m_mip_levels); img_create_info.setArrayLayers(1); img_create_info.setTiling(vk::ImageTiling::eOptimal); img_create_info.setSamples(vk::SampleCountFlagBits::e1); img_create_info.setSharingMode(vk::SharingMode::eExclusive); auto [img_result, image] = device->GetLogicalDeviceHandle().createImageUnique(img_create_info); assert(img_result == vk::Result::eSuccess); m_image = std::move(image); m_memory = CreateImageMemory(device, *m_image); TransitionImageLayout(*m_image, PRE_MIP_LAYOUT, m_mip_levels); auto& transfer_cmd_buffer = context.BeginSingleTimeCommands(); vk::BufferImageCopy image_copy; image_copy.setBufferOffset(0); image_copy.setBufferRowLength(0); image_copy.setBufferImageHeight(0); image_copy.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; image_copy.imageSubresource.baseArrayLayer = 0; image_copy.imageSubresource.layerCount = 1; image_copy.imageSubresource.mipLevel = 0; image_copy.setImageExtent({ m_width, m_height, 1 }); image_copy.setImageOffset({ 0, 0, 0 }); transfer_cmd_buffer.copyBufferToImage( staging_buffer.GetBuffer(), *m_image, vk::ImageLayout::eTransferDstOptimal, image_copy); context.EndSingleTimeCommands(); GenerateMipMaps(); m_image_view = CreateImageView(device, *m_image, format, vk::ImageAspectFlagBits::eColor, vk::ImageViewType::e2D, m_mip_levels); CreateDescriptorSet(); } void Texture::CreateDescriptorSet() { auto& context = fpr::Context::Get(); auto device = context.GetDevice(); //Vulkan allows for image and samppler to be combined. Texture loaded in with Sampler. vk::DescriptorSetLayout sampler_layout = context.m_render_graph->DescriptorSetLayouts.Get("Sampler"); vk::DescriptorSetAllocateInfo desc_set_alloc_info; desc_set_alloc_info.descriptorPool = context.GetDescriptorPool(); desc_set_alloc_info.setDescriptorSetCount(1); desc_set_alloc_info.setSetLayouts(sampler_layout); auto [alloc_result, descriptors] = device->GetLogicalDeviceHandle().allocateDescriptorSetsUnique(desc_set_alloc_info); m_descriptor_set = std::move(descriptors[0]); assert(alloc_result == vk::Result::eSuccess); vk::DescriptorImageInfo image_info; image_info.setImageLayout(vk::ImageLayout::eShaderReadOnlyOptimal); image_info.setImageView(*m_image_view); image_info.setSampler(context.m_render_graph->Samplers.Get("Sampler")); vk::WriteDescriptorSet write_set; write_set.setImageInfo(image_info); write_set.setDstSet(*m_descriptor_set); write_set.setDescriptorType(vk::DescriptorType::eCombinedImageSampler); device->GetLogicalDeviceHandle().updateDescriptorSets(write_set, nullptr); } void TransitionImageLayout(vk::Image image, const LayoutTransitionRules& transition_rules, uint32_t mip_levels) { //Transition the image into the layout specified by the rules passed. auto& context = fpr::Context::Get(); auto& single_use_cmd_buffer = context.BeginSingleTimeCommands(); vk::ImageMemoryBarrier img_mem_barrier; img_mem_barrier.setOldLayout(transition_rules.old_layout); img_mem_barrier.setNewLayout(transition_rules.new_layout); img_mem_barrier.setImage(image); img_mem_barrier.subresourceRange.aspectMask = transition_rules.image_aspect; img_mem_barrier.subresourceRange.baseMipLevel = 0; img_mem_barrier.subresourceRange.baseArrayLayer = 0; img_mem_barrier.subresourceRange.layerCount = 1; img_mem_barrier.subresourceRange.levelCount = mip_levels; img_mem_barrier.srcAccessMask = transition_rules.src_access; img_mem_barrier.dstAccessMask = transition_rules.dst_access; single_use_cmd_buffer.pipelineBarrier( transition_rules.src_stage, transition_rules.dst_stage, vk::DependencyFlagBits::eByRegion, nullptr, nullptr, img_mem_barrier); context.EndSingleTimeCommands(); } } // namespace fpr