diff --git a/lib/graphics_engine/CMakeLists.txt b/lib/graphics_engine/CMakeLists.txt index 67ec68e2e..101437ea7 100644 --- a/lib/graphics_engine/CMakeLists.txt +++ b/lib/graphics_engine/CMakeLists.txt @@ -71,6 +71,7 @@ set(GE_SOURCES src/ge_texture.cpp src/ge_vma.cpp src/ge_vulkan_2d_renderer.cpp + src/ge_vulkan_array_texture.cpp src/ge_vulkan_animated_mesh_scene_node.cpp src/ge_vulkan_camera_scene_node.cpp src/ge_vulkan_command_loader.cpp diff --git a/lib/graphics_engine/src/ge_vulkan_array_texture.cpp b/lib/graphics_engine/src/ge_vulkan_array_texture.cpp new file mode 100644 index 000000000..2a2aa6ab2 --- /dev/null +++ b/lib/graphics_engine/src/ge_vulkan_array_texture.cpp @@ -0,0 +1,245 @@ +#include "ge_vulkan_array_texture.hpp" + +#include "ge_main.hpp" +#include "ge_mipmap_generator.hpp" +#include "ge_compressor_astc_4x4.hpp" +#include "ge_compressor_s3tc_bc3.hpp" +#include "ge_texture.hpp" +#include "ge_vulkan_command_loader.hpp" +#include "ge_vulkan_driver.hpp" +#include "ge_vulkan_features.hpp" + +#include +#include + +namespace GE +{ +// ============================================================================ +std::vector getPathList(const std::vector& tlist) +{ + std::vector list; + for (GEVulkanTexture* tex : tlist) + list.push_back(tex->getFullPath()); + return list; +} // getPathList + +// ============================================================================ +GEVulkanArrayTexture::GEVulkanArrayTexture(const std::vector& list, + VkImageViewType type, + std::function image_mani) + : GEVulkanTexture() +{ + if (list.empty()) + throw std::runtime_error("empty texture list for array texture"); + + m_layer_count = list.size(); + m_image_view_type = type; + m_vk = getVKDriver(); + m_vulkan_device = m_vk->getDevice(); + m_image = VK_NULL_HANDLE; + m_vma_allocation = VK_NULL_HANDLE; + m_has_mipmaps = true; + m_locked_data = NULL; + m_max_size = m_vk->getDriverAttributes() + .getAttributeAsDimension2d("MAX_TEXTURE_SIZE"); + m_internal_format = VK_FORMAT_R8G8B8A8_UNORM; + m_texture_size = 0; + + m_size_lock.lock(); + m_image_view_lock.lock(); + GEVulkanCommandLoader::addMultiThreadingCommand( + [list, image_mani, this]() + { + reloadInternal(list, image_mani); + }); +} // GEVulkanArrayTexture + +// ---------------------------------------------------------------------------- +GEVulkanArrayTexture::GEVulkanArrayTexture( + const std::vector& textures, + VkImageViewType type, + std::function + image_mani) + : GEVulkanArrayTexture(getPathList(textures), type, + image_mani) +{ +} // GEVulkanArrayTexture + +// ---------------------------------------------------------------------------- +void GEVulkanArrayTexture::reloadInternal(const std::vector& list, + std::function image_mani) +{ + VkDeviceSize image_size = 0; + VkDeviceSize mipmap_data_size = 0; + VkDeviceSize image_total_size = 0; + std::vector images; + std::vector mipmaps; + + VkBuffer staging_buffer = NULL; + VmaAllocation staging_buffer_allocation = NULL; + VmaAllocationCreateInfo staging_buffer_create_info = {}; + staging_buffer_create_info.usage = VMA_MEMORY_USAGE_AUTO; + staging_buffer_create_info.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; + staging_buffer_create_info.preferredFlags = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + + assert(m_layer_count == list.size()); + uint8_t* mapped; + VkCommandBuffer command_buffer = VK_NULL_HANDLE; + unsigned offset = 0; + unsigned width = 0; + unsigned height = 0; + + for (unsigned i = 0; i < list.size(); i++) + { + const io::path& fullpath = list[i]; + video::IImageLoader* loader = NULL; + io::IReadFile* file = io::createReadFile(fullpath); + if (!file) + { + printf("Missing file %s in GEVulkanArrayTexture::reloadInternal", + fullpath.c_str()); + goto destroy; + } + m_vk->createImageFromFile(file, &loader); + core::dimension2du dim; + if (!loader || !loader->getImageSize(file, &dim)) + { + file->drop(); + printf("Missing image loader for %s in " + "GEVulkanArrayTexture::reloadInternal", fullpath.c_str()); + goto destroy; + } + file->drop(); + if (m_image_view_type == VK_IMAGE_VIEW_TYPE_CUBE || + m_image_view_type == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY) + { + width = std::max({ width, dim.Width, dim.Height }); + height = width; + } + else + { + width = std::max(width, dim.Width); + height = std::max(height, dim.Height); + } + } + + m_size = core::dimension2du(width, height); + image_size = m_size.Width * m_size.Height * 4; + m_orig_size = m_size; + m_size_lock.unlock(); + + for (unsigned i = 0; i < list.size(); i++) + { + const io::path& fullpath = list[i]; + video::IImage* texture_image = getResizedImageFullPath(fullpath, + m_max_size, NULL, &m_size); + if (texture_image == NULL) + goto destroy; + if (image_mani) + image_mani(texture_image, i); + uint8_t* texture_data = (uint8_t*)texture_image->lock(); + bgraConversion(texture_data); + GEMipmapGenerator* mipmap_generator = NULL; + const bool normal_map = (std::string(fullpath.c_str()).find( + "_Normal.") != std::string::npos); + bool texture_compression = getGEConfig()->m_texture_compression; + if (texture_compression && GEVulkanFeatures::supportsASTC4x4()) + { + if (i == 0) + { + image_size = get4x4CompressedTextureSize(m_size.Width, + m_size.Height); + m_internal_format = VK_FORMAT_ASTC_4x4_UNORM_BLOCK; + } + mipmap_generator = new GECompressorASTC4x4(texture_data, 4, m_size, + normal_map); + } + else if (texture_compression && GEVulkanFeatures::supportsS3TCBC3()) + { + if (i == 0) + { + image_size = get4x4CompressedTextureSize(m_size.Width, + m_size.Height); + m_internal_format = VK_FORMAT_BC3_UNORM_BLOCK; + } + mipmap_generator = new GECompressorS3TCBC3(texture_data, 4, m_size, + normal_map); + } + else + { + mipmap_generator = new GEMipmapGenerator(texture_data, 4, m_size, + normal_map); + } + mipmap_data_size = mipmap_generator->getMipmapSizes(); + if (mipmaps.empty()) + { + image_total_size = image_size + mipmap_data_size; + image_total_size *= m_layer_count; + if (!m_vk->createBuffer(image_total_size, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, staging_buffer_create_info, + staging_buffer, staging_buffer_allocation)) + { + goto destroy; + } + if (vmaMapMemory(m_vk->getVmaAllocator(), + staging_buffer_allocation, (void**)&mapped) != VK_SUCCESS) + { + goto destroy; + } + } + mipmaps.push_back(mipmap_generator); + for (GEImageLevel& level : mipmap_generator->getAllLevels()) + { + memcpy(mapped, level.m_data, level.m_size); + mapped += level.m_size; + } + images.push_back(texture_image); + } + vmaUnmapMemory(m_vk->getVmaAllocator(), staging_buffer_allocation); + vmaFlushAllocation(m_vk->getVmaAllocator(), + staging_buffer_allocation, 0, image_total_size); + + if (!createImage(VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT)) + goto destroy; + + command_buffer = GEVulkanCommandLoader::beginSingleTimeCommands(); + + transitionImageLayout(command_buffer, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + for (unsigned i = 0; i < list.size(); i++) + { + std::vector& levels = mipmaps[i]->getAllLevels(); + for (unsigned j = 0; j < levels.size(); j++) + { + GEImageLevel& level = levels[j]; + copyBufferToImage(command_buffer, staging_buffer, + level.m_dim.Width, level.m_dim.Height, 0, 0, offset, j, i); + offset += level.m_size; + } + } + transitionImageLayout(command_buffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + GEVulkanCommandLoader::endSingleTimeCommands(command_buffer); + + createImageView(VK_IMAGE_ASPECT_COLOR_BIT); + +destroy: + m_image_view_lock.unlock(); + for (video::IImage* image : images) + image->drop(); + for (GEMipmapGenerator* mipmap_generator : mipmaps) + delete mipmap_generator; + if (staging_buffer != VK_NULL_HANDLE) + { + vmaDestroyBuffer(m_vk->getVmaAllocator(), staging_buffer, + staging_buffer_allocation); + } + +} // reloadInternal + +} diff --git a/lib/graphics_engine/src/ge_vulkan_array_texture.hpp b/lib/graphics_engine/src/ge_vulkan_array_texture.hpp new file mode 100644 index 000000000..3a3a419f3 --- /dev/null +++ b/lib/graphics_engine/src/ge_vulkan_array_texture.hpp @@ -0,0 +1,57 @@ +#ifndef HEADER_GE_VULKAN_ARRAY_TEXTURE_HPP +#define HEADER_GE_VULKAN_ARRAY_TEXTURE_HPP + +#include "ge_vulkan_texture.hpp" + +namespace GE +{ +class GEVulkanDriver; +class GEVulkanArrayTexture : public GEVulkanTexture +{ +private: + void reloadInternal(const std::vector& list, + std::function + image_mani); +public: + // ------------------------------------------------------------------------ + GEVulkanArrayTexture(const std::vector& full_path_list, + VkImageViewType type, + std::function + image_mani = nullptr); + // ------------------------------------------------------------------------ + GEVulkanArrayTexture(const std::vector& textures, + VkImageViewType type, + std::function + image_mani = nullptr); + // ------------------------------------------------------------------------ + virtual ~GEVulkanArrayTexture() {} + // ------------------------------------------------------------------------ + virtual void* lock(video::E_TEXTURE_LOCK_MODE mode = + video::ETLM_READ_WRITE, u32 mipmap_level = 0) + { return NULL; } + // ------------------------------------------------------------------------ + virtual void unlock() {} + // ------------------------------------------------------------------------ + virtual video::E_DRIVER_TYPE getDriverType() const + { return video::EDT_VULKAN; } + // ------------------------------------------------------------------------ + virtual video::ECOLOR_FORMAT getColorFormat() const + { return video::ECF_A8R8G8B8; } + // ------------------------------------------------------------------------ + virtual u32 getPitch() const { return 0; } + // ------------------------------------------------------------------------ + virtual bool hasMipMaps() const { return false; } + // ------------------------------------------------------------------------ + virtual void regenerateMipMapLevels(void* mipmap_data = NULL) {} + // ------------------------------------------------------------------------ + virtual unsigned int getTextureSize() const { return m_texture_size; } + // ------------------------------------------------------------------------ + virtual void reload() {} + // ------------------------------------------------------------------------ + virtual void updateTexture(void* data, irr::video::ECOLOR_FORMAT format, + u32 w, u32 h, u32 x, u32 y) {} +}; // GEVulkanArrayTexture + +} + +#endif diff --git a/lib/graphics_engine/src/ge_vulkan_texture.cpp b/lib/graphics_engine/src/ge_vulkan_texture.cpp index 4ed875116..7e91a541b 100644 --- a/lib/graphics_engine/src/ge_vulkan_texture.cpp +++ b/lib/graphics_engine/src/ge_vulkan_texture.cpp @@ -28,9 +28,10 @@ GEVulkanTexture::GEVulkanTexture(const std::string& path, m_locked_data(NULL), m_vulkan_device(getVKDriver()->getDevice()), m_image(VK_NULL_HANDLE), m_vma_allocation(VK_NULL_HANDLE), - m_texture_size(0), m_disable_reload(false), - m_has_mipmaps(true), m_ondemand_load(false), - m_ondemand_loading(false), + m_texture_size(0), m_layer_count(1), + m_image_view_type(VK_IMAGE_VIEW_TYPE_2D), + m_disable_reload(false), m_has_mipmaps(true), + m_ondemand_load(false), m_ondemand_loading(false), m_internal_format(VK_FORMAT_R8G8B8A8_UNORM), m_vk(getVKDriver()) { @@ -75,9 +76,10 @@ GEVulkanTexture::GEVulkanTexture(video::IImage* img, const std::string& name) m_locked_data(NULL), m_vulkan_device(getVKDriver()->getDevice()), m_image(VK_NULL_HANDLE), m_vma_allocation(VK_NULL_HANDLE), - m_texture_size(0), m_disable_reload(true), - m_has_mipmaps(true), m_ondemand_load(false), - m_ondemand_loading(false), + m_texture_size(0), m_layer_count(1), + m_image_view_type(VK_IMAGE_VIEW_TYPE_2D), + m_disable_reload(true), m_has_mipmaps(true), + m_ondemand_load(false), m_ondemand_loading(false), m_internal_format(VK_FORMAT_R8G8B8A8_UNORM), m_vk(getVKDriver()) { @@ -102,9 +104,10 @@ GEVulkanTexture::GEVulkanTexture(const std::string& name, unsigned int size, : video::ITexture(name.c_str()), m_image_mani(nullptr), m_locked_data(NULL), m_vulkan_device(getVKDriver()->getDevice()), m_image(VK_NULL_HANDLE), m_vma_allocation(VK_NULL_HANDLE), - m_texture_size(0), m_disable_reload(true), m_has_mipmaps(true), - m_ondemand_load(false), m_ondemand_loading(false), - m_internal_format(single_channel ? + m_texture_size(0), m_layer_count(1), + m_image_view_type(VK_IMAGE_VIEW_TYPE_2D), m_disable_reload(true), + m_has_mipmaps(true), m_ondemand_load(false), + m_ondemand_loading(false), m_internal_format(single_channel ? VK_FORMAT_R8_UNORM : VK_FORMAT_R8G8B8A8_UNORM), m_vk(getVKDriver()) { @@ -229,14 +232,14 @@ bool GEVulkanTexture::createTextureImage(uint8_t* texture_data, { GEImageLevel& level = levels[i]; copyBufferToImage(command_buffer, staging_buffer, - level.m_dim.Width, level.m_dim.Height, 0, 0, offset, i); + level.m_dim.Width, level.m_dim.Height, 0, 0, offset, i, 0); offset += level.m_size; } } else { copyBufferToImage(command_buffer, staging_buffer, m_size.Width, - m_size.Height, 0, 0, 0, 0); + m_size.Height, 0, 0, 0, 0, 0); } transitionImageLayout(command_buffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, @@ -261,13 +264,16 @@ bool GEVulkanTexture::createImage(VkImageUsageFlags usage) image_info.extent.height = m_size.Height; image_info.extent.depth = 1; image_info.mipLevels = getMipmapLevels(); - image_info.arrayLayers = 1; + image_info.arrayLayers = m_layer_count; image_info.format = m_internal_format; image_info.tiling = VK_IMAGE_TILING_OPTIMAL; image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; image_info.usage = usage; image_info.samples = VK_SAMPLE_COUNT_1_BIT; image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + if (m_image_view_type == VK_IMAGE_VIEW_TYPE_CUBE || + m_image_view_type == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY) + image_info.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; VmaAllocationCreateInfo alloc_info = {}; alloc_info.usage = VMA_MEMORY_USAGE_AUTO; @@ -295,7 +301,7 @@ void GEVulkanTexture::transitionImageLayout(VkCommandBuffer command_buffer, barrier.subresourceRange.baseMipLevel = 0; barrier.subresourceRange.levelCount = getMipmapLevels(); barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.layerCount = m_layer_count; if (new_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { @@ -376,7 +382,8 @@ void GEVulkanTexture::transitionImageLayout(VkCommandBuffer command_buffer, // ---------------------------------------------------------------------------- void GEVulkanTexture::copyBufferToImage(VkCommandBuffer command_buffer, VkBuffer buffer, u32 w, u32 h, s32 x, - s32 y, u32 offset, u32 mipmap_level) + s32 y, u32 offset, u32 mipmap_level, + u32 layer_level) { VkBufferImageCopy region = {}; region.bufferOffset = offset; @@ -384,7 +391,7 @@ void GEVulkanTexture::copyBufferToImage(VkCommandBuffer command_buffer, region.bufferImageHeight = 0; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.imageSubresource.mipLevel = mipmap_level; - region.imageSubresource.baseArrayLayer = 0; + region.imageSubresource.baseArrayLayer = layer_level; region.imageSubresource.layerCount = 1; region.imageOffset = {x, y, 0}; region.imageExtent = {w, h, 1}; @@ -399,13 +406,13 @@ bool GEVulkanTexture::createImageView(VkImageAspectFlags aspect_flags) VkImageViewCreateInfo view_info = {}; view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; view_info.image = m_image; - view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + view_info.viewType = m_image_view_type; view_info.format = m_internal_format; view_info.subresourceRange.aspectMask = aspect_flags; view_info.subresourceRange.baseMipLevel = 0; view_info.subresourceRange.levelCount = getMipmapLevels(); view_info.subresourceRange.baseArrayLayer = 0; - view_info.subresourceRange.layerCount = 1; + view_info.subresourceRange.layerCount = m_layer_count; if (isSingleChannel()) { view_info.components.r = VK_COMPONENT_SWIZZLE_ONE; @@ -694,7 +701,7 @@ void GEVulkanTexture::updateTexture(void* data, video::ECOLOR_FORMAT format, transitionImageLayout(command_buffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - copyBufferToImage(command_buffer, staging_buffer, w, h, x, y, 0, 0); + copyBufferToImage(command_buffer, staging_buffer, w, h, x, y, 0, 0, 0); bool blit_mipmap = true; if (isSingleChannel() && !GEVulkanFeatures::supportsR8Blit()) diff --git a/lib/graphics_engine/src/ge_vulkan_texture.hpp b/lib/graphics_engine/src/ge_vulkan_texture.hpp index 11cb28248..790d923c5 100644 --- a/lib/graphics_engine/src/ge_vulkan_texture.hpp +++ b/lib/graphics_engine/src/ge_vulkan_texture.hpp @@ -39,6 +39,10 @@ protected: std::shared_ptr > m_placeholder_view; + unsigned m_layer_count; + + VkImageViewType m_image_view_type; + unsigned int m_texture_size; const bool m_disable_reload; @@ -72,7 +76,7 @@ protected: // ------------------------------------------------------------------------ void copyBufferToImage(VkCommandBuffer command_buffer, VkBuffer buffer, u32 w, u32 h, s32 x, s32 y, u32 offset, - u32 mipmap_level); + u32 mipmap_level, u32 layer_level); // ------------------------------------------------------------------------ void upload(uint8_t* data, bool generate_hq_mipmap = false); // ------------------------------------------------------------------------ @@ -114,8 +118,10 @@ protected: return true; } // ------------------------------------------------------------------------ - GEVulkanTexture() : video::ITexture(""), m_disable_reload(true), - m_ondemand_load(false), m_ondemand_loading(false) {} + GEVulkanTexture() : video::ITexture(""), m_layer_count(1), + m_image_view_type(VK_IMAGE_VIEW_TYPE_2D), + m_disable_reload(true), m_ondemand_load(false), + m_ondemand_loading(false) {} public: // ------------------------------------------------------------------------ GEVulkanTexture(const std::string& path,