VulkanTexture: support format conversion via vkCmdBlitImage.

This commit is contained in:
Philip Rideout
2021-05-06 19:23:21 -07:00
parent 4a25ba1111
commit 812f0d577b
6 changed files with 293 additions and 28 deletions

View File

@@ -17,6 +17,7 @@
#include "VulkanStagePool.h"
#include "VulkanConstants.h"
#include "VulkanUtility.h"
#include <utils/Panic.h>
@@ -58,6 +59,51 @@ VulkanStage const* VulkanStagePool::acquireStage(uint32_t numBytes) {
return stage;
}
VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, PixelDataType type,
uint32_t width, uint32_t height) {
const VkFormat vkformat = getVkFormat(format, type);
for (auto image : mFreeImages) {
if (image->format == vkformat && image->width == width && image->height == height) {
mFreeImages.erase(image);
mUsedImages.insert(image);
return image;
}
}
VulkanStageImage* image = new VulkanStageImage({
.format = vkformat,
.width = width,
.height = height,
.lastAccessed = mCurrentFrame,
});
mUsedImages.insert(image);
const VkImageCreateInfo imageInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = vkformat,
.extent = { width, height, 1 },
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_LINEAR,
.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT
};
const VmaAllocationCreateInfo allocInfo {
.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT,
.usage = VMA_MEMORY_USAGE_CPU_TO_GPU
};
const UTILS_UNUSED VkResult result = vmaCreateImage(mContext.allocator, &imageInfo, &allocInfo,
&image->image, &image->memory, nullptr);
assert_invariant(result == VK_SUCCESS);
return image;
}
void VulkanStagePool::gc() noexcept {
// If this is one of the first few frames, return early to avoid wrapping unsigned integers.
if (++mCurrentFrame <= TIME_BEFORE_EVICTION) {
@@ -65,6 +111,7 @@ void VulkanStagePool::gc() noexcept {
}
const uint64_t evictionTime = mCurrentFrame - TIME_BEFORE_EVICTION;
// Destroy buffers that have not been used for several frames.
decltype(mFreeStages) freeStages;
freeStages.swap(mFreeStages);
for (auto pair : freeStages) {
@@ -76,6 +123,7 @@ void VulkanStagePool::gc() noexcept {
}
}
// Reclaim buffers that are no longer being used by any command buffer.
decltype(mUsedStages) usedStages;
usedStages.swap(mUsedStages);
for (auto stage : usedStages) {
@@ -86,6 +134,30 @@ void VulkanStagePool::gc() noexcept {
mUsedStages.insert(stage);
}
}
// Destroy images that have not been used for several frames.
decltype(mFreeImages) freeImages;
freeImages.swap(mFreeImages);
for (auto image : freeImages) {
if (image->lastAccessed < evictionTime) {
vmaDestroyImage(mContext.allocator, image->image, image->memory);
delete image;
} else {
mFreeImages.insert(image);
}
}
// Reclaim images that are no longer being used by any command buffer.
decltype(mUsedImages) usedImages;
usedImages.swap(mUsedImages);
for (auto image : usedImages) {
if (image->lastAccessed < evictionTime) {
image->lastAccessed = mCurrentFrame;
mFreeImages.insert(image);
} else {
mUsedImages.insert(image);
}
}
}
void VulkanStagePool::reset() noexcept {
@@ -94,11 +166,24 @@ void VulkanStagePool::reset() noexcept {
delete stage;
}
mUsedStages.clear();
for (auto pair : mFreeStages) {
vmaDestroyBuffer(mContext.allocator, pair.second->buffer, pair.second->memory);
delete pair.second;
}
mFreeStages.clear();
for (auto image : mUsedImages) {
vmaDestroyImage(mContext.allocator, image->image, image->memory);
delete image;
}
mUsedStages.clear();
for (auto image : mFreeImages) {
vmaDestroyImage(mContext.allocator, image->image, image->memory);
delete image;
}
mFreeStages.clear();
}
} // namespace filament

View File

