862 lines
32 KiB
C++
862 lines
32 KiB
C++
#include "ge_vulkan_texture.hpp"
|
|
|
|
#include "ge_main.hpp"
|
|
#include "ge_mipmap_generator.hpp"
|
|
#include "ge_compressor_astc_4x4.hpp"
|
|
#include "ge_compressor_bptc_bc7.hpp"
|
|
#include "ge_compressor_s3tc_bc3.hpp"
|
|
#include "ge_texture.hpp"
|
|
#include "ge_vulkan_command_loader.hpp"
|
|
#include "ge_vulkan_features.hpp"
|
|
#include "ge_vulkan_driver.hpp"
|
|
|
|
extern "C"
|
|
{
|
|
#include <mipmap/img.h>
|
|
#include <mipmap/imgresize.h>
|
|
}
|
|
|
|
#include <cassert>
|
|
#include <IAttributes.h>
|
|
#include <IImageLoader.h>
|
|
#include <limits>
|
|
|
|
namespace GE
|
|
{
|
|
GEVulkanTexture::GEVulkanTexture(const std::string& path,
|
|
std::function<void(video::IImage*)> image_mani)
|
|
: video::ITexture(path.c_str()), m_image_mani(image_mani),
|
|
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_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())
|
|
{
|
|
m_max_size = getDriver()->getDriverAttributes()
|
|
.getAttributeAsDimension2d("MAX_TEXTURE_SIZE");
|
|
m_full_path = getDriver()->getFileSystem()->getAbsolutePath(NamedPath);
|
|
if (!getDriver()->getFileSystem()->existFileOnly(m_full_path))
|
|
{
|
|
LoadingFailed = true;
|
|
return;
|
|
}
|
|
|
|
auto& paths = getGEConfig()->m_ondemand_load_texture_paths;
|
|
m_ondemand_load = (paths.find(m_full_path.c_str()) != paths.end());
|
|
if (m_ondemand_load)
|
|
{
|
|
video::IImageLoader* loader = NULL;
|
|
io::IReadFile* file = io::createReadFile(m_full_path);
|
|
getDriver()->createImageFromFile(file, &loader);
|
|
if (loader && loader->getImageSize(file, &m_orig_size))
|
|
{
|
|
m_size = getResizingTarget(m_orig_size, m_max_size);
|
|
if (m_size.Width < 4 || m_size.Height < 4)
|
|
m_has_mipmaps = false;
|
|
setPlaceHolderView();
|
|
}
|
|
else
|
|
LoadingFailed = true;
|
|
file->drop();
|
|
return;
|
|
}
|
|
|
|
m_size_lock.lock();
|
|
m_image_view_lock.lock();
|
|
GEVulkanCommandLoader::addMultiThreadingCommand(
|
|
std::bind(&GEVulkanTexture::reloadInternal, this));
|
|
} // GEVulkanTexture
|
|
|
|
// ----------------------------------------------------------------------------
|
|
GEVulkanTexture::GEVulkanTexture(video::IImage* img, const std::string& name)
|
|
: 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_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())
|
|
{
|
|
if (!img)
|
|
{
|
|
LoadingFailed = true;
|
|
return;
|
|
}
|
|
m_size = m_orig_size = img->getDimension();
|
|
if (m_size.Width < 4 || m_size.Height < 4)
|
|
m_has_mipmaps = false;
|
|
uint8_t* data = (uint8_t*)img->lock();
|
|
bgraConversion(data);
|
|
upload(data, m_has_mipmaps/*generate_hq_mipmap*/);
|
|
img->unlock();
|
|
img->drop();
|
|
} // GEVulkanTexture
|
|
|
|
// ----------------------------------------------------------------------------
|
|
GEVulkanTexture::GEVulkanTexture(const std::string& name, unsigned int size,
|
|
bool single_channel)
|
|
: 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_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())
|
|
{
|
|
if (isSingleChannel() && !GEVulkanFeatures::supportsR8Blit())
|
|
m_has_mipmaps = false;
|
|
else if (!isSingleChannel() && !GEVulkanFeatures::supportsRGBA8Blit())
|
|
m_has_mipmaps = false;
|
|
|
|
m_orig_size.Width = size;
|
|
m_orig_size.Height = size;
|
|
m_size = m_orig_size;
|
|
|
|
std::vector<uint8_t> data;
|
|
data.resize(size * size * (isSingleChannel() ? 1 : 4), 0);
|
|
upload(data.data());
|
|
} // GEVulkanTexture
|
|
|
|
// ----------------------------------------------------------------------------
|
|
GEVulkanTexture::~GEVulkanTexture()
|
|
{
|
|
waitImageView();
|
|
|
|
if (m_image_view || m_image != VK_NULL_HANDLE ||
|
|
m_vma_allocation != VK_NULL_HANDLE)
|
|
m_vk->waitIdle();
|
|
|
|
clearVulkanData();
|
|
} // ~GEVulkanTexture
|
|
|
|
// ----------------------------------------------------------------------------
|
|
bool GEVulkanTexture::createTextureImage(uint8_t* texture_data,
|
|
bool generate_hq_mipmap)
|
|
{
|
|
VkDeviceSize mipmap_data_size = 0;
|
|
GEMipmapGenerator* mipmap_generator = NULL;
|
|
|
|
unsigned channels = (isSingleChannel() ? 1 : 4);
|
|
VkDeviceSize image_size = m_size.Width * m_size.Height * channels;
|
|
if (generate_hq_mipmap)
|
|
{
|
|
const bool normal_map = (std::string(NamedPath.getPtr()).find(
|
|
"_Normal.") != std::string::npos);
|
|
bool texture_compression = getGEConfig()->m_texture_compression;
|
|
if (texture_compression && GEVulkanFeatures::supportsASTC4x4())
|
|
{
|
|
image_size = get4x4CompressedTextureSize(m_size.Width,
|
|
m_size.Height);
|
|
m_internal_format = VK_FORMAT_ASTC_4x4_UNORM_BLOCK;
|
|
mipmap_generator = new GECompressorASTC4x4(texture_data, channels,
|
|
m_size, normal_map);
|
|
}
|
|
else if (texture_compression && GEVulkanFeatures::supportsBPTCBC7())
|
|
{
|
|
image_size = get4x4CompressedTextureSize(m_size.Width,
|
|
m_size.Height);
|
|
m_internal_format = VK_FORMAT_BC7_UNORM_BLOCK;
|
|
mipmap_generator = new GECompressorBPTCBC7(texture_data, channels,
|
|
m_size, normal_map);
|
|
}
|
|
else if (texture_compression && GEVulkanFeatures::supportsS3TCBC3())
|
|
{
|
|
image_size = get4x4CompressedTextureSize(m_size.Width,
|
|
m_size.Height);
|
|
m_internal_format = VK_FORMAT_BC3_UNORM_BLOCK;
|
|
mipmap_generator = new GECompressorS3TCBC3(texture_data, channels,
|
|
m_size, normal_map);
|
|
}
|
|
else
|
|
{
|
|
mipmap_generator = new GEMipmapGenerator(texture_data, channels,
|
|
m_size, normal_map);
|
|
}
|
|
mipmap_data_size = mipmap_generator->getMipmapSizes();
|
|
}
|
|
|
|
VkDeviceSize image_total_size = image_size + mipmap_data_size;
|
|
VkBuffer staging_buffer;
|
|
VmaAllocation staging_buffer_allocation;
|
|
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;
|
|
|
|
bool success = m_vk->createBuffer(image_total_size,
|
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT, staging_buffer_create_info,
|
|
staging_buffer, staging_buffer_allocation);
|
|
|
|
if (!success)
|
|
return false;
|
|
|
|
VkResult ret = VK_SUCCESS;
|
|
uint8_t* data;
|
|
VkCommandBuffer command_buffer = VK_NULL_HANDLE;
|
|
if ((ret = vmaMapMemory(m_vk->getVmaAllocator(), staging_buffer_allocation,
|
|
(void**)&data)) != VK_SUCCESS)
|
|
goto destroy;
|
|
|
|
if (mipmap_generator)
|
|
{
|
|
for (GEImageLevel& level : mipmap_generator->getAllLevels())
|
|
{
|
|
memcpy(data, level.m_data, level.m_size);
|
|
data += level.m_size;
|
|
}
|
|
}
|
|
else
|
|
memcpy(data, texture_data, image_size);
|
|
vmaUnmapMemory(m_vk->getVmaAllocator(), staging_buffer_allocation);
|
|
vmaFlushAllocation(m_vk->getVmaAllocator(),
|
|
staging_buffer_allocation, 0, image_total_size);
|
|
|
|
success = createImage(VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
|
|
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
|
|
if (!success)
|
|
{
|
|
ret = VK_NOT_READY;
|
|
goto destroy;
|
|
}
|
|
|
|
command_buffer = GEVulkanCommandLoader::beginSingleTimeCommands();
|
|
|
|
transitionImageLayout(command_buffer, VK_IMAGE_LAYOUT_UNDEFINED,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
|
|
|
|
if (mipmap_generator)
|
|
{
|
|
unsigned offset = 0;
|
|
std::vector<GEImageLevel>& levels = mipmap_generator->getAllLevels();
|
|
for (unsigned i = 0; i < levels.size(); i++)
|
|
{
|
|
GEImageLevel& level = levels[i];
|
|
copyBufferToImage(command_buffer, staging_buffer,
|
|
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, 0);
|
|
}
|
|
|
|
transitionImageLayout(command_buffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
|
|
|
GEVulkanCommandLoader::endSingleTimeCommands(command_buffer);
|
|
|
|
destroy:
|
|
delete mipmap_generator;
|
|
vmaDestroyBuffer(m_vk->getVmaAllocator(), staging_buffer,
|
|
staging_buffer_allocation);
|
|
return ret == VK_SUCCESS;
|
|
} // createTextureImage
|
|
|
|
// ----------------------------------------------------------------------------
|
|
bool GEVulkanTexture::createImage(VkImageUsageFlags usage)
|
|
{
|
|
VkImageCreateInfo image_info = {};
|
|
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
image_info.imageType = VK_IMAGE_TYPE_2D;
|
|
image_info.extent.width = m_size.Width;
|
|
image_info.extent.height = m_size.Height;
|
|
image_info.extent.depth = 1;
|
|
image_info.mipLevels = getMipmapLevels();
|
|
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;
|
|
VkResult result = vmaCreateImage(m_vk->getVmaAllocator(), &image_info,
|
|
&alloc_info, &m_image, &m_vma_allocation, NULL);
|
|
|
|
if (result != VK_SUCCESS)
|
|
return false;
|
|
|
|
return true;
|
|
} // createImage
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void GEVulkanTexture::transitionImageLayout(VkCommandBuffer command_buffer,
|
|
VkImageLayout old_layout,
|
|
VkImageLayout new_layout)
|
|
{
|
|
VkImageMemoryBarrier barrier = {};
|
|
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
|
barrier.oldLayout = old_layout;
|
|
barrier.newLayout = new_layout;
|
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
barrier.image = m_image;
|
|
barrier.subresourceRange.baseMipLevel = 0;
|
|
barrier.subresourceRange.levelCount = getMipmapLevels();
|
|
barrier.subresourceRange.baseArrayLayer = 0;
|
|
barrier.subresourceRange.layerCount = m_layer_count;
|
|
|
|
if (new_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL)
|
|
{
|
|
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
|
}
|
|
else
|
|
{
|
|
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
}
|
|
|
|
VkPipelineStageFlags source_stage;
|
|
VkPipelineStageFlags destination_stage;
|
|
|
|
if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED &&
|
|
new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
|
{
|
|
barrier.srcAccessMask = 0;
|
|
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
|
|
source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
|
destination_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
}
|
|
else if (old_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL &&
|
|
new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
|
{
|
|
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
|
|
source_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
destination_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
|
}
|
|
else if (old_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL &&
|
|
new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
|
{
|
|
barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
|
|
source_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
|
destination_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
}
|
|
else if (old_layout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL &&
|
|
new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
|
{
|
|
barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
|
|
source_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
destination_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
|
}
|
|
else if (old_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL &&
|
|
new_layout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
|
{
|
|
barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
|
|
source_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
|
destination_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
}
|
|
else if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED &&
|
|
new_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL)
|
|
{
|
|
barrier.srcAccessMask = 0;
|
|
barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
|
|
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
|
|
|
source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
|
destination_stage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
vkCmdPipelineBarrier(command_buffer, source_stage, destination_stage, 0, 0,
|
|
NULL, 0, NULL, 1, &barrier);
|
|
} // transitionImageLayout
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void GEVulkanTexture::copyBufferToImage(VkCommandBuffer command_buffer,
|
|
VkBuffer buffer, u32 w, u32 h, s32 x,
|
|
s32 y, u32 offset, u32 mipmap_level,
|
|
u32 layer_level)
|
|
{
|
|
VkBufferImageCopy region = {};
|
|
region.bufferOffset = offset;
|
|
region.bufferRowLength = 0;
|
|
region.bufferImageHeight = 0;
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.imageSubresource.mipLevel = mipmap_level;
|
|
region.imageSubresource.baseArrayLayer = layer_level;
|
|
region.imageSubresource.layerCount = 1;
|
|
region.imageOffset = {x, y, 0};
|
|
region.imageExtent = {w, h, 1};
|
|
|
|
vkCmdCopyBufferToImage(command_buffer, buffer, m_image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
|
} // copyBufferToImage
|
|
|
|
// ----------------------------------------------------------------------------
|
|
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 = 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 = m_layer_count;
|
|
if (isSingleChannel())
|
|
{
|
|
view_info.components.r = VK_COMPONENT_SWIZZLE_ONE;
|
|
view_info.components.g = VK_COMPONENT_SWIZZLE_ONE;
|
|
view_info.components.b = VK_COMPONENT_SWIZZLE_ONE;
|
|
view_info.components.a = VK_COMPONENT_SWIZZLE_R;
|
|
}
|
|
|
|
auto image_view = std::make_shared<std::atomic<VkImageView> >();
|
|
VkImageView view_ptr = NULL;
|
|
VkResult result = vkCreateImageView(m_vulkan_device, &view_info, NULL,
|
|
&view_ptr);
|
|
if (result == VK_SUCCESS)
|
|
{
|
|
image_view.get()->store(view_ptr);
|
|
m_image_view = image_view;
|
|
|
|
if (m_placeholder_view)
|
|
m_placeholder_view.get()->store(VK_NULL_HANDLE);
|
|
m_ondemand_loading.store(false);
|
|
return true;
|
|
}
|
|
return false;
|
|
} // createImageView
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void GEVulkanTexture::clearVulkanData()
|
|
{
|
|
if (m_image_view)
|
|
{
|
|
vkDestroyImageView(m_vulkan_device, m_image_view.get()->load(), NULL);
|
|
m_image_view.get()->store(VK_NULL_HANDLE);
|
|
m_image_view.reset();
|
|
}
|
|
if (m_image != VK_NULL_HANDLE)
|
|
{
|
|
vmaDestroyImage(m_vk->getVmaAllocator(), m_image, m_vma_allocation);
|
|
m_image = VK_NULL_HANDLE;
|
|
m_vma_allocation = VK_NULL_HANDLE;
|
|
}
|
|
} // clearVulkanData
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void GEVulkanTexture::reloadInternal()
|
|
{
|
|
if (m_disable_reload)
|
|
return;
|
|
|
|
clearVulkanData();
|
|
|
|
video::IImage* texture_image = NULL;
|
|
if (m_ondemand_load)
|
|
{
|
|
texture_image = getResizedImageFullPath(m_full_path, m_max_size,
|
|
NULL, &m_size);
|
|
if (texture_image == NULL)
|
|
{
|
|
throw std::runtime_error(
|
|
"Missing texture_image in getResizedImageFullPath");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
texture_image = getResizedImageFullPath(m_full_path, m_max_size,
|
|
&m_orig_size);
|
|
if (texture_image == NULL)
|
|
{
|
|
throw std::runtime_error(
|
|
"Missing texture_image in getResizedImageFullPath");
|
|
}
|
|
m_size = texture_image->getDimension();
|
|
if (m_size.Width < 4 || m_size.Height < 4)
|
|
m_has_mipmaps = false;
|
|
}
|
|
m_size_lock.unlock();
|
|
|
|
if (m_image_mani)
|
|
m_image_mani(texture_image);
|
|
|
|
uint8_t* data = (uint8_t*)texture_image->lock();
|
|
bgraConversion(data);
|
|
upload(data, m_has_mipmaps/*generate_hq_mipmap*/);
|
|
m_image_view_lock.unlock();
|
|
|
|
texture_image->unlock();
|
|
texture_image->drop();
|
|
} // reloadInternal
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void GEVulkanTexture::upload(uint8_t* data, bool generate_hq_mipmap)
|
|
{
|
|
if (!createTextureImage(data, generate_hq_mipmap))
|
|
return;
|
|
if (!createImageView(VK_IMAGE_ASPECT_COLOR_BIT))
|
|
return;
|
|
m_texture_size = m_size.Width * m_size.Height * (isSingleChannel() ? 1 : 4);
|
|
} // upload
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void* GEVulkanTexture::lock(video::E_TEXTURE_LOCK_MODE mode, u32 mipmap_level)
|
|
{
|
|
uint8_t* texture_data = getTextureData();
|
|
if (!texture_data)
|
|
return NULL;
|
|
if (isSingleChannel())
|
|
{
|
|
m_locked_data = new uint8_t[m_size.Width * m_size.Height * 4]();
|
|
for (unsigned int i = 0; i < m_size.Width * m_size.Height; i++)
|
|
{
|
|
m_locked_data[i * 4 + 2] = texture_data[i];
|
|
m_locked_data[i * 4 + 3] = 255;
|
|
}
|
|
delete [] texture_data;
|
|
return m_locked_data;
|
|
}
|
|
else
|
|
{
|
|
m_locked_data = texture_data;
|
|
bgraConversion(m_locked_data);
|
|
return m_locked_data;
|
|
}
|
|
} // lock
|
|
|
|
// ----------------------------------------------------------------------------
|
|
uint8_t* GEVulkanTexture::getTextureData()
|
|
{
|
|
if (m_internal_format != VK_FORMAT_R8G8B8A8_UNORM ||
|
|
m_internal_format != VK_FORMAT_R8_UNORM)
|
|
{
|
|
if (m_full_path.empty())
|
|
return NULL;
|
|
|
|
video::IImage* texture_image = getResizedImageFullPath(m_full_path,
|
|
m_max_size, NULL, &m_size);
|
|
if (texture_image == NULL)
|
|
return NULL;
|
|
texture_image->setDeleteMemory(false);
|
|
uint8_t* data = (uint8_t*)texture_image->lock();
|
|
texture_image->drop();
|
|
return data;
|
|
}
|
|
|
|
if (!waitImageView())
|
|
return NULL;
|
|
|
|
VkBuffer buffer;
|
|
VmaAllocation buffer_allocation;
|
|
VkDeviceSize image_size =
|
|
m_size.Width * m_size.Height * (isSingleChannel() ? 1 : 4);
|
|
VmaAllocationCreateInfo buffer_create_info = {};
|
|
buffer_create_info.usage = VMA_MEMORY_USAGE_AUTO;
|
|
buffer_create_info.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
|
|
buffer_create_info.preferredFlags = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
|
|
|
if (!m_vk->createBuffer(image_size,
|
|
VK_BUFFER_USAGE_TRANSFER_DST_BIT, buffer_create_info, buffer,
|
|
buffer_allocation))
|
|
return NULL;
|
|
|
|
VkCommandBuffer command_buffer = GEVulkanCommandLoader::beginSingleTimeCommands();
|
|
|
|
transitionImageLayout(command_buffer,
|
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
|
|
|
|
VkBufferImageCopy region = {};
|
|
region.bufferOffset = 0;
|
|
region.bufferRowLength = 0;
|
|
region.bufferImageHeight = 0;
|
|
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
region.imageSubresource.mipLevel = 0;
|
|
region.imageSubresource.baseArrayLayer = 0;
|
|
region.imageSubresource.layerCount = 1;
|
|
region.imageOffset = {0, 0, 0};
|
|
region.imageExtent = {m_size.Width, m_size.Height, 1};
|
|
|
|
vkCmdCopyImageToBuffer(command_buffer, m_image,
|
|
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer, 1, ®ion);
|
|
|
|
transitionImageLayout(command_buffer, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
|
|
|
GEVulkanCommandLoader::endSingleTimeCommands(command_buffer);
|
|
|
|
uint8_t* texture_data = new uint8_t[image_size];
|
|
void* mapped_data;
|
|
if (vmaMapMemory(m_vk->getVmaAllocator(), buffer_allocation,
|
|
&mapped_data) != VK_SUCCESS)
|
|
{
|
|
delete [] texture_data;
|
|
texture_data = NULL;
|
|
goto cleanup;
|
|
}
|
|
|
|
vmaInvalidateAllocation(m_vk->getVmaAllocator(), buffer_allocation,
|
|
0, image_size);
|
|
memcpy(texture_data, mapped_data, image_size);
|
|
vmaUnmapMemory(m_vk->getVmaAllocator(), buffer_allocation);
|
|
|
|
cleanup:
|
|
vmaDestroyBuffer(m_vk->getVmaAllocator(), buffer, buffer_allocation);
|
|
return texture_data;
|
|
} // getTextureData
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void GEVulkanTexture::updateTexture(void* data, video::ECOLOR_FORMAT format,
|
|
u32 w, u32 h, u32 x, u32 y)
|
|
{
|
|
if (!waitImageView())
|
|
return;
|
|
|
|
VkBuffer staging_buffer;
|
|
VmaAllocation staging_buffer_allocation;
|
|
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;
|
|
if (isSingleChannel())
|
|
{
|
|
if (format == video::ECF_R8)
|
|
{
|
|
unsigned image_size = w * h;
|
|
if (!m_vk->createBuffer(image_size,
|
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT, staging_buffer_create_info,
|
|
staging_buffer, staging_buffer_allocation))
|
|
return;
|
|
|
|
void* mapped_data;
|
|
vmaMapMemory(m_vk->getVmaAllocator(), staging_buffer_allocation,
|
|
&mapped_data);
|
|
memcpy(mapped_data, data, (size_t)(image_size));
|
|
vmaUnmapMemory(m_vk->getVmaAllocator(), staging_buffer_allocation);
|
|
vmaFlushAllocation(m_vk->getVmaAllocator(),
|
|
staging_buffer_allocation, 0, image_size);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (format == video::ECF_R8)
|
|
{
|
|
unsigned image_size = w * h * 4;
|
|
if (!m_vk->createBuffer(w * h * 4,
|
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT, staging_buffer_create_info,
|
|
staging_buffer, staging_buffer_allocation))
|
|
return;
|
|
|
|
const unsigned int size = w * h;
|
|
std::vector<uint8_t> image_data(size * 4, 255);
|
|
uint8_t* orig_data = (uint8_t*)data;
|
|
for (unsigned int i = 0; i < size; i++)
|
|
image_data[4 * i + 3] = orig_data[i];
|
|
void* mapped_data;
|
|
vmaMapMemory(m_vk->getVmaAllocator(), staging_buffer_allocation,
|
|
&mapped_data);
|
|
memcpy(mapped_data, image_data.data(), (size_t)(image_size));
|
|
vmaUnmapMemory(m_vk->getVmaAllocator(), staging_buffer_allocation);
|
|
vmaFlushAllocation(m_vk->getVmaAllocator(),
|
|
staging_buffer_allocation, 0, image_size);
|
|
}
|
|
else if (format == video::ECF_A8R8G8B8)
|
|
{
|
|
unsigned image_size = w * h * 4;
|
|
if (!m_vk->createBuffer(w * h * 4,
|
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT, staging_buffer_create_info,
|
|
staging_buffer, staging_buffer_allocation))
|
|
return;
|
|
|
|
uint8_t* u8_data = (uint8_t*)data;
|
|
for (unsigned int i = 0; i < w * h; i++)
|
|
{
|
|
uint8_t tmp_val = u8_data[i * 4];
|
|
u8_data[i * 4] = u8_data[i * 4 + 2];
|
|
u8_data[i * 4 + 2] = tmp_val;
|
|
}
|
|
void* mapped_data;
|
|
vmaMapMemory(m_vk->getVmaAllocator(), staging_buffer_allocation,
|
|
&mapped_data);
|
|
memcpy(mapped_data, u8_data, (size_t)(image_size));
|
|
vmaUnmapMemory(m_vk->getVmaAllocator(), staging_buffer_allocation);
|
|
vmaFlushAllocation(m_vk->getVmaAllocator(),
|
|
staging_buffer_allocation, 0, image_size);
|
|
}
|
|
}
|
|
|
|
VkCommandBuffer command_buffer = GEVulkanCommandLoader::beginSingleTimeCommands();
|
|
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, 0);
|
|
|
|
bool blit_mipmap = true;
|
|
if (isSingleChannel() && !GEVulkanFeatures::supportsR8Blit())
|
|
blit_mipmap = false;
|
|
else if (!isSingleChannel() && !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);
|
|
|
|
vmaDestroyBuffer(m_vk->getVmaAllocator(), staging_buffer,
|
|
staging_buffer_allocation);
|
|
} // updateTexture
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void GEVulkanTexture::bgraConversion(uint8_t* img_data)
|
|
{
|
|
for (unsigned int i = 0; i < m_size.Width * m_size.Height; i++)
|
|
{
|
|
uint8_t tmp_val = img_data[i * 4];
|
|
img_data[i * 4] = img_data[i * 4 + 2];
|
|
img_data[i * 4 + 2] = tmp_val;
|
|
}
|
|
} // bgraConversion
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void GEVulkanTexture::reload()
|
|
{
|
|
if (!waitImageView())
|
|
return;
|
|
|
|
if (m_image_view || m_image != VK_NULL_HANDLE ||
|
|
m_vma_allocation != VK_NULL_HANDLE)
|
|
m_vk->waitIdle();
|
|
|
|
if (m_ondemand_load)
|
|
{
|
|
clearVulkanData();
|
|
setPlaceHolderView();
|
|
}
|
|
else if (!m_disable_reload)
|
|
{
|
|
m_size_lock.lock();
|
|
m_image_view_lock.lock();
|
|
GEVulkanCommandLoader::addMultiThreadingCommand(
|
|
std::bind(&GEVulkanTexture::reloadInternal, this));
|
|
}
|
|
} // reload
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void GEVulkanTexture::setPlaceHolderView()
|
|
{
|
|
auto tex = static_cast<GEVulkanTexture*>(m_vk->getTransparentTexture());
|
|
auto image_view = std::make_shared<std::atomic<VkImageView> >();
|
|
image_view.get()->store((VkImageView)tex->getTextureHandler());
|
|
if (m_placeholder_view)
|
|
m_placeholder_view.get()->store(VK_NULL_HANDLE);
|
|
m_placeholder_view = image_view;
|
|
} // setPlaceHolderView
|
|
|
|
//-----------------------------------------------------------------------------
|
|
std::shared_ptr<std::atomic<VkImageView> > GEVulkanTexture::getImageViewLive() const
|
|
{
|
|
assert(m_ondemand_load && m_placeholder_view);
|
|
if (m_ondemand_loading.load() == false)
|
|
{
|
|
if (m_image_view)
|
|
return m_image_view;
|
|
else
|
|
{
|
|
GEVulkanTexture* tex = const_cast<GEVulkanTexture*>(this);
|
|
GEVulkanCommandLoader::addMultiThreadingCommand(
|
|
std::bind(&GEVulkanTexture::reloadInternal, tex));
|
|
m_ondemand_loading.store(true);
|
|
return m_placeholder_view;
|
|
}
|
|
}
|
|
return m_placeholder_view;
|
|
} // getImageViewLive
|
|
|
|
}
|