/* * Copyright 2022 Frederico de Oliveira Linhares * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "texture.hpp" #include #include #include "../command.hpp" #include "../core.hpp" #include "image.hpp" #include "source_buffer.hpp" namespace { struct TextureBuilder { VK::Texture *texture; std::string texture_path; TextureBuilder(VK::Texture *t, std::string tp); TextureBuilder(VK::Texture *t, const char* tp); }; TextureBuilder::TextureBuilder(VK::Texture *t, std::string tp): texture{t}, texture_path{tp} { } TextureBuilder::TextureBuilder(VK::Texture *t, const char* tp): TextureBuilder{t, std::string(tp)} { } void move_image_state( VkCommandBuffer vk_command_buffer, VkImage vk_image, VkFormat format, VkAccessFlags src_access_mask, VkAccessFlags dst_access_mask, VkImageLayout old_layout, VkImageLayout new_layout, VkPipelineStageFlags source_stage, VkPipelineStageFlags destination_stage) { VkImageMemoryBarrier barrier{}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.pNext = nullptr; barrier.srcAccessMask = src_access_mask; barrier.dstAccessMask = dst_access_mask; barrier.oldLayout = old_layout; barrier.newLayout = new_layout; barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.image = vk_image; barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; barrier.subresourceRange.baseMipLevel = 0; barrier.subresourceRange.levelCount = 1; barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = 1; vkCmdPipelineBarrier( vk_command_buffer, source_stage, destination_stage, 0, 0, nullptr, 0, nullptr, 1, &barrier); } void load_image(void *obj) { auto self = static_cast(obj); SDL_Surface *image{nullptr}; // Load file image from file. { SDL_Surface *raw_surface{nullptr}; raw_surface = IMG_Load(self->texture_path.c_str()); if(raw_surface == nullptr) { std::string error{"Failed to load image. SDL2_image Error: "}; error += IMG_GetError(); throw CommandError{error}; } image = SDL_ConvertSurfaceFormat(raw_surface, SDL_PIXELFORMAT_ARGB8888, 0); if(image == nullptr) { std::string error{"Failed to convert image. SDL2 Error: "}; error += SDL_GetError(); throw CommandError{error}; } self->texture->width = static_cast(image->w); self->texture->height = static_cast(image->h); self->texture->mip_levels = 1; SDL_FreeSurface(raw_surface); } // Load file image into a vulkan buffer. size_t image_size{static_cast( image->format->BytesPerPixel * image->w * image->h)}; VK::SourceBuffer source_image_buffer{ cg_core.vk_device_with_swapchain, image->pixels, image_size}; // Create vulkan image. { try { VkExtent3D vk_extent3d{}; vk_extent3d.width = self->texture->width; vk_extent3d.height = self->texture->height; vk_extent3d.depth = 1; VK::Image::create( cg_core.vk_device_with_swapchain, &self->texture->image, &self->texture->device_memory, VK_FORMAT_R8G8B8A8_UNORM, vk_extent3d, self->texture->mip_levels, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); } catch(VK::Image::Error error) { throw CommandError{error.what()}; } } // Copy image from vulkan buffer into vulkan image. { auto queue_family{ cg_core.vk_device_with_swapchain->get_queue_family_with_presentation()}; auto queue{queue_family->get_queue()}; VK::CommandPool command_pool{queue_family, 1}; VkCommandBuffer vk_command_buffer{command_pool.command_buffers[0]}; queue.submit_one_time_command(vk_command_buffer, [&](){ move_image_state( vk_command_buffer, self->texture->image, VK_FORMAT_R8G8B8A8_UNORM, 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); VkBufferImageCopy image_copy{}; image_copy.bufferOffset = 0; image_copy.bufferRowLength = 0; image_copy.bufferImageHeight = 0; image_copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; image_copy.imageSubresource.mipLevel = 0; image_copy.imageSubresource.baseArrayLayer = 0; image_copy.imageSubresource.layerCount = 1; image_copy.imageOffset = {0, 0, 0}; image_copy.imageExtent = {self->texture->width, self->texture->height, 1}; vkCmdCopyBufferToImage( vk_command_buffer, source_image_buffer.buffer, self->texture->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &image_copy); move_image_state( vk_command_buffer, self->texture->image, VK_FORMAT_R8G8B8A8_UNORM, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); }); } // Free resources. SDL_FreeSurface(image); } void unload_image(void *obj) { auto self = static_cast(obj); vkDestroyImage( cg_core.vk_device_with_swapchain->device, self->texture->image, nullptr); vkFreeMemory( cg_core.vk_device_with_swapchain->device, self->texture->device_memory, nullptr); } void load_sampler(void *obj) { auto self = static_cast(obj); VkSamplerCreateInfo sampler_info{}; sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; sampler_info.pNext = nullptr; sampler_info.flags = 0; sampler_info.magFilter = VK_FILTER_LINEAR; sampler_info.minFilter = VK_FILTER_LINEAR; sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; sampler_info.mipLodBias = 0.0f; sampler_info.anisotropyEnable = VK_TRUE; sampler_info.maxAnisotropy = 16; sampler_info.compareEnable = VK_FALSE; sampler_info.compareOp = VK_COMPARE_OP_NEVER; sampler_info.minLod = 0.0f; sampler_info.maxLod = 0.0f; sampler_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; sampler_info.unnormalizedCoordinates = VK_FALSE; if(vkCreateSampler( cg_core.vk_device_with_swapchain->device, &sampler_info, nullptr, &self->texture->sampler) != VK_SUCCESS) throw CommandError{"Failed to create texture sampler."}; } void unload_sampler(void *obj) { auto self = static_cast(obj); vkDestroySampler( cg_core.vk_device_with_swapchain->device, self->texture->sampler, nullptr); } void load_view(void *obj) { auto self = static_cast(obj); try { VK::Image::create_view( cg_core.vk_device_with_swapchain, &self->texture->view, self->texture->image, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT); } catch(VK::Image::Error error) { throw CommandError{error.what()}; } } void unload_view(void *obj) { auto self = static_cast(obj); vkDestroyImageView( cg_core.vk_device_with_swapchain->device, self->texture->view, nullptr); } const CommandChain loader{ {&load_image, &unload_image}, {&load_sampler, &unload_sampler}, {&load_view, &unload_view} }; } namespace VK { Texture::Texture(std::string texture_path) { TextureBuilder texture_builder(this, texture_path); loader.execute(&texture_builder); } Texture::Texture(const char* texture_path): Texture{std::string(texture_path)} { } Texture::~Texture() { TextureBuilder texture_builder(this, ""); loader.revert(&texture_builder); } }