@@ -33,7 +33,17 @@ struct VulkanStage {
mutable uint64_t lastAccessed;
};
struct VulkanStageImage {
VkFormat format;
uint32_t width;
uint32_t height;
mutable uint64_t lastAccessed;
VmaAllocation memory;
VkImage image;
};
// Manages a pool of stages, periodically releasing stages that have been unused for a while.
// This class manages two types of host-mappable staging areas: buffer stages and image stages.
class VulkanStagePool {
public:
explicit VulkanStagePool(VulkanContext& context) noexcept : mContext(context) {}
@@ -42,6 +52,9 @@ public:
// The stage is automatically released back to the pool after TIME_BEFORE_EVICTION frames.
VulkanStage const* acquireStage(uint32_t numBytes);
VulkanStageImage const* acquireImage(PixelDataFormat format, PixelDataType type,
uint32_t width, uint32_t height);
// Evicts old unused stages and bumps the current frame number.
void gc() noexcept;
@@ -58,6 +71,9 @@ private:
// Simple unordered set for stashing a list of in-use stages that can be reclaimed later.
std::unordered_set<VulkanStage const*> mUsedStages;
std::unordered_set<VulkanStageImage const*> mFreeImages;
std::unordered_set<VulkanStageImage const*> mUsedImages;
// Store the current "time" (really just a frame count) and LRU eviction parameters.
uint64_t mCurrentFrame = 0;
};

View File

