diff --git a/android/Android.mk b/android/Android.mk index b7a7e3193..efd5fd9f8 100644 --- a/android/Android.mk +++ b/android/Android.mk @@ -168,6 +168,7 @@ LOCAL_CPP_FEATURES += rtti exceptions LOCAL_SRC_FILES := $(wildcard ../lib/graphics_engine/src/*.c) \ $(wildcard ../lib/graphics_engine/src/*.cpp) LOCAL_CFLAGS := -I../lib/graphics_engine/include \ + -I../lib/graphics_utils \ -I../lib/sdl2/include/ \ -I../lib/irrlicht/include/ ifeq ($(TARGET_ARCH_ABI), armeabi-v7a) diff --git a/lib/graphics_engine/CMakeLists.txt b/lib/graphics_engine/CMakeLists.txt index d2e80beef..bc89682cb 100644 --- a/lib/graphics_engine/CMakeLists.txt +++ b/lib/graphics_engine/CMakeLists.txt @@ -1,4 +1,5 @@ include_directories("${PROJECT_SOURCE_DIR}/lib/graphics_engine/include") +include_directories("${PROJECT_SOURCE_DIR}/lib/graphics_utils") include_directories("${PROJECT_SOURCE_DIR}/lib/irrlicht/include") find_path(SDL2_INCLUDEDIR NAMES SDL.h PATH_SUFFIXES SDL2 include/SDL2 include PATHS) if (NOT SDL2_INCLUDEDIR) diff --git a/lib/graphics_engine/src/ge_vulkan_features.cpp b/lib/graphics_engine/src/ge_vulkan_features.cpp index e8c50397e..7835334e8 100644 --- a/lib/graphics_engine/src/ge_vulkan_features.cpp +++ b/lib/graphics_engine/src/ge_vulkan_features.cpp @@ -15,6 +15,8 @@ namespace GEVulkanFeatures { // ============================================================================ bool g_supports_bind_textures_at_once = false; +bool g_supports_rgba8_blit = false; +bool g_supports_r8_blit = false; // https://chunkstories.xyz/blog/a-note-on-descriptor-indexing bool g_supports_descriptor_indexing = false; bool g_supports_non_uniform_indexing = false; @@ -47,6 +49,15 @@ void GEVulkanFeatures::init(GEVulkanDriver* vk) g_supports_bind_textures_at_once = false; } + VkFormatProperties format_properties = {}; + vkGetPhysicalDeviceFormatProperties(vk->getPhysicalDevice(), + VK_FORMAT_R8G8B8A8_UNORM, &format_properties); + g_supports_rgba8_blit = format_properties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT; + format_properties = {}; + vkGetPhysicalDeviceFormatProperties(vk->getPhysicalDevice(), + VK_FORMAT_R8_UNORM, &format_properties); + g_supports_r8_blit = format_properties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT; + uint32_t extension_count; vkEnumerateDeviceExtensionProperties(vk->getPhysicalDevice(), NULL, &extension_count, NULL); @@ -111,6 +122,12 @@ void GEVulkanFeatures::printStats() os::Printer::log( "Vulkan can bind textures at once in shader", g_supports_bind_textures_at_once ? "true" : "false"); + os::Printer::log( + "Vulkan supports linear blitting for rgba8", + g_supports_rgba8_blit ? "true" : "false"); + os::Printer::log( + "Vulkan supports linear blitting for r8", + g_supports_r8_blit ? "true" : "false"); os::Printer::log( "Vulkan supports VK_EXT_descriptor_indexing", g_supports_descriptor_indexing ? "true" : "false"); @@ -128,6 +145,18 @@ bool GEVulkanFeatures::supportsBindTexturesAtOnce() return g_supports_bind_textures_at_once; } // supportsBindTexturesAtOnce +// ---------------------------------------------------------------------------- +bool GEVulkanFeatures::supportsRGBA8Blit() +{ + return g_supports_rgba8_blit; +} // supportsRGBA8Blit + +// ---------------------------------------------------------------------------- +bool GEVulkanFeatures::supportsR8Blit() +{ + return g_supports_r8_blit; +} // supportsR8Blit + // ---------------------------------------------------------------------------- bool GEVulkanFeatures::supportsDescriptorIndexing() { diff --git a/lib/graphics_engine/src/ge_vulkan_features.hpp b/lib/graphics_engine/src/ge_vulkan_features.hpp index 05df74ee0..ea33e296f 100644 --- a/lib/graphics_engine/src/ge_vulkan_features.hpp +++ b/lib/graphics_engine/src/ge_vulkan_features.hpp @@ -15,6 +15,10 @@ void printStats(); // ---------------------------------------------------------------------------- bool supportsBindTexturesAtOnce(); // ---------------------------------------------------------------------------- +bool supportsRGBA8Blit(); +// ---------------------------------------------------------------------------- +bool supportsR8Blit(); +// ---------------------------------------------------------------------------- bool supportsDescriptorIndexing(); // ---------------------------------------------------------------------------- bool supportsNonUniformIndexing(); diff --git a/lib/graphics_engine/src/ge_vulkan_texture.cpp b/lib/graphics_engine/src/ge_vulkan_texture.cpp index ebcf2188c..8e960ec62 100644 --- a/lib/graphics_engine/src/ge_vulkan_texture.cpp +++ b/lib/graphics_engine/src/ge_vulkan_texture.cpp @@ -1,14 +1,20 @@ #include "ge_vulkan_texture.hpp" #include "ge_vulkan_command_loader.hpp" +#include "ge_vulkan_features.hpp" #include "ge_vulkan_driver.hpp" #include "ge_gl_utils.hpp" #include "ge_main.hpp" #include "ge_texture.hpp" +extern "C" +{ + #include + #include +} + #include #include -#include namespace GE { @@ -19,7 +25,8 @@ GEVulkanTexture::GEVulkanTexture(const std::string& path, m_vulkan_device(getVKDriver()->getDevice()), m_image(VK_NULL_HANDLE), m_image_memory(VK_NULL_HANDLE), m_image_view(VK_NULL_HANDLE), m_texture_size(0), - m_disable_reload(false), m_single_channel(false) + m_disable_reload(false), m_single_channel(false), + m_has_mipmaps(true) { m_max_size = getDriver()->getDriverAttributes() .getAttributeAsDimension2d("MAX_TEXTURE_SIZE"); @@ -43,7 +50,8 @@ GEVulkanTexture::GEVulkanTexture(video::IImage* img, const std::string& name) m_vulkan_device(getVKDriver()->getDevice()), m_image(VK_NULL_HANDLE), m_image_memory(VK_NULL_HANDLE), m_image_view(VK_NULL_HANDLE), m_texture_size(0), - m_disable_reload(true), m_single_channel(false) + m_disable_reload(true), m_single_channel(false), + m_has_mipmaps(true) { if (!img) { @@ -53,7 +61,7 @@ GEVulkanTexture::GEVulkanTexture(video::IImage* img, const std::string& name) m_size = m_orig_size = img->getDimension(); uint8_t* data = (uint8_t*)img->lock(); bgraConversion(data); - upload(data); + upload(data, true/*generate_hq_mipmap*/); img->unlock(); img->drop(); } // GEVulkanTexture @@ -65,8 +73,14 @@ GEVulkanTexture::GEVulkanTexture(const std::string& name, unsigned int size, m_locked_data(NULL), m_vulkan_device(getVKDriver()->getDevice()), m_image(VK_NULL_HANDLE), m_image_memory(VK_NULL_HANDLE), m_image_view(VK_NULL_HANDLE), m_texture_size(0), - m_disable_reload(true), m_single_channel(single_channel) + m_disable_reload(true), m_single_channel(single_channel), + m_has_mipmaps(true) { + if (m_single_channel && !GEVulkanFeatures::supportsR8Blit()) + m_has_mipmaps = false; + else if (!m_single_channel && !GEVulkanFeatures::supportsRGBA8Blit()) + m_has_mipmaps = false; + m_orig_size.Width = size; m_orig_size.Height = size; m_size = m_orig_size; @@ -90,14 +104,32 @@ GEVulkanTexture::~GEVulkanTexture() } // ~GEVulkanTexture // ---------------------------------------------------------------------------- -bool GEVulkanTexture::createTextureImage(uint8_t* texture_data) +bool GEVulkanTexture::createTextureImage(uint8_t* texture_data, + bool generate_hq_mipmap) { + video::IImage* mipmap = NULL; + std::vector > mipmap_sizes = + getMipmapSizes(); + VkDeviceSize mipmap_data_size = 0; + unsigned channels = (m_single_channel ? 1 : 4); VkDeviceSize image_size = m_size.Width * m_size.Height * channels; + if (generate_hq_mipmap) + { + mipmap = getDriver()->createImage(video::ECF_A8R8G8B8, + mipmap_sizes[0].first); + if (mipmap == NULL) + throw std::runtime_error("Creating mipmap memory failed"); + generateHQMipmap(texture_data, mipmap_sizes, (uint8_t*)mipmap->lock()); + mipmap_data_size = mipmap_sizes.back().second + + mipmap_sizes.back().first.getArea() * channels - image_size; + } + + VkDeviceSize image_total_size = image_size + mipmap_data_size; VkBuffer staging_buffer; VkDeviceMemory staging_buffer_memory; - bool success = getVKDriver()->createBuffer(image_size, + bool success = getVKDriver()->createBuffer(image_total_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, staging_buffer, @@ -107,12 +139,17 @@ bool GEVulkanTexture::createTextureImage(uint8_t* texture_data) return false; VkResult ret = VK_SUCCESS; - void* data; + uint8_t* data; VkCommandBuffer command_buffer = VK_NULL_HANDLE; if ((ret = vkMapMemory(m_vulkan_device, staging_buffer_memory, 0, - image_size, 0, &data)) != VK_SUCCESS) + image_total_size, 0, (void**)&data)) != VK_SUCCESS) goto destroy; - memcpy(data, texture_data, (size_t)(image_size)); + memcpy(data, texture_data, image_size); + if (mipmap) + { + memcpy(data + image_size, mipmap->lock(), mipmap_data_size); + mipmap->drop(); + } vkUnmapMemory(m_vulkan_device, staging_buffer_memory); success = createImage(VK_IMAGE_USAGE_TRANSFER_SRC_BIT | @@ -128,8 +165,20 @@ bool GEVulkanTexture::createTextureImage(uint8_t* texture_data) transitionImageLayout(command_buffer, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - copyBufferToImage(command_buffer, staging_buffer, m_size.Width, - m_size.Height, 0, 0); + if (generate_hq_mipmap) + { + unsigned level = 0; + for (auto& mip : mipmap_sizes) + { + copyBufferToImage(command_buffer, staging_buffer, mip.first.Width, + mip.first.Height, 0, 0, mip.second, level++); + } + } + else + { + copyBufferToImage(command_buffer, staging_buffer, m_size.Width, + m_size.Height, 0, 0, 0, 0); + } transitionImageLayout(command_buffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); @@ -152,7 +201,7 @@ bool GEVulkanTexture::createImage(VkImageUsageFlags usage) image_info.extent.width = m_size.Width; image_info.extent.height = m_size.Height; image_info.extent.depth = 1; - image_info.mipLevels = 1; + image_info.mipLevels = getMipmapLevels(); image_info.arrayLayers = 1; image_info.format = (m_single_channel ? VK_FORMAT_R8_UNORM : VK_FORMAT_R8G8B8A8_UNORM); @@ -220,7 +269,7 @@ void GEVulkanTexture::transitionImageLayout(VkCommandBuffer command_buffer, barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.image = m_image; barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.levelCount = getMipmapLevels(); barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = 1; @@ -303,14 +352,14 @@ void GEVulkanTexture::transitionImageLayout(VkCommandBuffer command_buffer, // ---------------------------------------------------------------------------- void GEVulkanTexture::copyBufferToImage(VkCommandBuffer command_buffer, VkBuffer buffer, u32 w, u32 h, s32 x, - s32 y) + s32 y, u32 offset, u32 mipmap_level) { VkBufferImageCopy region = {}; - region.bufferOffset = 0; + region.bufferOffset = offset; region.bufferRowLength = 0; region.bufferImageHeight = 0; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.imageSubresource.mipLevel = 0; + region.imageSubresource.mipLevel = mipmap_level; region.imageSubresource.baseArrayLayer = 0; region.imageSubresource.layerCount = 1; region.imageOffset = {x, y, 0}; @@ -331,7 +380,7 @@ bool GEVulkanTexture::createImageView(VkImageAspectFlags aspect_flags) (m_single_channel ? VK_FORMAT_R8_UNORM : VK_FORMAT_R8G8B8A8_UNORM); view_info.subresourceRange.aspectMask = aspect_flags; view_info.subresourceRange.baseMipLevel = 0; - view_info.subresourceRange.levelCount = 1; + view_info.subresourceRange.levelCount = getMipmapLevels(); view_info.subresourceRange.baseArrayLayer = 0; view_info.subresourceRange.layerCount = 1; if (m_single_channel) @@ -386,7 +435,7 @@ void GEVulkanTexture::reloadInternal() uint8_t* data = (uint8_t*)texture_image->lock(); bgraConversion(data); - upload(data); + upload(data, true/*generate_hq_mipmap*/); m_image_view_lock.unlock(); texture_image->unlock(); @@ -394,9 +443,9 @@ void GEVulkanTexture::reloadInternal() } // reloadInternal // ---------------------------------------------------------------------------- -void GEVulkanTexture::upload(uint8_t* data) +void GEVulkanTexture::upload(uint8_t* data, bool generate_hq_mipmap) { - if (!createTextureImage(data)) + if (!createTextureImage(data, generate_hq_mipmap)) return; if (!createImageView(VK_IMAGE_ASPECT_COLOR_BIT)) return; @@ -566,7 +615,80 @@ 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); + copyBufferToImage(command_buffer, staging_buffer, w, h, x, y, 0, 0); + + bool blit_mipmap = true; + if (m_single_channel && !GEVulkanFeatures::supportsR8Blit()) + blit_mipmap = false; + else if (!m_single_channel && !GEVulkanFeatures::supportsRGBA8Blit()) + blit_mipmap = false; + if (blit_mipmap) + { + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.image = m_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; + + int mip_width = m_size.Width; + int mip_height = m_size.Height; + unsigned mip_levels = getMipmapLevels(); + + for (unsigned i = 1; i < mip_levels; i++) + { + barrier.subresourceRange.baseMipLevel = i - 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(command_buffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, NULL, 0, NULL, 1, &barrier); + + VkImageBlit blit{}; + blit.srcOffsets[0] = {0, 0, 0}; + blit.srcOffsets[1] = {mip_width, mip_height, 1}; + blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.srcSubresource.mipLevel = i - 1; + blit.srcSubresource.baseArrayLayer = 0; + blit.srcSubresource.layerCount = 1; + blit.dstOffsets[0] = {0, 0, 0}; + blit.dstOffsets[1] = + { + mip_width > 1 ? mip_width / 2 : 1, + mip_height > 1 ? mip_height / 2 : 1, + 1 + }; + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.mipLevel = i; + blit.dstSubresource.baseArrayLayer = 0; + blit.dstSubresource.layerCount = 1; + + vkCmdBlitImage(command_buffer, + m_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + m_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_TRANSFER_DST_OPTIMAL; + barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + + vkCmdPipelineBarrier(command_buffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, + &barrier); + + if (mip_width > 1) mip_width /= 2; + if (mip_height > 1) mip_height /= 2; + } + } + transitionImageLayout(command_buffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); GEVulkanCommandLoader::endSingleTimeCommands(command_buffer); @@ -605,4 +727,41 @@ void GEVulkanTexture::reload() } } // reload +// ---------------------------------------------------------------------------- +void GEVulkanTexture::generateHQMipmap(void* in, + std::vector >& mms, + uint8_t* out) +{ + imMipmapCascade cascade; + imReduceOptions options; + imReduceSetOptions(&options, + std::string(NamedPath.getPtr()).find("_Normal.") != std::string::npos ? + IM_REDUCE_FILTER_NORMALMAP: IM_REDUCE_FILTER_LINEAR/*filter*/, + 2/*hopcount*/, 2.0f/*alpha*/, 1.0f/*amplifynormal*/, + 0.0f/*normalsustainfactor*/); + + unsigned channels = (m_single_channel ? 1 : 4); +#ifdef DEBUG + int ret = imBuildMipmapCascade(&cascade, in, mms[0].first.Width, + mms[0].first.Height, 1/*layercount*/, channels, + mms[0].first.Width * channels, &options, 0); + if (ret != 1) + throw std::runtime_error("imBuildMipmapCascade failed"); +#else + imBuildMipmapCascade(&cascade, in, mms[0].first.Width, + mms[0].first.Height, 1/*layercount*/, channels, + mms[0].first.Width * channels, &options, 0); +#endif + for (unsigned int i = 1; i < mms.size(); i++) + { + const unsigned copy_size = mms[i].first.getArea() * channels; + memcpy(out, cascade.mipmap[i], copy_size); + out += copy_size; + mms[i].second = mms[i - 1].first.getArea() * channels + + mms[i - 1].second; + } + imFreeMipmapCascade(&cascade); +} // generateHQMipmap + } diff --git a/lib/graphics_engine/src/ge_vulkan_texture.hpp b/lib/graphics_engine/src/ge_vulkan_texture.hpp index 387db61f6..ac1d50811 100644 --- a/lib/graphics_engine/src/ge_vulkan_texture.hpp +++ b/lib/graphics_engine/src/ge_vulkan_texture.hpp @@ -5,8 +5,11 @@ #include "ge_spin_lock.hpp" +#include +#include #include #include +#include #include using namespace irr; @@ -36,6 +39,8 @@ private: const bool m_single_channel; + bool m_has_mipmaps; + GESpinLock m_size_lock; GESpinLock m_image_view_lock; @@ -43,7 +48,7 @@ private: io::path m_full_path; // ------------------------------------------------------------------------ - bool createTextureImage(uint8_t* texture_data); + bool createTextureImage(uint8_t* texture_data, bool generate_hq_mipmap); // ------------------------------------------------------------------------ bool createImage(VkImageUsageFlags usage); // ------------------------------------------------------------------------ @@ -54,9 +59,10 @@ private: VkImageLayout new_layout); // ------------------------------------------------------------------------ void copyBufferToImage(VkCommandBuffer command_buffer, VkBuffer buffer, - u32 w, u32 h, s32 x, s32 y); + u32 w, u32 h, s32 x, s32 y, u32 offset, + u32 mipmap_level); // ------------------------------------------------------------------------ - void upload(uint8_t* data); + void upload(uint8_t* data, bool generate_hq_mipmap = false); // ------------------------------------------------------------------------ void clearVulkanData(); // ------------------------------------------------------------------------ @@ -66,6 +72,37 @@ private: // ------------------------------------------------------------------------ uint8_t* getTextureData(); // ------------------------------------------------------------------------ + std::vector > getMipmapSizes() + { + std::vector > mipmap_sizes; + unsigned width = m_size.Width; + unsigned height = m_size.Height; + mipmap_sizes.emplace_back(core::dimension2du(width, height), + 0); + while (true) + { + width = width < 2 ? 1 : width >> 1; + height = height < 2 ? 1 : height >> 1; + mipmap_sizes.emplace_back(core::dimension2du(width, height), 0); + if (width == 1 && height == 1) + { + break; + } + } + return mipmap_sizes; + } + // ------------------------------------------------------------------------ + void generateHQMipmap(void* in, + std::vector >& mms, uint8_t* out); + // ------------------------------------------------------------------------ + unsigned getMipmapLevels() const + { + if (!hasMipMaps()) + return 1; + return std::floor(std::log2(std::max(m_size.Width, m_size.Height))) + 1; + } + // ------------------------------------------------------------------------ public: // ------------------------------------------------------------------------ GEVulkanTexture(const std::string& path, @@ -112,7 +149,7 @@ public: // ------------------------------------------------------------------------ virtual u32 getPitch() const { return 0; } // ------------------------------------------------------------------------ - virtual bool hasMipMaps() const { return false; } + virtual bool hasMipMaps() const { return m_has_mipmaps; } // ------------------------------------------------------------------------ virtual void regenerateMipMapLevels(void* mipmap_data = NULL) {} // ------------------------------------------------------------------------