366 lines
12 KiB
C++
366 lines
12 KiB
C++
|
/*
|
||
|
* Vulkan framebuffer class
|
||
|
*
|
||
|
* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
|
||
|
*
|
||
|
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
||
|
*/
|
||
|
|
||
|
#pragma once
|
||
|
|
||
|
#include <algorithm>
|
||
|
#include <iterator>
|
||
|
#include <vector>
|
||
|
#include "vulkan/vulkan.h"
|
||
|
#include "VulkanDevice.h"
|
||
|
#include "VulkanTools.h"
|
||
|
|
||
|
namespace vks
|
||
|
{
|
||
|
/**
|
||
|
* @brief Encapsulates a single frame buffer attachment
|
||
|
*/
|
||
|
struct FramebufferAttachment
|
||
|
{
|
||
|
VkImage image;
|
||
|
VkDeviceMemory memory;
|
||
|
VkImageView view;
|
||
|
VkFormat format;
|
||
|
VkImageSubresourceRange subresourceRange;
|
||
|
VkAttachmentDescription description;
|
||
|
|
||
|
/**
|
||
|
* @brief Returns true if the attachment has a depth component
|
||
|
*/
|
||
|
bool hasDepth()
|
||
|
{
|
||
|
std::vector<VkFormat> formats =
|
||
|
{
|
||
|
VK_FORMAT_D16_UNORM,
|
||
|
VK_FORMAT_X8_D24_UNORM_PACK32,
|
||
|
VK_FORMAT_D32_SFLOAT,
|
||
|
VK_FORMAT_D16_UNORM_S8_UINT,
|
||
|
VK_FORMAT_D24_UNORM_S8_UINT,
|
||
|
VK_FORMAT_D32_SFLOAT_S8_UINT,
|
||
|
};
|
||
|
return std::find(formats.begin(), formats.end(), format) != std::end(formats);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Returns true if the attachment has a stencil component
|
||
|
*/
|
||
|
bool hasStencil()
|
||
|
{
|
||
|
std::vector<VkFormat> formats =
|
||
|
{
|
||
|
VK_FORMAT_S8_UINT,
|
||
|
VK_FORMAT_D16_UNORM_S8_UINT,
|
||
|
VK_FORMAT_D24_UNORM_S8_UINT,
|
||
|
VK_FORMAT_D32_SFLOAT_S8_UINT,
|
||
|
};
|
||
|
return std::find(formats.begin(), formats.end(), format) != std::end(formats);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Returns true if the attachment is a depth and/or stencil attachment
|
||
|
*/
|
||
|
bool isDepthStencil()
|
||
|
{
|
||
|
return(hasDepth() || hasStencil());
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @brief Describes the attributes of an attachment to be created
|
||
|
*/
|
||
|
struct AttachmentCreateInfo
|
||
|
{
|
||
|
uint32_t width, height;
|
||
|
uint32_t layerCount;
|
||
|
VkFormat format;
|
||
|
VkImageUsageFlags usage;
|
||
|
VkSampleCountFlagBits imageSampleCount = VK_SAMPLE_COUNT_1_BIT;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @brief Encapsulates a complete Vulkan framebuffer with an arbitrary number and combination of attachments
|
||
|
*/
|
||
|
struct Framebuffer
|
||
|
{
|
||
|
private:
|
||
|
vks::VulkanDevice *vulkanDevice;
|
||
|
public:
|
||
|
uint32_t width, height;
|
||
|
VkFramebuffer framebuffer;
|
||
|
VkRenderPass renderPass;
|
||
|
VkSampler sampler;
|
||
|
std::vector<vks::FramebufferAttachment> attachments;
|
||
|
|
||
|
/**
|
||
|
* Default constructor
|
||
|
*
|
||
|
* @param vulkanDevice Pointer to a valid VulkanDevice
|
||
|
*/
|
||
|
Framebuffer(vks::VulkanDevice *vulkanDevice)
|
||
|
{
|
||
|
assert(vulkanDevice);
|
||
|
this->vulkanDevice = vulkanDevice;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Destroy and free Vulkan resources used for the framebuffer and all of its attachments
|
||
|
*/
|
||
|
~Framebuffer()
|
||
|
{
|
||
|
assert(vulkanDevice);
|
||
|
for (auto attachment : attachments)
|
||
|
{
|
||
|
vkDestroyImage(vulkanDevice->logicalDevice, attachment.image, nullptr);
|
||
|
vkDestroyImageView(vulkanDevice->logicalDevice, attachment.view, nullptr);
|
||
|
vkFreeMemory(vulkanDevice->logicalDevice, attachment.memory, nullptr);
|
||
|
}
|
||
|
vkDestroySampler(vulkanDevice->logicalDevice, sampler, nullptr);
|
||
|
vkDestroyRenderPass(vulkanDevice->logicalDevice, renderPass, nullptr);
|
||
|
vkDestroyFramebuffer(vulkanDevice->logicalDevice, framebuffer, nullptr);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add a new attachment described by createinfo to the framebuffer's attachment list
|
||
|
*
|
||
|
* @param createinfo Structure that specifies the framebuffer to be constructed
|
||
|
*
|
||
|
* @return Index of the new attachment
|
||
|
*/
|
||
|
uint32_t addAttachment(vks::AttachmentCreateInfo createinfo)
|
||
|
{
|
||
|
vks::FramebufferAttachment attachment;
|
||
|
|
||
|
attachment.format = createinfo.format;
|
||
|
|
||
|
VkImageAspectFlags aspectMask = VK_FLAGS_NONE;
|
||
|
|
||
|
// Select aspect mask and layout depending on usage
|
||
|
|
||
|
// Color attachment
|
||
|
if (createinfo.usage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)
|
||
|
{
|
||
|
aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||
|
}
|
||
|
|
||
|
// Depth (and/or stencil) attachment
|
||
|
if (createinfo.usage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)
|
||
|
{
|
||
|
if (attachment.hasDepth())
|
||
|
{
|
||
|
aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||
|
}
|
||
|
if (attachment.hasStencil())
|
||
|
{
|
||
|
aspectMask = aspectMask | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
assert(aspectMask > 0);
|
||
|
|
||
|
VkImageCreateInfo image = vks::initializers::imageCreateInfo();
|
||
|
image.imageType = VK_IMAGE_TYPE_2D;
|
||
|
image.format = createinfo.format;
|
||
|
image.extent.width = createinfo.width;
|
||
|
image.extent.height = createinfo.height;
|
||
|
image.extent.depth = 1;
|
||
|
image.mipLevels = 1;
|
||
|
image.arrayLayers = createinfo.layerCount;
|
||
|
image.samples = createinfo.imageSampleCount;
|
||
|
image.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||
|
image.usage = createinfo.usage;
|
||
|
|
||
|
VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
|
||
|
VkMemoryRequirements memReqs;
|
||
|
|
||
|
// Create image for this attachment
|
||
|
VK_CHECK_RESULT(vkCreateImage(vulkanDevice->logicalDevice, &image, nullptr, &attachment.image));
|
||
|
vkGetImageMemoryRequirements(vulkanDevice->logicalDevice, attachment.image, &memReqs);
|
||
|
memAlloc.allocationSize = memReqs.size;
|
||
|
memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||
|
VK_CHECK_RESULT(vkAllocateMemory(vulkanDevice->logicalDevice, &memAlloc, nullptr, &attachment.memory));
|
||
|
VK_CHECK_RESULT(vkBindImageMemory(vulkanDevice->logicalDevice, attachment.image, attachment.memory, 0));
|
||
|
|
||
|
attachment.subresourceRange = {};
|
||
|
attachment.subresourceRange.aspectMask = aspectMask;
|
||
|
attachment.subresourceRange.levelCount = 1;
|
||
|
attachment.subresourceRange.layerCount = createinfo.layerCount;
|
||
|
|
||
|
VkImageViewCreateInfo imageView = vks::initializers::imageViewCreateInfo();
|
||
|
imageView.viewType = (createinfo.layerCount == 1) ? VK_IMAGE_VIEW_TYPE_2D : VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
||
|
imageView.format = createinfo.format;
|
||
|
imageView.subresourceRange = attachment.subresourceRange;
|
||
|
//todo: workaround for depth+stencil attachments
|
||
|
imageView.subresourceRange.aspectMask = (attachment.hasDepth()) ? VK_IMAGE_ASPECT_DEPTH_BIT : aspectMask;
|
||
|
imageView.image = attachment.image;
|
||
|
VK_CHECK_RESULT(vkCreateImageView(vulkanDevice->logicalDevice, &imageView, nullptr, &attachment.view));
|
||
|
|
||
|
// Fill attachment description
|
||
|
attachment.description = {};
|
||
|
attachment.description.samples = createinfo.imageSampleCount;
|
||
|
attachment.description.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||
|
attachment.description.storeOp = (createinfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||
|
attachment.description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||
|
attachment.description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||
|
attachment.description.format = createinfo.format;
|
||
|
attachment.description.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||
|
// Final layout
|
||
|
// If not, final layout depends on attachment type
|
||
|
if (attachment.hasDepth() || attachment.hasStencil())
|
||
|
{
|
||
|
attachment.description.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
attachment.description.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||
|
}
|
||
|
|
||
|
attachments.push_back(attachment);
|
||
|
|
||
|
return static_cast<uint32_t>(attachments.size() - 1);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a default sampler for sampling from any of the framebuffer attachments
|
||
|
* Applications are free to create their own samplers for different use cases
|
||
|
*
|
||
|
* @param magFilter Magnification filter for lookups
|
||
|
* @param minFilter Minification filter for lookups
|
||
|
* @param adressMode Addressing mode for the U,V and W coordinates
|
||
|
*
|
||
|
* @return VkResult for the sampler creation
|
||
|
*/
|
||
|
VkResult createSampler(VkFilter magFilter, VkFilter minFilter, VkSamplerAddressMode adressMode)
|
||
|
{
|
||
|
VkSamplerCreateInfo samplerInfo = vks::initializers::samplerCreateInfo();
|
||
|
samplerInfo.magFilter = magFilter;
|
||
|
samplerInfo.minFilter = minFilter;
|
||
|
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||
|
samplerInfo.addressModeU = adressMode;
|
||
|
samplerInfo.addressModeV = adressMode;
|
||
|
samplerInfo.addressModeW = adressMode;
|
||
|
samplerInfo.mipLodBias = 0.0f;
|
||
|
samplerInfo.maxAnisotropy = 1.0f;
|
||
|
samplerInfo.minLod = 0.0f;
|
||
|
samplerInfo.maxLod = 1.0f;
|
||
|
samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||
|
return vkCreateSampler(vulkanDevice->logicalDevice, &samplerInfo, nullptr, &sampler);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a default render pass setup with one sub pass
|
||
|
*
|
||
|
* @return VK_SUCCESS if all resources have been created successfully
|
||
|
*/
|
||
|
VkResult createRenderPass()
|
||
|
{
|
||
|
std::vector<VkAttachmentDescription> attachmentDescriptions;
|
||
|
for (auto& attachment : attachments)
|
||
|
{
|
||
|
attachmentDescriptions.push_back(attachment.description);
|
||
|
};
|
||
|
|
||
|
// Collect attachment references
|
||
|
std::vector<VkAttachmentReference> colorReferences;
|
||
|
VkAttachmentReference depthReference = {};
|
||
|
bool hasDepth = false;
|
||
|
bool hasColor = false;
|
||
|
|
||
|
uint32_t attachmentIndex = 0;
|
||
|
|
||
|
for (auto& attachment : attachments)
|
||
|
{
|
||
|
if (attachment.isDepthStencil())
|
||
|
{
|
||
|
// Only one depth attachment allowed
|
||
|
assert(!hasDepth);
|
||
|
depthReference.attachment = attachmentIndex;
|
||
|
depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||
|
hasDepth = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
colorReferences.push_back({ attachmentIndex, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
|
||
|
hasColor = true;
|
||
|
}
|
||
|
attachmentIndex++;
|
||
|
};
|
||
|
|
||
|
// Default render pass setup uses only one subpass
|
||
|
VkSubpassDescription subpass = {};
|
||
|
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||
|
if (hasColor)
|
||
|
{
|
||
|
subpass.pColorAttachments = colorReferences.data();
|
||
|
subpass.colorAttachmentCount = static_cast<uint32_t>(colorReferences.size());
|
||
|
}
|
||
|
if (hasDepth)
|
||
|
{
|
||
|
subpass.pDepthStencilAttachment = &depthReference;
|
||
|
}
|
||
|
|
||
|
// Use subpass dependencies for attachment layout transitions
|
||
|
std::array<VkSubpassDependency, 2> dependencies;
|
||
|
|
||
|
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
||
|
dependencies[0].dstSubpass = 0;
|
||
|
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||
|
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||
|
dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||
|
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||
|
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||
|
|
||
|
dependencies[1].srcSubpass = 0;
|
||
|
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
||
|
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||
|
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||
|
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||
|
dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
||
|
dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||
|
|
||
|
// Create render pass
|
||
|
VkRenderPassCreateInfo renderPassInfo = {};
|
||
|
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||
|
renderPassInfo.pAttachments = attachmentDescriptions.data();
|
||
|
renderPassInfo.attachmentCount = static_cast<uint32_t>(attachmentDescriptions.size());
|
||
|
renderPassInfo.subpassCount = 1;
|
||
|
renderPassInfo.pSubpasses = &subpass;
|
||
|
renderPassInfo.dependencyCount = 2;
|
||
|
renderPassInfo.pDependencies = dependencies.data();
|
||
|
VK_CHECK_RESULT(vkCreateRenderPass(vulkanDevice->logicalDevice, &renderPassInfo, nullptr, &renderPass));
|
||
|
|
||
|
std::vector<VkImageView> attachmentViews;
|
||
|
for (auto attachment : attachments)
|
||
|
{
|
||
|
attachmentViews.push_back(attachment.view);
|
||
|
}
|
||
|
|
||
|
// Find. max number of layers across attachments
|
||
|
uint32_t maxLayers = 0;
|
||
|
for (auto attachment : attachments)
|
||
|
{
|
||
|
if (attachment.subresourceRange.layerCount > maxLayers)
|
||
|
{
|
||
|
maxLayers = attachment.subresourceRange.layerCount;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
VkFramebufferCreateInfo framebufferInfo = {};
|
||
|
framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||
|
framebufferInfo.renderPass = renderPass;
|
||
|
framebufferInfo.pAttachments = attachmentViews.data();
|
||
|
framebufferInfo.attachmentCount = static_cast<uint32_t>(attachmentViews.size());
|
||
|
framebufferInfo.width = width;
|
||
|
framebufferInfo.height = height;
|
||
|
framebufferInfo.layers = maxLayers;
|
||
|
VK_CHECK_RESULT(vkCreateFramebuffer(vulkanDevice->logicalDevice, &framebufferInfo, nullptr, &framebuffer));
|
||
|
|
||
|
return VK_SUCCESS;
|
||
|
}
|
||
|
};
|
||
|
}
|