Files
FGL-Engine/src/engine/rendering/SwapChain.cpp
2024-07-10 10:50:16 -04:00

588 lines
22 KiB
C++

#include "SwapChain.hpp"
#include <array>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <limits>
#include <stdexcept>
#include "Attachment.hpp"
#include "RenderPassBuilder.hpp"
#include "Subpass.hpp"
#include "engine/assets/TransferManager.hpp"
namespace fgl::engine
{
SwapChain::SwapChain( const vk::Extent2D extent, PhysicalDevice& phy_device ) :
m_phy_device( phy_device ),
m_swapchain_support( Device::getInstance().getSwapChainSupport() ),
m_surface_format( chooseSwapSurfaceFormat( m_swapchain_support.formats ) ),
m_swap_chain_format( m_surface_format.format ),
m_swap_chain_depth_format( findDepthFormat() ),
m_swap_chain_extent( chooseSwapExtent( m_swapchain_support.capabilities ) ),
m_swapchain_extent( extent ),
m_swapchain( createSwapChain() ),
m_swap_chain_images( createSwapchainImages() ),
m_render_attachments( getSwapChainImageFormat(), findDepthFormat() ),
m_render_pass( createRenderPass() ),
m_swap_chain_buffers( createFramebuffers() ),
m_clear_values( populateAttachmentClearValues() ),
m_gbuffer_descriptor_set( createGBufferDescriptors() ),
m_gbuffer_composite_descriptor_set( createGBufferCompositeDescriptors() )
{
init();
}
SwapChain::SwapChain( const vk::Extent2D extent, std::shared_ptr< SwapChain > previous ) :
m_phy_device( previous->m_phy_device ),
old_swap_chain( previous ),
m_swapchain_support( Device::getInstance().getSwapChainSupport() ),
m_surface_format( chooseSwapSurfaceFormat( m_swapchain_support.formats ) ),
m_swap_chain_format( m_surface_format.format ),
m_swap_chain_depth_format( findDepthFormat() ),
m_swap_chain_extent( chooseSwapExtent( m_swapchain_support.capabilities ) ),
m_swapchain_extent( extent ),
m_swapchain( createSwapChain() ),
m_swap_chain_images( createSwapchainImages() ),
m_render_attachments( getSwapChainImageFormat(), findDepthFormat() ),
m_render_pass( createRenderPass() ),
m_swap_chain_buffers( createFramebuffers() ),
m_clear_values( populateAttachmentClearValues() ),
m_gbuffer_descriptor_set( createGBufferDescriptors() ),
m_gbuffer_composite_descriptor_set( createGBufferCompositeDescriptors() )
{
init();
old_swap_chain.reset();
}
vk::raii::Framebuffer& SwapChain::getFrameBuffer( const FrameIndex frame_idx, const PresentIndex present_idx )
{
assert( present_idx < m_swap_chain_buffers.size() );
assert( frame_idx < SwapChain::MAX_FRAMES_IN_FLIGHT );
return m_swap_chain_buffers[ present_idx ][ frame_idx ];
}
void SwapChain::init()
{
createSyncObjects();
}
std::pair< vk::Result, std::uint32_t > SwapChain::acquireNextImage()
{
ZoneScoped;
std::vector< vk::Fence > fences { in_flight_fences[ current_frame_index ] };
if ( Device::getInstance().device().waitForFences( fences, VK_TRUE, std::numeric_limits< uint64_t >::max() )
!= vk::Result::eSuccess )
throw std::runtime_error( "failed to wait for fences!" );
auto result { m_swapchain.acquireNextImage(
std::numeric_limits< uint64_t >::max(),
imageAvailableSemaphores[ current_frame_index ] // must be a not signaled semaphore
) };
return result;
}
vk::Result SwapChain::
submitCommandBuffers( const vk::raii::CommandBuffer& buffers, const PresentIndex current_present_index )
{
ZoneScoped;
images_in_flight[ current_present_index ] = in_flight_fences[ current_frame_index ];
std::vector< vk::Fence > fences { images_in_flight[ current_present_index ] };
if ( Device::getInstance().device().waitForFences( fences, VK_TRUE, std::numeric_limits< uint64_t >::max() )
!= vk::Result::eSuccess )
throw std::runtime_error( "failed to wait for fences!" );
vk::SubmitInfo submitInfo {};
std::vector< vk::Semaphore > wait_sems { imageAvailableSemaphores[ current_frame_index ],
memory::TransferManager::getInstance().getFinishedSem() };
std::vector< vk::PipelineStageFlags > wait_stages { vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eTopOfPipe };
submitInfo.setWaitSemaphores( wait_sems );
submitInfo.setWaitDstStageMask( wait_stages );
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &( *buffers );
std::vector< vk::Semaphore > signaled_semaphores { renderFinishedSemaphores[ current_frame_index ] };
submitInfo.setSignalSemaphores( signaled_semaphores );
Device::getInstance().device().resetFences( fences );
std::vector< vk::SubmitInfo > submit_infos { submitInfo };
Device::getInstance().graphicsQueue().submit( submitInfo, in_flight_fences[ current_frame_index ] );
vk::PresentInfoKHR presentInfo = {};
presentInfo.setWaitSemaphores( signaled_semaphores );
std::vector< vk::SwapchainKHR > swapchains { m_swapchain };
presentInfo.setSwapchains( swapchains );
std::array< std::uint32_t, 1 > indicies { { current_present_index } };
presentInfo.setImageIndices( indicies );
if ( auto present_result = Device::getInstance().presentQueue().presentKHR( presentInfo );
present_result != vk::Result::eSuccess )
{
if ( present_result == vk::Result::eSuboptimalKHR ) return vk::Result::eSuboptimalKHR;
throw std::runtime_error( "failed to present swap chain image!" );
}
current_frame_index = ( current_frame_index + 1 ) % MAX_FRAMES_IN_FLIGHT;
return vk::Result::eSuccess;
}
vk::raii::SwapchainKHR SwapChain::createSwapChain()
{
ZoneScoped;
const vk::PresentModeKHR present_mode { chooseSwapPresentMode( m_swapchain_support.presentModes ) };
uint32_t image_count = m_swapchain_support.capabilities.minImageCount + 1;
if ( m_swapchain_support.capabilities.maxImageCount > 0
&& image_count > m_swapchain_support.capabilities.maxImageCount )
{
image_count = m_swapchain_support.capabilities.maxImageCount;
}
vk::SwapchainCreateInfoKHR create_info = {};
create_info.surface = Device::getInstance().surface();
create_info.minImageCount = image_count;
create_info.imageFormat = m_surface_format.format;
create_info.imageColorSpace = m_surface_format.colorSpace;
create_info.imageExtent = m_swap_chain_extent;
create_info.imageArrayLayers = 1;
create_info.imageUsage = vk::ImageUsageFlagBits::eColorAttachment;
const std::uint32_t graphics_family { m_phy_device.queueInfo().getIndex( vk::QueueFlagBits::eGraphics ) };
const std::uint32_t present_family { m_phy_device.queueInfo().getPresentIndex() };
const uint32_t queueFamilyIndices[] = { graphics_family, present_family };
if ( graphics_family != present_family )
{
// If the familys are not the same then the swapchain must be shared between
// both queues.
create_info.imageSharingMode = vk::SharingMode::eConcurrent;
create_info.queueFamilyIndexCount = 2;
create_info.pQueueFamilyIndices = queueFamilyIndices;
}
else
{
create_info.imageSharingMode = vk::SharingMode::eExclusive;
create_info.queueFamilyIndexCount = 0; // Optional
create_info.pQueueFamilyIndices = nullptr; // Optional
}
create_info.preTransform = m_swapchain_support.capabilities.currentTransform;
create_info.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque;
create_info.presentMode = present_mode;
create_info.clipped = VK_TRUE;
create_info.oldSwapchain = ( old_swap_chain == nullptr ? VK_NULL_HANDLE : *old_swap_chain->m_swapchain );
return Device::getInstance()->createSwapchainKHR( create_info );
}
std::vector< std::shared_ptr< Image > > SwapChain::createSwapchainImages()
{
std::vector< vk::Image > swap_chain_images { m_swapchain.getImages() };
std::vector< std::shared_ptr< Image > > images {};
images.reserve( swap_chain_images.size() );
for ( std::uint64_t i = 0; i < swap_chain_images.size(); ++i )
{
images.emplace_back( std::make_shared< Image >(
m_swap_chain_extent,
m_surface_format.format,
swap_chain_images[ i ],
vk::ImageUsageFlagBits::eColorAttachment ) );
}
assert( swap_chain_images.size() == images.size() );
return images;
}
vk::raii::RenderPass SwapChain::createRenderPass()
{
ZoneScoped;
RenderPassBuilder render_pass_builder {};
render_pass_builder.registerAttachments(
m_render_attachments.color,
m_render_attachments.depth,
m_gbuffer_attachments.position,
m_gbuffer_attachments.normal,
m_gbuffer_attachments.albedo,
m_gbuffer_attachments.composite );
static_assert( is_attachment< ColoredPresentAttachment > );
static_assert( is_attachment< DepthAttachment > );
Subpass<
vk::PipelineBindPoint::eGraphics,
UsedAttachment< DepthAttachment, vk::ImageLayout::eDepthStencilAttachmentOptimal >,
UsedAttachment< ColorAttachment, vk::ImageLayout::eColorAttachmentOptimal >,
UsedAttachment< ColorAttachment, vk::ImageLayout::eColorAttachmentOptimal >,
UsedAttachment< ColorAttachment, vk::ImageLayout::eColorAttachmentOptimal > >
g_buffer_subpass { 0,
m_render_attachments.depth,
m_gbuffer_attachments.position,
m_gbuffer_attachments.normal,
m_gbuffer_attachments.albedo };
g_buffer_subpass.registerDependencyFromExternal(
vk::AccessFlagBits::eDepthStencilAttachmentWrite,
vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests );
g_buffer_subpass.registerDependencyFromExternal(
vk::AccessFlagBits::eColorAttachmentWrite, vk::PipelineStageFlagBits::eColorAttachmentOutput );
Subpass<
vk::PipelineBindPoint::eGraphics,
UsedAttachment< DepthAttachment, vk::ImageLayout::eDepthStencilAttachmentOptimal >,
UsedAttachment< ColorAttachment, vk::ImageLayout::eColorAttachmentOptimal >,
InputAttachment< ColorAttachment, vk::ImageLayout::eShaderReadOnlyOptimal >,
InputAttachment< ColorAttachment, vk::ImageLayout::eShaderReadOnlyOptimal >,
InputAttachment< ColorAttachment, vk::ImageLayout::eShaderReadOnlyOptimal > >
composite_subpass { 1,
m_render_attachments.depth,
m_gbuffer_attachments.composite,
m_gbuffer_attachments.position,
m_gbuffer_attachments.normal,
m_gbuffer_attachments.albedo };
composite_subpass.registerDependencyFromExternal(
vk::AccessFlagBits::eColorAttachmentWrite, vk::PipelineStageFlagBits::eColorAttachmentOutput );
// For color attachments
composite_subpass.registerDependencyFrom(
g_buffer_subpass,
vk::AccessFlagBits::eColorAttachmentWrite,
vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::AccessFlagBits::eInputAttachmentRead,
vk::PipelineStageFlagBits::eFragmentShader,
vk::DependencyFlagBits::eByRegion );
// For depth attachment
composite_subpass.registerDependencyFrom(
g_buffer_subpass,
vk::AccessFlagBits::eDepthStencilAttachmentWrite,
vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests,
vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite,
vk::PipelineStageFlagBits::eEarlyFragmentTests | vk::PipelineStageFlagBits::eLateFragmentTests,
vk::DependencyFlagBits::eByRegion );
// To prevent the composite buffer from getting obliterated by the gui pass and so we can use it to render to the GUI in certian areas, We need to keep them seperate and the composite image to be unmodified.
Subpass<
vk::PipelineBindPoint::eGraphics,
UsedAttachment< DepthAttachment, vk::ImageLayout::eDepthStencilAttachmentOptimal >,
UsedAttachment< ColoredPresentAttachment, vk::ImageLayout::eColorAttachmentOptimal >,
InputAttachment< ColorAttachment, vk::ImageLayout::eShaderReadOnlyOptimal > >
gui_subpass { 2, m_render_attachments.depth, m_render_attachments.color, m_gbuffer_attachments.composite };
gui_subpass.registerDependencyFromExternal(
vk::AccessFlagBits::eColorAttachmentWrite, vk::PipelineStageFlagBits::eColorAttachmentOutput );
/*
g_buffer_subpass -> composite_subpass -> gui_subpass
*/
gui_subpass.registerDependencyFrom(
composite_subpass,
vk::AccessFlagBits::eColorAttachmentWrite,
vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eInputAttachmentRead,
vk::PipelineStageFlagBits::eFragmentShader,
vk::DependencyFlagBits::eByRegion );
gui_subpass.registerDependencyFrom(
composite_subpass,
vk::AccessFlagBits::eColorAttachmentWrite,
vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eColorAttachmentRead,
vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::DependencyFlagBits::eByRegion );
gui_subpass.registerDependencyToExternal(
vk::AccessFlagBits::eColorAttachmentWrite,
vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::AccessFlagBits::eMemoryRead,
vk::PipelineStageFlagBits::eBottomOfPipe,
vk::DependencyFlagBits::eByRegion );
render_pass_builder.registerSubpass( g_buffer_subpass );
render_pass_builder.registerSubpass( composite_subpass );
render_pass_builder.registerSubpass( gui_subpass );
return render_pass_builder.create();
}
template < is_attachment... Attachments >
std::vector< std::shared_ptr< ImageView > >
fillViewsFromAttachments( uint8_t frame_idx, Attachments&&... attachments )
{
std::vector< std::shared_ptr< ImageView > > views {};
views.resize( sizeof...( Attachments ) );
( ( attachments.fillVec( frame_idx, views ) ), ... );
return views;
}
std::vector< std::vector< vk::raii::Framebuffer > > SwapChain::createFramebuffers()
{
ZoneScoped;
assert( imageCount() > 0 );
m_render_attachments.color.linkImages( m_swap_chain_images );
m_render_attachments.depth.createResources( imageCount(), getSwapChainExtent() );
m_render_attachments.depth.setClear( vk::ClearDepthStencilValue( 1.0f, 0 ) );
// GBuffer resource management
m_gbuffer_attachments.position.createResourceSpread(
SwapChain::MAX_FRAMES_IN_FLIGHT, getSwapChainExtent(), vk::ImageUsageFlagBits::eSampled );
m_gbuffer_attachments.normal.createResourceSpread(
SwapChain::MAX_FRAMES_IN_FLIGHT, getSwapChainExtent(), vk::ImageUsageFlagBits::eSampled );
m_gbuffer_attachments.albedo.createResourceSpread(
SwapChain::MAX_FRAMES_IN_FLIGHT, getSwapChainExtent(), vk::ImageUsageFlagBits::eSampled );
m_gbuffer_attachments.composite.createResourceSpread(
SwapChain::MAX_FRAMES_IN_FLIGHT, getSwapChainExtent(), vk::ImageUsageFlagBits::eSampled );
m_gbuffer_attachments.position.setClear( vk::ClearColorValue( std::array< float, 4 > {
{ 0.0f, 0.0f, 0.0f, 0.0f } } ) );
m_gbuffer_attachments.normal.setClear( vk::ClearColorValue( std::array< float, 4 > {
{ 0.0f, 0.0f, 0.0f, 0.0f } } ) );
m_gbuffer_attachments.albedo.setClear( vk::ClearColorValue( std::array< float, 4 > {
{ 0.0f, 0.0f, 0.0f, 0.0f } } ) );
m_gbuffer_attachments.composite.setClear( vk::ClearColorValue( std::array< float, 4 > {
{ 0.0f, 0.0f, 0.0f, 0.0f } } ) );
;
std::vector< std::vector< vk::raii::Framebuffer > > framebuffers {};
for ( std::uint16_t present_idx = 0; present_idx < imageCount(); ++present_idx )
{
std::vector< vk::raii::Framebuffer > temp {};
for ( FrameIndex frame_idx = 0; frame_idx < SwapChain::MAX_FRAMES_IN_FLIGHT; frame_idx++ )
{
std::vector< vk::ImageView > attachments {};
attachments.emplace_back( *m_render_attachments.color.view( present_idx ) );
attachments.emplace_back( *m_render_attachments.depth.view( present_idx ) );
attachments.emplace_back( *m_gbuffer_attachments.position.view( frame_idx ) );
attachments.emplace_back( *m_gbuffer_attachments.normal.view( frame_idx ) );
attachments.emplace_back( *m_gbuffer_attachments.albedo.view( frame_idx ) );
attachments.emplace_back( *m_gbuffer_attachments.composite.view( frame_idx ) );
//Fill attachments for this frame
const vk::Extent2D swapchain_extent { getSwapChainExtent() };
vk::FramebufferCreateInfo framebufferInfo {};
framebufferInfo.renderPass = m_render_pass;
framebufferInfo.attachmentCount = static_cast< uint32_t >( attachments.size() );
framebufferInfo.pAttachments = attachments.data();
framebufferInfo.width = swapchain_extent.width;
framebufferInfo.height = swapchain_extent.height;
framebufferInfo.layers = 1;
temp.emplace_back( Device::getInstance()->createFramebuffer( framebufferInfo ) );
}
framebuffers.emplace_back( std::move( temp ) );
}
return framebuffers;
}
void SwapChain::createSyncObjects()
{
ZoneScoped;
imageAvailableSemaphores.reserve( MAX_FRAMES_IN_FLIGHT );
renderFinishedSemaphores.reserve( MAX_FRAMES_IN_FLIGHT );
in_flight_fences.reserve( MAX_FRAMES_IN_FLIGHT );
images_in_flight.resize( imageCount(), VK_NULL_HANDLE );
vk::SemaphoreCreateInfo semaphoreInfo {};
vk::FenceCreateInfo fenceInfo {};
fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled;
for ( size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++ )
{
auto& device { Device::getInstance() };
imageAvailableSemaphores.push_back( device->createSemaphore( semaphoreInfo ) );
renderFinishedSemaphores.push_back( device->createSemaphore( semaphoreInfo ) );
in_flight_fences.push_back( device->createFence( fenceInfo ) );
}
}
vk::SurfaceFormatKHR SwapChain::chooseSwapSurfaceFormat( const std::vector< vk::SurfaceFormatKHR >&
available_formats )
{
ZoneScoped;
for ( const auto& format : available_formats )
{
if ( format.format == vk::Format::eB8G8R8A8Srgb && format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear )
{
return format;
}
}
return available_formats[ 0 ];
}
vk::PresentModeKHR SwapChain::chooseSwapPresentMode( const std::vector< vk::PresentModeKHR >&
available_present_modes )
{
ZoneScoped;
for ( const auto& availablePresentMode : available_present_modes )
{
switch ( availablePresentMode )
{
case vk::PresentModeKHR::eImmediate:
std::cout << "Present mode: Immediate" << std::endl;
break;
case vk::PresentModeKHR::eMailbox:
std::cout << "Present mode: Mailbox" << std::endl;
break;
case vk::PresentModeKHR::eFifo:
std::cout << "Present mode: V-Sync" << std::endl;
break;
case vk::PresentModeKHR::eFifoRelaxed:
std::cout << "Present mode: V-Sync: RELAXED" << std::endl;
break;
case vk::PresentModeKHR::eSharedDemandRefresh:
std::cout << "Present mode: Shared Demand Refresh" << std::endl;
break;
case vk::PresentModeKHR::eSharedContinuousRefresh:
std::cout << "Present mode: Shared Continuous Refresh" << std::endl;
break;
}
}
for ( const auto& present_mode_khr : available_present_modes )
{
if ( present_mode_khr == vk::PresentModeKHR::eMailbox )
{
std::cout << "Present mode: Mailbox: ACTIVE" << std::endl;
return present_mode_khr;
}
}
// for (const auto &availablePresentMode : availablePresentModes) {
// if (availablePresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
// std::cout << "Present mode: Immediate" << std::endl;
// return availablePresentMode;
// }
// }
std::cout << "Present mode: V-Sync: ACTIVE" << std::endl;
return vk::PresentModeKHR::eFifo;
}
vk::Extent2D SwapChain::chooseSwapExtent( const vk::SurfaceCapabilitiesKHR& capabilities )
{
ZoneScoped;
if ( capabilities.currentExtent.width != std::numeric_limits< uint32_t >::max() )
{
return capabilities.currentExtent;
}
else
{
vk::Extent2D actualExtent = m_swapchain_extent;
actualExtent.width = std::
max( capabilities.minImageExtent.width,
std::min( capabilities.maxImageExtent.width, actualExtent.width ) );
actualExtent.height = std::
max( capabilities.minImageExtent.height,
std::min( capabilities.maxImageExtent.height, actualExtent.height ) );
return actualExtent;
}
}
std::array< std::unique_ptr< descriptors::DescriptorSet >, SwapChain::MAX_FRAMES_IN_FLIGHT > SwapChain::
createGBufferDescriptors()
{
std::array< std::unique_ptr< descriptors::DescriptorSet >, SwapChain::MAX_FRAMES_IN_FLIGHT > descriptors {};
for ( FrameIndex frame_idx = 0; frame_idx < SwapChain::MAX_FRAMES_IN_FLIGHT; ++frame_idx )
{
auto gbuffer_set { std::make_unique< descriptors::DescriptorSet >( GBufferDescriptorSet::createLayout() ) };
gbuffer_set->setMaxIDX( 2 );
gbuffer_set->bindAttachment(
0, m_gbuffer_attachments.position.view( frame_idx ), vk::ImageLayout::eShaderReadOnlyOptimal );
gbuffer_set->bindAttachment(
1, m_gbuffer_attachments.normal.view( frame_idx ), vk::ImageLayout::eShaderReadOnlyOptimal );
gbuffer_set->bindAttachment(
2, m_gbuffer_attachments.albedo.view( frame_idx ), vk::ImageLayout::eShaderReadOnlyOptimal );
gbuffer_set->update();
descriptors.at( frame_idx ) = std::move( gbuffer_set );
}
return descriptors;
}
std::array< std::unique_ptr< descriptors::DescriptorSet >, SwapChain::MAX_FRAMES_IN_FLIGHT > SwapChain::
createGBufferCompositeDescriptors()
{
std::array< std::unique_ptr< descriptors::DescriptorSet >, SwapChain::MAX_FRAMES_IN_FLIGHT > descriptors {};
for ( FrameIndex frame_idx = 0; frame_idx < SwapChain::MAX_FRAMES_IN_FLIGHT; ++frame_idx )
{
auto composite_set {
std::make_unique< descriptors::DescriptorSet >( GBufferCompositeDescriptorSet::createLayout() )
};
composite_set->setMaxIDX( 0 );
composite_set->bindAttachment(
0, m_gbuffer_attachments.composite.view( frame_idx ), vk::ImageLayout::eShaderReadOnlyOptimal );
composite_set->update();
m_gbuffer_composite_descriptor_set.at( frame_idx ) = std::move( composite_set );
}
return descriptors;
}
vk::Format SwapChain::findDepthFormat()
{
ZoneScoped;
return Device::getInstance().findSupportedFormat(
{ vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint },
vk::ImageTiling::eOptimal,
vk::FormatFeatureFlagBits::eDepthStencilAttachment );
}
} // namespace fgl::engine