Add mipmap generation for GEVulkanTexture

This commit is contained in:
Benau 2022-04-19 12:49:05 +08:00
parent b0421569d0
commit 26bd8a5b8b
6 changed files with 257 additions and 26 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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()
{

View File

@ -15,6 +15,10 @@ void printStats();
// ----------------------------------------------------------------------------
bool supportsBindTexturesAtOnce();
// ----------------------------------------------------------------------------
bool supportsRGBA8Blit();
// ----------------------------------------------------------------------------
bool supportsR8Blit();
// ----------------------------------------------------------------------------
bool supportsDescriptorIndexing();
// ----------------------------------------------------------------------------
bool supportsNonUniformIndexing();

View File

@ -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 <mipmap/img.h>
#include <mipmap/imgresize.h>
}
#include <IAttributes.h>
#include <limits>
#include <vector>
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<std::pair<core::dimension2du, unsigned> > 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<std::pair
<core::dimension2du, unsigned> >& 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
}

View File

@ -5,8 +5,11 @@
#include "ge_spin_lock.hpp"
#include <algorithm>
#include <cmath>
#include <functional>
#include <string>
#include <vector>
#include <ITexture.h>
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<std::pair<core::dimension2du, unsigned> > getMipmapSizes()
{
std::vector<std::pair<core::dimension2du, unsigned> > 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<std::pair<core::dimension2du,
unsigned> >& 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) {}
// ------------------------------------------------------------------------