src/ngfx/porting/vulkan/VKTexture.cpp
Source code
/*
* Copyright 2020 GoPro Inc.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "ngfx/porting/vulkan/VKTexture.h"
#include "ngfx/porting/vulkan/VKBlit.h"
#include "ngfx/porting/vulkan/VKBuffer.h"
#include "ngfx/porting/vulkan/VKCommandBuffer.h"
#include "ngfx/porting/vulkan/VKComputePipeline.h"
#include "ngfx/porting/vulkan/VKGraphicsPipeline.h"
#include "ngfx/porting/vulkan/VKQueue.h"
#include <algorithm>
using namespace ngfx;
void VKTexture::create(VKGraphicsContext *ctx, void *data, uint32_t size,
VkExtent3D extent, uint32_t arrayLayers, VkFormat format,
VkImageUsageFlags imageUsageFlags,
VkImageViewType imageViewType, bool genMipmaps,
VKSamplerCreateInfo *pSamplerCreateInfo,
uint32_t numSamples) {
this->ctx = ctx;
this->w = extent.width;
this->h = extent.height;
this->d = extent.depth;
this->arrayLayers = arrayLayers;
this->numSamples = numSamples;
this->vkFormat = format;
this->imageUsageFlags = imageUsageFlags;
this->genMipmaps = genMipmaps;
this->samplerCreateInfo.reset(pSamplerCreateInfo);
const std::vector<VkFormat> depthFormats = {
VK_FORMAT_D16_UNORM, VK_FORMAT_X8_D24_UNORM_PACK32, VK_FORMAT_D32_SFLOAT};
depthTexture = std::find(depthFormats.begin(), depthFormats.end(), format) !=
depthFormats.end();
this->aspectFlags =
depthTexture ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
VkImageType imageType;
if (imageViewType == VK_IMAGE_VIEW_TYPE_1D ||
imageViewType == VK_IMAGE_VIEW_TYPE_1D_ARRAY)
imageType = VK_IMAGE_TYPE_1D;
else if (imageViewType == VK_IMAGE_VIEW_TYPE_2D ||
imageViewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY ||
imageViewType == VK_IMAGE_VIEW_TYPE_CUBE)
imageType = VK_IMAGE_TYPE_2D;
else
imageType = VK_IMAGE_TYPE_3D;
this->mipLevels =
genMipmaps ? floor(log2(float(glm::min(extent.width, extent.height)))) + 1
: 1;
vkImage.create(&ctx->vkDevice, extent, format, imageUsageFlags, imageType,
mipLevels, arrayLayers, numSamples,
(imageViewType == VK_IMAGE_VIEW_TYPE_CUBE)
? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT
: 0);
vkDefaultImageView = getImageView(imageViewType, mipLevels, arrayLayers);
auto ©CommandBuffer = ctx->vkCopyCommandBuffer;
std::unique_ptr<VKBuffer> stagingBuffer;
if (data) {
stagingBuffer.reset(new VKBuffer());
stagingBuffer->create(ctx, data, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
}
copyCommandBuffer.begin();
uploadFn(copyCommandBuffer.v, data, size, stagingBuffer.get());
if (imageUsageFlags & IMAGE_USAGE_SAMPLED_BIT) {
if (genMipmaps)
samplerCreateInfo->maxLod = mipLevels;
if (!sampler)
initSampler();
if (!samplerDescriptorSet)
initSamplerDescriptorSet(copyCommandBuffer.v);
}
if (imageUsageFlags & IMAGE_USAGE_STORAGE_BIT) {
if (!storageImageDescriptorSet)
initStorageImageDescriptorSet(copyCommandBuffer.v);
}
if (imageUsageFlags & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
vkImage.changeLayout(
copyCommandBuffer.v, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, aspectFlags);
} else if (imageUsageFlags & VK_IMAGE_USAGE_STORAGE_BIT) {
vkImage.changeLayout(copyCommandBuffer.v, VK_IMAGE_LAYOUT_GENERAL,
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, aspectFlags, 0,
mipLevels, 0, this->arrayLayers);
} else if (imageUsageFlags & VK_IMAGE_USAGE_SAMPLED_BIT) {
vkImage.changeLayout(
copyCommandBuffer.v, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
aspectFlags, 0, mipLevels, 0, this->arrayLayers);
}
copyCommandBuffer.end();
vk(ctx->queue)->submit(©CommandBuffer, 0, {}, {}, nullptr);
ctx->queue->waitIdle();
}
void VKTexture::generateMipmaps(CommandBuffer *commandBuffer) {
generateMipmapsFn(vk(commandBuffer)->v);
}
void VKTexture::generateMipmapsFn(VkCommandBuffer cmdBuffer) {
vkImage.changeLayout(cmdBuffer, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_ACCESS_TRANSFER_READ_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, aspectFlags, 0, 1, 0,
arrayLayers);
for (uint32_t j = 1; j < mipLevels; j++) {
vkImage.changeLayout(cmdBuffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, aspectFlags, j, 1, 0,
arrayLayers);
VKBlit::blitImage(
cmdBuffer, vkImage.v, j - 1, vkImage.v, j,
{{0, 0, 0},
{int32_t(glm::max(w >> (j - 1), 1u)),
int32_t(glm::max(h >> (j - 1), 1u)), 1}},
{{0, 0, 0},
{int32_t(glm::max(w >> j, 1u)), int32_t(glm::max(h >> j, 1u)), 1}},
0, arrayLayers, 0, arrayLayers);
vkImage.changeLayout(cmdBuffer, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_ACCESS_TRANSFER_READ_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, aspectFlags, j, 1, 0,
arrayLayers);
}
}
void VKTexture::upload(void *data, uint32_t size, uint32_t x, uint32_t y,
uint32_t z, int32_t w, int32_t h, int32_t d,
int32_t arrayLayers) {
auto ©CommandBuffer = ctx->vkCopyCommandBuffer;
std::unique_ptr<VKBuffer> stagingBuffer;
if (data) {
stagingBuffer.reset(new VKBuffer());
stagingBuffer->create(ctx, data, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
}
copyCommandBuffer.begin();
uploadFn(copyCommandBuffer.v, data, size, stagingBuffer.get(), x, y, z, w, h,
d, arrayLayers);
if (imageUsageFlags & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
vkImage.changeLayout(
copyCommandBuffer.v, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, aspectFlags);
} else if (imageUsageFlags & VK_IMAGE_USAGE_STORAGE_BIT) {
vkImage.changeLayout(copyCommandBuffer.v, VK_IMAGE_LAYOUT_GENERAL,
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, aspectFlags, 0,
mipLevels, 0, this->arrayLayers);
} else if (imageUsageFlags & VK_IMAGE_USAGE_SAMPLED_BIT) {
vkImage.changeLayout(
copyCommandBuffer.v, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
aspectFlags, 0, mipLevels, 0, this->arrayLayers);
}
copyCommandBuffer.end();
vk(ctx->queue)->submit(©CommandBuffer, 0, {}, {}, nullptr);
ctx->queue->waitIdle();
}
void VKTexture::uploadFn(VkCommandBuffer cmdBuffer, void *data, uint32_t size,
VKBuffer *stagingBuffer, uint32_t x, uint32_t y,
uint32_t z, int32_t w, int32_t h, int32_t d,
int32_t arrayLayers) {
if (data) {
if (w == -1)
w = this->w;
if (h == -1)
h = this->h;
if (d == -1)
d = this->d;
if (arrayLayers == -1)
arrayLayers = this->arrayLayers;
vkImage.changeLayout(cmdBuffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_ACCESS_TRANSFER_WRITE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, aspectFlags, 0, 1, 0,
arrayLayers);
std::vector<VkBufferImageCopy> bufferCopyRegions = {
{0,
0,
0,
{aspectFlags, 0, 0, uint32_t(arrayLayers)},
{int32_t(x), int32_t(y), int32_t(z)},
{uint32_t(w), uint32_t(h), uint32_t(d)}}};
VK_TRACE(vkCmdCopyBufferToImage(
cmdBuffer, stagingBuffer->v, vkImage.v, vkImage.imageLayout[0],
uint32_t(bufferCopyRegions.size()), bufferCopyRegions.data()));
}
if (data && mipLevels != 1)
generateMipmapsFn(cmdBuffer);
}
void VKTexture::download(void *data, uint32_t size, uint32_t x, uint32_t y,
uint32_t z, int32_t w, int32_t h, int32_t d,
int32_t arrayLayers) {
auto ©CommandBuffer = ctx->vkCopyCommandBuffer;
std::unique_ptr<VKBuffer> stagingBuffer;
stagingBuffer.reset(new VKBuffer());
stagingBuffer->create(ctx, nullptr, size, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
copyCommandBuffer.begin();
downloadFn(copyCommandBuffer.v, data, size, stagingBuffer.get(), x, y, z, w,
h, d, arrayLayers);
copyCommandBuffer.end();
vk(ctx->queue)->submit(©CommandBuffer, 0, {}, {}, nullptr);
ctx->queue->waitIdle();
stagingBuffer->download(data, size);
}
void VKTexture::downloadFn(VkCommandBuffer cmdBuffer, void *data, uint32_t size,
VKBuffer *stagingBuffer, uint32_t x, uint32_t y,
uint32_t z, int32_t w, int32_t h, int32_t d,
int32_t arrayLayers) {
if (w == -1)
w = this->w;
if (h == -1)
h = this->h;
if (d == -1)
d = this->d;
if (arrayLayers == -1)
arrayLayers = this->arrayLayers;
vkImage.changeLayout(cmdBuffer, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_ACCESS_TRANSFER_READ_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT);
std::vector<VkBufferImageCopy> bufferCopyRegions = {
{0,
0,
0,
{aspectFlags, 0, 0, 1},
{int32_t(x), int32_t(y), int32_t(z)},
{uint32_t(w), uint32_t(h), uint32_t(d)}}};
VK_TRACE(vkCmdCopyImageToBuffer(
cmdBuffer, vkImage.v, vkImage.imageLayout[0], stagingBuffer->v,
uint32_t(bufferCopyRegions.size()), bufferCopyRegions.data()));
}
VKTexture::~VKTexture() {
if (sampler)
VK_TRACE(vkDestroySampler(ctx->vkDevice.v, sampler, nullptr));
}
void VKTexture::initSampler() {
VkResult vkResult;
V(vkCreateSampler(ctx->vkDevice.v, samplerCreateInfo.get(), nullptr,
&sampler));
}
void VKTexture::initSamplerDescriptorSet(VkCommandBuffer cmdBuffer) {
VkResult vkResult;
vkImage.changeLayout(cmdBuffer, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, aspectFlags, 0,
mipLevels, 0, this->arrayLayers);
VkDescriptorPool descriptorPool = ctx->vkDescriptorPool;
VkDescriptorSetLayout descriptorSetLayout =
ctx->vkDescriptorSetLayoutCache.get(
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
VkDescriptorSetAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, nullptr, descriptorPool,
1, &descriptorSetLayout};
V(vkAllocateDescriptorSets(ctx->vkDevice.v, &allocInfo,
&samplerDescriptorSet));
VkDescriptorImageInfo descriptorImageInfo = {sampler, vkDefaultImageView->v,
vkImage.imageLayout[0]};
VkWriteDescriptorSet writeDescriptorSet = {
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
samplerDescriptorSet,
0,
0,
1,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
&descriptorImageInfo,
nullptr,
nullptr};
VK_TRACE(vkUpdateDescriptorSets(ctx->vkDevice.v, 1, &writeDescriptorSet, 0,
nullptr));
}
void VKTexture::initStorageImageDescriptorSet(VkCommandBuffer cmdBuffer) {
VkResult vkResult;
vkImage.changeLayout(cmdBuffer, VK_IMAGE_LAYOUT_GENERAL,
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, aspectFlags, 0,
mipLevels, 0, this->arrayLayers);
VkDescriptorPool descriptorPool = ctx->vkDescriptorPool;
VkDescriptorSetLayout descriptorSetLayout =
ctx->vkDescriptorSetLayoutCache.get(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE);
VkDescriptorSetAllocateInfo allocInfo = {
VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, nullptr, descriptorPool,
1, &descriptorSetLayout};
V(vkAllocateDescriptorSets(ctx->vkDevice.v, &allocInfo,
&storageImageDescriptorSet));
VkDescriptorImageInfo descriptorImageInfo = {sampler, vkDefaultImageView->v,
vkImage.imageLayout[0]};
VkWriteDescriptorSet writeDescriptorSet = {
VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
nullptr,
storageImageDescriptorSet,
0,
0,
1,
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
&descriptorImageInfo,
nullptr,
nullptr};
VK_TRACE(vkUpdateDescriptorSets(ctx->vkDevice.v, 1, &writeDescriptorSet, 0,
nullptr));
}
void VKTexture::changeLayout(CommandBuffer *commandBuffer,
ImageLayout imageLayout) {
if (imageLayout == IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) {
vkImage.changeLayout(vk(commandBuffer)->v, VkImageLayout(imageLayout),
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
aspectFlags, 0, mipLevels, 0, arrayLayers);
} else if (imageLayout == IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
vkImage.changeLayout(vk(commandBuffer)->v, VkImageLayout(imageLayout),
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, aspectFlags, 0,
mipLevels, 0, arrayLayers);
} else if (imageLayout == IMAGE_LAYOUT_GENERAL) {
vkImage.changeLayout(vk(commandBuffer)->v, VkImageLayout(imageLayout),
VK_ACCESS_SHADER_READ_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, aspectFlags, 0,
mipLevels, 0, arrayLayers);
} else if (imageLayout == IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
vkImage.changeLayout(vk(commandBuffer)->v, VkImageLayout(imageLayout),
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
aspectFlags, 0, mipLevels, 0, arrayLayers);
}
}
VKImageView *VKTexture::getImageView(VkImageViewType imageViewType,
uint32_t mipLevels, uint32_t arrayLayers,
uint32_t baseMipLevel,
uint32_t baseArrayLayer) {
VKImageViewCreateInfo imageViewCreateInfo(vkImage.v, imageViewType, vkFormat,
aspectFlags, mipLevels, arrayLayers,
baseMipLevel, baseArrayLayer);
for (auto &imageView : vkImageViewCache) {
if (imageView->createInfo == imageViewCreateInfo) {
return imageView.get();
}
}
auto vkImageView = std::make_unique<VKImageView>();
vkImageView->create(ctx->vkDevice.v, imageViewCreateInfo);
auto result = vkImageView.get();
vkImageViewCache.push_back(std::move(vkImageView));
return result;
}
Texture *Texture::create(GraphicsContext *ctx, Graphics *graphics, void *data,
PixelFormat format, uint32_t size, uint32_t w,
uint32_t h, uint32_t d, uint32_t arrayLayers,
ImageUsageFlags imageUsageFlags,
TextureType textureType, bool genMipmaps,
FilterMode minFilter, FilterMode magFilter,
FilterMode mipFilter, uint32_t numSamples) {
VKTexture *vkTexture = new VKTexture();
VKSamplerCreateInfo *samplerCreateInfo = nullptr;
if (imageUsageFlags & IMAGE_USAGE_SAMPLED_BIT) {
samplerCreateInfo = new VKSamplerCreateInfo;
samplerCreateInfo->minFilter = VkFilter(minFilter);
samplerCreateInfo->magFilter = VkFilter(magFilter);
samplerCreateInfo->mipmapMode = (mipFilter == FILTER_LINEAR)
? VK_SAMPLER_MIPMAP_MODE_LINEAR
: VK_SAMPLER_MIPMAP_MODE_NEAREST;
}
vkTexture->create(vk(ctx), data, size, {w, h, d}, arrayLayers,
VkFormat(format), imageUsageFlags,
VkImageViewType(textureType), genMipmaps, samplerCreateInfo,
numSamples);
vkTexture->format = format;
return vkTexture;
}
Updated on 3 April 2021 at 20:21:52 PDT