@@ -236,48 +236,74 @@ void VulkanTexture::update2DImage(const PixelBufferDescriptor& data, uint32_t wi
void VulkanTexture::update3DImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
uint32_t depth, int miplevel) {
assert_invariant(width <= this->width && height <= this->height && depth <= this->depth);
const uint32_t numSrcBytes = data.size;
const PixelBufferDescriptor* hostData = &data;
PixelBufferDescriptor reshapedData;
uint32_t numDstBytes = numSrcBytes;
PixelDataFormat dstFormat = data.format;
void* mapped = nullptr;
VulkanStage const* stage = nullptr;
const void* clientData = data.buffer;
// Create and populate the staging buffer.
// First, reshape 3-component data into 4-component data. The fourth component is usually
// set to 1 (one exception is when type = HALF). In practice, alpha is just a dummy channel.
// Note that the reshaped data is freed at the end of this method due to the callback.
if (data.format == PixelDataFormat::RGB) {
const auto freeFunc = [](void* buffer, size_t size, void* user) { free(buffer); };
const size_t reshapedSize = 4 * data.size / 3;
const PixelDataFormat reshapedFormat = PixelDataFormat::RGBA;
switch (data.type) {
case PixelDataType::BYTE:
case PixelDataType::UBYTE:
numDstBytes = 4 * numSrcBytes / 3;
dstFormat = PixelDataFormat::RGBA;
stage = mStagePool.acquireStage(numDstBytes);
vmaMapMemory(mContext.allocator, stage->memory, &mapped);
DataReshaper::reshape<uint8_t, 3, 4>(mapped, clientData, numSrcBytes);
case PixelDataType::UBYTE: {
uint8_t* bytes = (uint8_t*) malloc(reshapedSize);
DataReshaper::reshape<uint8_t, 3, 4>(bytes, data.buffer, data.size);
PixelBufferDescriptor pbd(bytes, reshapedSize, reshapedFormat, data.type, freeFunc);
reshapedData = std::move(pbd);
hostData = &reshapedData;
break;
}
case PixelDataType::SHORT:
case PixelDataType::USHORT:
case PixelDataType::HALF:
numDstBytes = 4 * numSrcBytes / 3;
dstFormat = PixelDataFormat::RGBA;
stage = mStagePool.acquireStage(numDstBytes);
vmaMapMemory(mContext.allocator, stage->memory, &mapped);
DataReshaper::reshape<uint16_t, 3, 4>(mapped, clientData, numSrcBytes);
case PixelDataType::HALF: {
uint8_t* bytes = (uint8_t*) malloc(reshapedSize);
DataReshaper::reshape<uint16_t, 3, 4>(bytes, data.buffer, data.size);
PixelBufferDescriptor pbd(bytes, reshapedSize, reshapedFormat, data.type, freeFunc);
reshapedData = std::move(pbd);
hostData = &reshapedData;
break;
}
case PixelDataType::INT:
case PixelDataType::UINT:
case PixelDataType::FLOAT: {
uint8_t* bytes = (uint8_t*) malloc(reshapedSize);
DataReshaper::reshape<uint32_t, 3, 4>(bytes, data.buffer, data.size);
PixelBufferDescriptor pbd(bytes, reshapedSize, reshapedFormat, data.type, freeFunc);
reshapedData = std::move(pbd);
hostData = &reshapedData;
break;
}
default:
break;
}
}
}
if (numDstBytes == numSrcBytes) {
stage = mStagePool.acquireStage(numDstBytes);
vmaMapMemory(mContext.allocator, stage->memory, &mapped);
memcpy(mapped, clientData, numSrcBytes);
}
// If format conversion is both required and supported, use vkCmdBlitImage. Otherwise, use
// vkCmdCopyBufferToImage.
const VkFormat hostFormat = backend::getVkFormat(hostData->format, hostData->type);
const VkFormat deviceFormat = getVkFormatLinear(mVkFormat);
if (hostFormat != deviceFormat && hostFormat != VK_FORMAT_UNDEFINED) {
updateWithBlitImage(*hostData, width, height, depth, miplevel);
} else {
updateWithCopyBuffer(*hostData, width, height, depth, miplevel);
}
}
void VulkanTexture::updateWithCopyBuffer(const PixelBufferDescriptor& hostData, uint32_t width,
uint32_t height, uint32_t depth, int miplevel) {
void* mapped = nullptr;
VulkanStage const* stage = mStagePool.acquireStage(hostData.size);
vmaMapMemory(mContext.allocator, stage->memory, &mapped);
memcpy(mapped, hostData.buffer, hostData.size);
vmaUnmapMemory(mContext.allocator, stage->memory);
vmaFlushAllocation(mContext.allocator, stage->memory, 0, numDstBytes);
vmaFlushAllocation(mContext.allocator, stage->memory, 0, hostData.size);
const VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
transitionImageLayout(cmdbuffer, mTextureImage, VK_IMAGE_LAYOUT_UNDEFINED,
@@ -286,7 +312,44 @@ void VulkanTexture::update3DImage(const PixelBufferDescriptor& data, uint32_t wi
copyBufferToImage(cmdbuffer, stage->buffer, mTextureImage, width, height, depth,
nullptr, miplevel);
transitionImageLayout(cmdbuffer, mTextureImage,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
transitionImageLayout(cmdbuffer, mTextureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
getTextureLayout(usage), miplevel, 1, 1, mAspect);
}
void VulkanTexture::updateWithBlitImage(const PixelBufferDescriptor& hostData, uint32_t width,
uint32_t height, uint32_t depth, int miplevel) {
void* mapped = nullptr;
VulkanStageImage const* stage = mStagePool.acquireImage(hostData.format, hostData.type,
width, height);
vmaMapMemory(mContext.allocator, stage->memory, &mapped);
memcpy(mapped, hostData.buffer, hostData.size);
vmaUnmapMemory(mContext.allocator, stage->memory);
vmaFlushAllocation(mContext.allocator, stage->memory, 0, hostData.size);
const VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
// TODO: support blit-based format conversion for 3D images and cubemaps.
const int layer = 0;
const VkOffset3D rect[2] { {0, 0, 0}, {int32_t(width), int32_t(height), 1} };
const VkImageBlit blitRegions[1] = {{
.srcSubresource = { mAspect, 0, 0, 1 },
.srcOffsets = { rect[0], rect[1] },
.dstSubresource = { mAspect, uint32_t(miplevel), layer, 1 },
.dstOffsets = { rect[0], rect[1] }
}};
transitionImageLayout(cmdbuffer, stage->image, VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, miplevel, 1, 1, mAspect);
transitionImageLayout(cmdbuffer, mTextureImage, VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, miplevel, 1, 1, mAspect);
vkCmdBlitImage(cmdbuffer, stage->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, mTextureImage,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, blitRegions, VK_FILTER_NEAREST);
transitionImageLayout(cmdbuffer, mTextureImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
getTextureLayout(usage), miplevel, 1, 1, mAspect);
}

View File

@@ -69,6 +69,12 @@ private:
uint32_t width, uint32_t height, uint32_t depth,
FaceOffsets const* faceOffsets, uint32_t miplevel);
void updateWithCopyBuffer(const PixelBufferDescriptor& hostData, uint32_t width,
uint32_t height, uint32_t depth, int miplevel);
void updateWithBlitImage(const PixelBufferDescriptor& hostData, uint32_t width,
uint32_t height, uint32_t depth, int miplevel);
const VkFormat mVkFormat;
const VkComponentMapping mSwizzle;
VkImageViewType mViewType;

View File

@@ -232,6 +232,99 @@ VkFormat getVkFormat(TextureFormat format) {
}
}
// Converts PixelBufferDescriptor format + type pair into a VkFormat.
//
// NOTE: This function only returns formats that support VK_FORMAT_FEATURE_BLIT_SRC_BIT as per
// "Required Format Support" in the Vulkan specification. These are the only formats that can be
// used for format conversion. If the requested format does not support this feature, then
// VK_FORMAT_UNDEFINED is returned.
VkFormat getVkFormat(PixelDataFormat format, PixelDataType type) {
if (type == PixelDataType::USHORT_565) return VK_FORMAT_R5G6B5_UNORM_PACK16;
if (type == PixelDataType::UINT_2_10_10_10_REV) return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
if (type == PixelDataType::UINT_10F_11F_11F_REV) return VK_FORMAT_B10G11R11_UFLOAT_PACK32;
#define CONVERT(FORMAT, TYPE, VK) \
if (PixelDataFormat::FORMAT == format && PixelDataType::TYPE == type) return VK_FORMAT_ ## VK;
CONVERT(R, UBYTE, R8_UNORM);
CONVERT(R, BYTE, R8_SNORM);
CONVERT(R_INTEGER, UBYTE, R8_UINT);
CONVERT(R_INTEGER, BYTE, R8_SINT);
CONVERT(RG, UBYTE, R8G8_UNORM);
CONVERT(RG, BYTE, R8G8_SNORM);
CONVERT(RG_INTEGER, UBYTE, R8G8_UINT);
CONVERT(RG_INTEGER, BYTE, R8G8_SINT);
CONVERT(RGBA, UBYTE, R8G8B8A8_UNORM);
CONVERT(RGBA, BYTE, R8G8B8A8_SNORM);
CONVERT(RGBA_INTEGER, UBYTE, R8G8B8A8_UINT);
CONVERT(RGBA_INTEGER, BYTE, R8G8B8A8_SINT);
CONVERT(R_INTEGER, USHORT, R16_UINT);
CONVERT(R_INTEGER, SHORT, R16_SINT);
CONVERT(R, HALF, R16_SFLOAT);
CONVERT(RG_INTEGER, USHORT, R16G16_UINT);
CONVERT(RG_INTEGER, SHORT, R16G16_SINT);
CONVERT(RG, HALF, R16G16_SFLOAT);
CONVERT(RGBA_INTEGER, USHORT, R16G16B16A16_SINT);
CONVERT(RGBA_INTEGER, SHORT, R16G16B16A16_SINT);
CONVERT(RGBA, HALF, R16G16B16A16_SFLOAT);
CONVERT(R_INTEGER, UINT, R32_UINT);
CONVERT(R_INTEGER, INT, R32_SINT);
CONVERT(R, FLOAT, R32_SFLOAT);
CONVERT(RG_INTEGER, UINT, R32G32_UINT);
CONVERT(RG_INTEGER, INT, R32G32_SINT);
CONVERT(RG, FLOAT, R32G32_SFLOAT);
CONVERT(RGBA_INTEGER, UINT, R32G32B32A32_UINT);
CONVERT(RGBA_INTEGER, INT, R32G32B32A32_SINT);
CONVERT(RGBA, FLOAT, R32G32B32A32_SFLOAT);
#undef CONVERT
return VK_FORMAT_UNDEFINED;
}
// Converts an SRGB Vulkan format identifier into its corresponding canonical UNORM format. If the
// given format is not an SRGB format, it is returned unmodified. This function is useful when
// determining if blit-based conversion is needed, since SRGB is orthogonal to the actual bit layout
// of color components.
VkFormat getVkFormatLinear(VkFormat format) {
switch (format) {
case VK_FORMAT_R8_SRGB: return VK_FORMAT_R8_UNORM;
case VK_FORMAT_R8G8_SRGB: return VK_FORMAT_R8G8_UNORM;
case VK_FORMAT_R8G8B8_SRGB: return VK_FORMAT_R8G8B8_UNORM;
case VK_FORMAT_B8G8R8_SRGB: return VK_FORMAT_B8G8R8_UNORM;
case VK_FORMAT_R8G8B8A8_SRGB: return VK_FORMAT_R8G8B8A8_UNORM;
case VK_FORMAT_B8G8R8A8_SRGB: return VK_FORMAT_B8G8R8A8_UNORM;
case VK_FORMAT_A8B8G8R8_SRGB_PACK32: return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
case VK_FORMAT_BC1_RGB_SRGB_BLOCK: return VK_FORMAT_BC1_RGB_UNORM_BLOCK;
case VK_FORMAT_BC1_RGBA_SRGB_BLOCK: return VK_FORMAT_BC1_RGBA_UNORM_BLOCK;
case VK_FORMAT_BC2_SRGB_BLOCK: return VK_FORMAT_BC2_UNORM_BLOCK;
case VK_FORMAT_BC3_SRGB_BLOCK: return VK_FORMAT_BC3_UNORM_BLOCK;
case VK_FORMAT_BC7_SRGB_BLOCK: return VK_FORMAT_BC7_UNORM_BLOCK;
case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: return VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: return VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK;
case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: return VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK;
case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: return VK_FORMAT_ASTC_4x4_UNORM_BLOCK;
case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: return VK_FORMAT_ASTC_5x4_UNORM_BLOCK;
case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: return VK_FORMAT_ASTC_5x5_UNORM_BLOCK;
case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: return VK_FORMAT_ASTC_6x5_UNORM_BLOCK;
case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: return VK_FORMAT_ASTC_6x6_UNORM_BLOCK;
case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: return VK_FORMAT_ASTC_8x5_UNORM_BLOCK;
case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: return VK_FORMAT_ASTC_8x6_UNORM_BLOCK;
case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: return VK_FORMAT_ASTC_8x8_UNORM_BLOCK;
case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: return VK_FORMAT_ASTC_10x5_UNORM_BLOCK;
case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: return VK_FORMAT_ASTC_10x6_UNORM_BLOCK;
case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: return VK_FORMAT_ASTC_10x8_UNORM_BLOCK;
case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: return VK_FORMAT_ASTC_10x10_UNORM_BLOCK;
case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: return VK_FORMAT_ASTC_12x10_UNORM_BLOCK;
case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: return VK_FORMAT_ASTC_12x12_UNORM_BLOCK;
case VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG: return VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG;
case VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG: return VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG;
case VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG: return VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG;
case VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG: return VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG;
default: return format;
}
return format;
}
// See also FTexture::computeTextureDataSize, which takes a public-facing Texture format rather
// than a driver-level Texture format, and can account for a specified byte alignment.
uint32_t getBytesPerPixel(TextureFormat format) {

View File

@@ -38,6 +38,8 @@ struct VulkanLayoutTransition {
void createSemaphore(VkDevice device, VkSemaphore* semaphore);
VkFormat getVkFormat(ElementType type, bool normalized, bool integer);
VkFormat getVkFormat(TextureFormat format);
VkFormat getVkFormat(PixelDataFormat format, PixelDataType type);
VkFormat getVkFormatLinear(VkFormat format);
uint32_t getBytesPerPixel(TextureFormat format);
VkCompareOp getCompareOp(SamplerCompareFunc func);
VkBlendFactor getBlendFactor(BlendFunction mode);