diff --git a/source/vulkan_renderer/CMakeLists.txt b/source/vulkan_renderer/CMakeLists.txt index 1f8a213acb..92e2a0ae10 100644 --- a/source/vulkan_renderer/CMakeLists.txt +++ b/source/vulkan_renderer/CMakeLists.txt @@ -5,6 +5,7 @@ find_package(Vulkan REQUIRED) find_package(VulkanMemoryAllocator CONFIG REQUIRED) find_package(magic_enum CONFIG REQUIRED) find_package(imgui CONFIG REQUIRED) +find_package(Stb REQUIRED) add_subdirectory("lib") diff --git a/source/vulkan_renderer/lib/CMakeLists.txt b/source/vulkan_renderer/lib/CMakeLists.txt index 6390cace69..51793447fb 100644 --- a/source/vulkan_renderer/lib/CMakeLists.txt +++ b/source/vulkan_renderer/lib/CMakeLists.txt @@ -31,10 +31,12 @@ target_sources(tactile-vulkan-renderer "src/vulkan_shader_module.cpp" "src/vulkan_surface.cpp" "src/vulkan_swapchain.cpp" + "src/vulkan_texture.cpp" "src/vulkan_util.cpp" PUBLIC FILE_SET "HEADERS" BASE_DIRS "inc" FILES "inc/tactile/vulkan_renderer/api.hpp" + "inc/tactile/vulkan_renderer/imgui_shader_code.hpp" "inc/tactile/vulkan_renderer/vulkan_allocator.hpp" "inc/tactile/vulkan_renderer/vulkan_buffer.hpp" "inc/tactile/vulkan_renderer/vulkan_command_buffer.hpp" @@ -57,6 +59,7 @@ target_sources(tactile-vulkan-renderer "inc/tactile/vulkan_renderer/vulkan_shader_module.hpp" "inc/tactile/vulkan_renderer/vulkan_surface.hpp" "inc/tactile/vulkan_renderer/vulkan_swapchain.hpp" + "inc/tactile/vulkan_renderer/vulkan_texture.hpp" "inc/tactile/vulkan_renderer/vulkan_util.hpp" ) @@ -67,6 +70,10 @@ target_compile_definitions(tactile-vulkan-renderer "TACTILE_BUILDING_VULKAN_RENDERER" ) +target_include_directories(tactile-vulkan-renderer + PRIVATE + ${Stb_INCLUDE_DIR}) + target_link_libraries(tactile-vulkan-renderer PUBLIC tactile::base diff --git a/source/vulkan_renderer/lib/inc/tactile/vulkan_renderer/vulkan_buffer.hpp b/source/vulkan_renderer/lib/inc/tactile/vulkan_renderer/vulkan_buffer.hpp index f56a3a62ad..b1661f579b 100644 --- a/source/vulkan_renderer/lib/inc/tactile/vulkan_renderer/vulkan_buffer.hpp +++ b/source/vulkan_renderer/lib/inc/tactile/vulkan_renderer/vulkan_buffer.hpp @@ -36,4 +36,15 @@ TACTILE_VULKAN_API auto create_vulkan_buffer(VmaAllocator allocator, const VmaAllocationCreateInfo& allocation_info) -> std::expected; +[[nodiscard]] +TACTILE_VULKAN_API auto create_vulkan_staging_buffer(VmaAllocator allocator, + std::uint64_t size, + VkBufferUsageFlags usage_flags) + -> std::expected; + +[[nodiscard]] +TACTILE_VULKAN_API auto set_buffer_data(VulkanBuffer& buffer, + const void* data, + std::uint64_t data_size) -> VkResult; + } // namespace tactile diff --git a/source/vulkan_renderer/lib/inc/tactile/vulkan_renderer/vulkan_image.hpp b/source/vulkan_renderer/lib/inc/tactile/vulkan_renderer/vulkan_image.hpp index ffaec4d60c..ca2fe69990 100644 --- a/source/vulkan_renderer/lib/inc/tactile/vulkan_renderer/vulkan_image.hpp +++ b/source/vulkan_renderer/lib/inc/tactile/vulkan_renderer/vulkan_image.hpp @@ -2,8 +2,10 @@ #pragma once -#include // uint32_t -#include // expected +#include // max +#include // floor, log2 +#include // uint32_t, uint64_t +#include // expected #include #include @@ -38,10 +40,39 @@ class TACTILE_VULKAN_API VulkanImage final VmaAllocation allocation {VK_NULL_HANDLE}; VulkanImageParams params {}; + [[nodiscard]] + auto change_layout(VkDevice device, + VkQueue queue, + VkCommandPool command_pool, + VkImageLayout new_layout) -> VkResult; + + [[nodiscard]] + auto set_data(VkDevice device, + VkQueue queue, + VkCommandPool command_pool, + const void* data, + std::uint64_t data_size) -> VkResult; + + [[nodiscard]] + auto copy_buffer(VkDevice device, VkQueue queue, VkCommandPool command_pool, VkBuffer buffer) + -> VkResult; + + [[nodiscard]] + auto generate_mipmaps(VkDevice device, + VkQueue queue, + VkCommandPool command_pool) -> VkResult; + private: void _destroy() noexcept; }; +[[nodiscard]] +constexpr auto calculate_vulkan_mip_levels(const VkExtent2D extent) -> std::uint32_t +{ + const auto max_extent = std::max(extent.width, extent.height); + return 1u + static_cast(std::floor(std::log2(max_extent))); +} + [[nodiscard]] TACTILE_VULKAN_API auto create_vulkan_image(VmaAllocator allocator, const VulkanImageParams& params) diff --git a/source/vulkan_renderer/lib/inc/tactile/vulkan_renderer/vulkan_renderer.hpp b/source/vulkan_renderer/lib/inc/tactile/vulkan_renderer/vulkan_renderer.hpp index 7ac110c2e6..6df6fbf37a 100644 --- a/source/vulkan_renderer/lib/inc/tactile/vulkan_renderer/vulkan_renderer.hpp +++ b/source/vulkan_renderer/lib/inc/tactile/vulkan_renderer/vulkan_renderer.hpp @@ -2,11 +2,13 @@ #pragma once -#include // size_t -#include // vector +#include // size_t +#include // unordered_map +#include // vector #include +#include "tactile/base/id.hpp" #include "tactile/base/prelude.hpp" #include "tactile/base/render/renderer.hpp" #include "tactile/vulkan_renderer/api.hpp" @@ -22,6 +24,7 @@ #include "tactile/vulkan_renderer/vulkan_semaphore.hpp" #include "tactile/vulkan_renderer/vulkan_surface.hpp" #include "tactile/vulkan_renderer/vulkan_swapchain.hpp" +#include "tactile/vulkan_renderer/vulkan_texture.hpp" namespace tactile { @@ -97,8 +100,9 @@ class TACTILE_VULKAN_API VulkanRenderer final : public IRenderer PFN_vkCmdEndRenderingKHR m_vkCmdEndRendering {}; std::vector m_frames {}; std::size_t m_frame_index {0}; - VulkanImGuiContext m_imgui_context {}; + std::unordered_map m_textures {}; + TextureID m_next_texture_id {1}; void _record_commands() const; diff --git a/source/vulkan_renderer/lib/inc/tactile/vulkan_renderer/vulkan_texture.hpp b/source/vulkan_renderer/lib/inc/tactile/vulkan_renderer/vulkan_texture.hpp new file mode 100644 index 0000000000..35751690db --- /dev/null +++ b/source/vulkan_renderer/lib/inc/tactile/vulkan_renderer/vulkan_texture.hpp @@ -0,0 +1,46 @@ +// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) + +#pragma once + +#include // expected +#include // path + +#include +#include + +#include "tactile/base/prelude.hpp" +#include "tactile/base/render/texture.hpp" +#include "tactile/vulkan_renderer/api.hpp" +#include "tactile/vulkan_renderer/vulkan_image.hpp" +#include "vulkan_image_view.hpp" + +namespace tactile { + +class TACTILE_VULKAN_API VulkanTexture final : public ITexture +{ + public: + [[nodiscard]] + auto get_handle() const -> void* override; + + [[nodiscard]] + auto get_size() const -> TextureSize override; + + [[nodiscard]] + auto get_path() const -> const std::filesystem::path& override; + + VulkanImage image {}; + VulkanImageView view {}; + std::filesystem::path path {}; + void* imgui_handle {}; +}; + +[[nodiscard]] +TACTILE_VULKAN_API auto load_vulkan_texture(VkDevice device, + VkQueue queue, + VkCommandPool command_pool, + VmaAllocator allocator, + VkSampler sampler, + const std::filesystem::path& image_path) + -> std::expected; + +} // namespace tactile diff --git a/source/vulkan_renderer/lib/src/vulkan_buffer.cpp b/source/vulkan_renderer/lib/src/vulkan_buffer.cpp index 2f530a4e8f..a60e48c51e 100644 --- a/source/vulkan_renderer/lib/src/vulkan_buffer.cpp +++ b/source/vulkan_renderer/lib/src/vulkan_buffer.cpp @@ -2,7 +2,9 @@ #include "tactile/vulkan_renderer/vulkan_buffer.hpp" -#include // exchange +#include // min +#include // memcpy +#include // exchange #include "tactile/runtime/logging.hpp" #include "tactile/vulkan_renderer/vulkan_util.hpp" @@ -64,4 +66,57 @@ auto create_vulkan_buffer(VmaAllocator allocator, return buffer; } +auto create_vulkan_staging_buffer(VmaAllocator allocator, + const std::uint64_t size, + const VkBufferUsageFlags usage_flags) + -> std::expected +{ + const VkBufferCreateInfo buffer_info { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = size, + .usage = usage_flags | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; + + constexpr VmaAllocationCreateInfo allocation_info { + .flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + .usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + .requiredFlags = + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + .preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + .memoryTypeBits = 0, + .pool = nullptr, + .pUserData = nullptr, + .priority = 0, + }; + + return create_vulkan_buffer(allocator, buffer_info, allocation_info); +} + +auto set_buffer_data(VulkanBuffer& buffer, + const void* data, + const uint64 data_size) -> VkResult +{ + void* mapped_data = nullptr; + const auto result = vmaMapMemory(buffer.allocator, buffer.allocation, &mapped_data); + + if (result != VK_SUCCESS) { + return result; + } + + VmaAllocationInfo allocation_info {}; + vmaGetAllocationInfo(buffer.allocator, buffer.allocation, &allocation_info); + + const auto allocation_size = static_cast(allocation_info.size); + std::memcpy(mapped_data, data, std::min(data_size, allocation_size)); + + vmaUnmapMemory(buffer.allocator, buffer.allocation); + + return result; +} + } // namespace tactile diff --git a/source/vulkan_renderer/lib/src/vulkan_descriptor_pool.cpp b/source/vulkan_renderer/lib/src/vulkan_descriptor_pool.cpp index 981a3923d2..64329a48d3 100644 --- a/source/vulkan_renderer/lib/src/vulkan_descriptor_pool.cpp +++ b/source/vulkan_renderer/lib/src/vulkan_descriptor_pool.cpp @@ -46,7 +46,7 @@ auto create_vulkan_imgui_descriptor_pool(VkDevice device) constexpr VkDescriptorPoolSize pool_sizes[] = { VkDescriptorPoolSize { .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - .descriptorCount = 1, + .descriptorCount = 8, }, }; @@ -54,7 +54,7 @@ auto create_vulkan_imgui_descriptor_pool(VkDevice device) .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .pNext = nullptr, .flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, - .maxSets = 5, + .maxSets = 64, .poolSizeCount = 1, .pPoolSizes = pool_sizes, }; diff --git a/source/vulkan_renderer/lib/src/vulkan_image.cpp b/source/vulkan_renderer/lib/src/vulkan_image.cpp index 48360b174a..50567aeb61 100644 --- a/source/vulkan_renderer/lib/src/vulkan_image.cpp +++ b/source/vulkan_renderer/lib/src/vulkan_image.cpp @@ -2,18 +2,89 @@ #include "tactile/vulkan_renderer/vulkan_image.hpp" -#include // exchange +#include // assert +#include // unordered_map +#include // exchange #include "tactile/runtime/logging.hpp" +#include "tactile/vulkan_renderer/vulkan_buffer.hpp" +#include "tactile/vulkan_renderer/vulkan_command_pool.hpp" #include "tactile/vulkan_renderer/vulkan_util.hpp" namespace tactile { +namespace { + +// Used to determine access flags for layout transitions. +const std::unordered_map kTransitionAccessMap { + {VK_IMAGE_LAYOUT_UNDEFINED, 0}, + {VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_READ_BIT}, + {VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT}, + {VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_SHADER_READ_BIT}, + {VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT}, +}; + +// Used to determine pipeline stage flags for layout transitions. +const std::unordered_map kTransitionStageMap { + {VK_IMAGE_LAYOUT_UNDEFINED, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT}, + {VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT}, + {VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT}, + {VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT}, + {VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}, +}; + +void _do_change_layout(VkCommandBuffer command_buffer, + VkImage image, + const VkImageLayout old_layout, + const VkImageLayout new_layout, + const std::uint32_t base_mip_level, + const std::uint32_t level_count) +{ + const auto src_access = kTransitionAccessMap.at(old_layout); + const auto dst_access = kTransitionAccessMap.at(new_layout); + + const auto src_stage = kTransitionStageMap.at(old_layout); + const auto dst_stage = kTransitionStageMap.at(new_layout); + + const VkImageMemoryBarrier image_memory_barrier { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = src_access, + .dstAccessMask = dst_access, + .oldLayout = old_layout, + .newLayout = new_layout, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange = + VkImageSubresourceRange { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = base_mip_level, + .levelCount = level_count, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + + vkCmdPipelineBarrier(command_buffer, + src_stage, + dst_stage, + 0, + 0, + nullptr, + 0, + nullptr, + 1, + &image_memory_barrier); +} + +} // namespace VulkanImage::VulkanImage(VulkanImage&& other) noexcept - : allocator {std::exchange(other.allocator, VK_NULL_HANDLE)}, - handle {std::exchange(other.handle, VK_NULL_HANDLE)}, - allocation {std::exchange(other.allocation, VK_NULL_HANDLE)}, - params {std::exchange(other.params, VulkanImageParams {})} + : allocator {std::exchange(other.allocator, VK_NULL_HANDLE)}, + handle {std::exchange(other.handle, VK_NULL_HANDLE)}, + allocation {std::exchange(other.allocation, VK_NULL_HANDLE)}, + params {std::exchange(other.params, VulkanImageParams {})} {} VulkanImage::~VulkanImage() noexcept @@ -43,6 +114,175 @@ void VulkanImage::_destroy() noexcept } } +auto VulkanImage::change_layout(VkDevice device, + VkQueue queue, + VkCommandPool command_pool, + const VkImageLayout new_layout) -> VkResult +{ + if (params.layout == new_layout) { + return VK_SUCCESS; + } + + const auto work = [this, new_layout](VkCommandBuffer command_buffer) { + _do_change_layout(command_buffer, handle, params.layout, new_layout, 0, params.mip_levels); + params.layout = new_layout; + }; + + return record_and_submit_commands(device, queue, command_pool, work); +} + +auto VulkanImage::set_data(VkDevice device, + VkQueue queue, + VkCommandPool command_pool, + const void* data, + const std::uint64_t data_size) -> VkResult +{ + auto staging_buffer = create_vulkan_staging_buffer(allocator, data_size, 0); + if (!staging_buffer) { + return staging_buffer.error(); + } + + auto result = set_buffer_data(*staging_buffer, data, data_size); + if (result != VK_SUCCESS) { + return result; + } + + result = change_layout(device, queue, command_pool, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + if (result != VK_SUCCESS) { + return result; + } + + result = copy_buffer(device, queue, command_pool, staging_buffer->handle); + if (result != VK_SUCCESS) { + return result; + } + + result = + change_layout(device, queue, command_pool, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + if (result != VK_SUCCESS) { + return result; + } + + return result; +} + +auto VulkanImage::copy_buffer(VkDevice device, + VkQueue queue, + VkCommandPool command_pool, + VkBuffer buffer) -> VkResult +{ + auto work = [&](VkCommandBuffer command_buffer) { + const VkBufferImageCopy region = { + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = + VkImageSubresourceLayers { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .imageOffset = VkOffset3D {0, 0, 0}, + .imageExtent = VkExtent3D {params.extent.width, params.extent.height, 1}, + }; + + vkCmdCopyBufferToImage(command_buffer, buffer, handle, params.layout, 1, ®ion); + }; + + return record_and_submit_commands(device, queue, command_pool, work); +} + +auto VulkanImage::generate_mipmaps(VkDevice device, + VkQueue queue, + VkCommandPool command_pool) -> VkResult +{ + assert(params.mip_levels > 0); + + const auto work = [this](VkCommandBuffer command_buffer) { + auto mip_width = params.extent.width; + auto mip_height = params.extent.height; + + for (std::uint32_t mip_level = 1u; mip_level < params.mip_levels; ++mip_level) { + const std::uint32_t base_mip_level = mip_level - 1u; + + _do_change_layout(command_buffer, + handle, + params.layout, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + base_mip_level, + 1); + + const VkImageBlit image_blit { + .srcSubresource = + VkImageSubresourceLayers { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = base_mip_level, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .srcOffsets = + { + VkOffset3D {0, 0, 0}, + VkOffset3D {static_cast(mip_width), + static_cast(mip_height), + 1}, + }, + .dstSubresource = + VkImageSubresourceLayers { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = mip_level, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .dstOffsets = + { + VkOffset3D {0, 0, 0}, + VkOffset3D {static_cast(mip_width > 1 ? (mip_width / 2) : 1), + static_cast(mip_height > 1 ? (mip_height / 2) : 1), + 1}, + }, + }; + + vkCmdBlitImage(command_buffer, + handle, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + handle, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + &image_blit, + VK_FILTER_NEAREST); + + _do_change_layout(command_buffer, + handle, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + base_mip_level, + 1); + + if (mip_width > 1) { + mip_width /= 2; + } + + if (mip_height > 1) { + mip_height /= 2; + } + } + + // Transition the last mipmap image to the optimal shader read layout + _do_change_layout(command_buffer, + handle, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + params.mip_levels - 1, + 1); + + params.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + }; + + return record_and_submit_commands(device, queue, command_pool, work); +} + auto create_vulkan_image(VmaAllocator allocator, const VulkanImageParams& params) -> std::expected { diff --git a/source/vulkan_renderer/lib/src/vulkan_renderer.cpp b/source/vulkan_renderer/lib/src/vulkan_renderer.cpp index b5a59fc24b..432d334740 100644 --- a/source/vulkan_renderer/lib/src/vulkan_renderer.cpp +++ b/source/vulkan_renderer/lib/src/vulkan_renderer.cpp @@ -2,18 +2,22 @@ #include "tactile/vulkan_renderer/vulkan_renderer.hpp" -#include // uint32_t, uint64_t -#include // numeric_limits -#include // ignore +#include // uint32_t, uint64_t +#include // numeric_limits +#include // make_error_code, errc +#include // ignore +#include // move #include #include #include #include +#include "tactile/base/io/file_io.hpp" #include "tactile/base/render/window.hpp" #include "tactile/runtime/logging.hpp" #include "tactile/runtime/runtime.hpp" +#include "tactile/vulkan_renderer/vulkan_buffer.hpp" #include "tactile/vulkan_renderer/vulkan_physical_device.hpp" #include "tactile/vulkan_renderer/vulkan_util.hpp" @@ -174,17 +178,38 @@ void VulkanRenderer::end_frame() auto VulkanRenderer::load_texture(const std::filesystem::path& image_path) -> std::expected { - // TODO - return {}; + auto texture = load_vulkan_texture(m_device.handle, + m_graphics_queue, + m_graphics_command_pool.handle, + m_allocator.handle, + m_sampler.handle, + image_path); + + if (!texture.has_value()) { + return std::unexpected {std::make_error_code(std::errc::io_error)}; + } + + const auto id = m_next_texture_id; + ++m_next_texture_id.value; + + m_textures.insert_or_assign(id, std::move(*texture)); + + return id; } -void VulkanRenderer::unload_texture(TextureID id) +void VulkanRenderer::unload_texture(const TextureID id) { - // TODO + m_textures.erase(id); } -auto VulkanRenderer::find_texture(TextureID id) const -> const ITexture* +auto VulkanRenderer::find_texture(const TextureID id) const -> const ITexture* { + const auto iter = m_textures.find(id); + + if (iter != m_textures.end()) { + return &iter->second; + } + return nullptr; } @@ -372,7 +397,8 @@ void VulkanRenderer::_begin_dynamic_rendering(const VulkanFrame& frame) const .sType = VK_STRUCTURE_TYPE_RENDERING_INFO_KHR, .pNext = nullptr, .flags = 0, - .renderArea = VkRect2D {0, 0, image_extent.width, image_extent.height}, + .renderArea = + VkRect2D {VkOffset2D {0, 0}, VkExtent2D {image_extent.width, image_extent.height}}, .layerCount = 1, .viewMask = 0, .colorAttachmentCount = 1, diff --git a/source/vulkan_renderer/lib/src/vulkan_texture.cpp b/source/vulkan_renderer/lib/src/vulkan_texture.cpp new file mode 100644 index 0000000000..fd0e5282d7 --- /dev/null +++ b/source/vulkan_renderer/lib/src/vulkan_texture.cpp @@ -0,0 +1,138 @@ +// Copyright (C) 2024 Albin Johansson (GNU General Public License v3.0) + +#include "tactile/vulkan_renderer/vulkan_texture.hpp" + +#define STB_IMAGE_IMPLEMENTATION +#include +#include + +#include "tactile/base/io/file_io.hpp" +#include "tactile/base/numeric/saturate_cast.hpp" +#include "tactile/base/util/scope_exit.hpp" +#include "tactile/vulkan_renderer/vulkan_buffer.hpp" + +namespace tactile { + +auto VulkanTexture::get_handle() const -> void* +{ + return imgui_handle; +} + +auto VulkanTexture::get_size() const -> TextureSize +{ + return TextureSize {saturate_cast(image.params.extent.width), + saturate_cast(image.params.extent.height)}; +} + +auto VulkanTexture::get_path() const -> const std::filesystem::path& +{ + return path; +} + +auto load_vulkan_texture(VkDevice device, + VkQueue queue, + VkCommandPool command_pool, + VmaAllocator allocator, + VkSampler sampler, + const std::filesystem::path& image_path) + -> std::expected +{ + int width {}; + int height {}; + int channels {}; + auto* pixels = + stbi_load(image_path.string().c_str(), &width, &height, &channels, STBI_default); + + if (!pixels) { + return std::unexpected {VK_ERROR_UNKNOWN}; + } + + const auto pixel_bytes = width * height * channels; + const ScopeExit pixels_deleter {[pixels] { stbi_image_free(pixels); }}; + + auto staging_buffer = create_vulkan_staging_buffer(allocator, pixel_bytes, 0); + if (!staging_buffer.has_value()) { + return std::unexpected {staging_buffer.error()}; + } + + VkFormat format {VK_FORMAT_UNDEFINED}; + switch (channels) { + case STBI_rgb: format = VK_FORMAT_R8G8B8_UNORM; break; + case STBI_rgb_alpha: format = VK_FORMAT_R8G8B8A8_UNORM; break; + case STBI_grey: [[fallthrough]]; + case STBI_grey_alpha: [[fallthrough]]; + default: return std::unexpected {VK_ERROR_FORMAT_NOT_SUPPORTED}; + } + + const VkExtent2D image_extent {static_cast(width), + static_cast(height)}; + + auto image = create_vulkan_image( + allocator, + VulkanImageParams { + .type = VK_IMAGE_TYPE_2D, + .format = format, + .layout = VK_IMAGE_LAYOUT_UNDEFINED, + .extent = image_extent, + .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT, + .mip_levels = calculate_vulkan_mip_levels(image_extent), + }); + if (!image.has_value()) { + return std::unexpected {image.error()}; + } + + auto result = set_buffer_data(*staging_buffer, pixels, pixel_bytes); + if (result != VK_SUCCESS) { + return std::unexpected {result}; + } + + result = + image->change_layout(device, queue, command_pool, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + if (result != VK_SUCCESS) { + return std::unexpected {result}; + } + + result = image->copy_buffer(device, queue, command_pool, staging_buffer->handle); + if (result != VK_SUCCESS) { + return std::unexpected {result}; + } + + result = image->generate_mipmaps(device, queue, command_pool); + if (result != VK_SUCCESS) { + return std::unexpected {result}; + } + + result = image->change_layout(device, + queue, + command_pool, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + if (result != VK_SUCCESS) { + return std::unexpected {result}; + } + + auto image_view = create_vulkan_image_view(device, + image->handle, + image->params.format, + VK_IMAGE_VIEW_TYPE_2D, + VK_IMAGE_ASPECT_COLOR_BIT, + 1); + if (!image_view.has_value()) { + return std::unexpected {image_view.error()}; + } + + VulkanTexture texture {}; + texture.path = image_path; + texture.image = std::move(*image); + texture.view = std::move(*image_view); + + texture.imgui_handle = + ImGui_ImplVulkan_AddTexture(sampler, texture.view.handle, texture.image.params.layout); + if (texture.imgui_handle == VK_NULL_HANDLE) { + return std::unexpected {VK_ERROR_INITIALIZATION_FAILED}; + } + + return texture; +} + +} // namespace tactile