2022-09-23 20:44:47 +02:00
# include <mutex>
2022-09-17 01:34:38 +02:00
# include "VulkanFrameData.h"
2022-09-19 18:07:50 +02:00
# include "Common/Log.h"
2022-09-25 23:24:29 +02:00
# include "Common/StringUtils.h"
2022-09-17 01:34:38 +02:00
2023-07-23 19:20:55 +02:00
#if 0 // def _DEBUG
2024-07-14 14:42:59 +02:00
# define VLOG(...) NOTICE_LOG(Log::G3D, __VA_ARGS__)
2023-07-23 19:20:55 +02:00
# else
# define VLOG(...)
# endif
2023-02-05 16:59:23 +01:00
void CachedReadback : : Destroy ( VulkanContext * vulkan ) {
if ( buffer ) {
vulkan - > Delete ( ) . QueueDeleteBufferAllocation ( buffer , allocation ) ;
}
bufferSize = 0 ;
}
2022-09-19 19:27:50 +02:00
void FrameData : : Init ( VulkanContext * vulkan , int index ) {
this - > index = index ;
2022-09-19 18:07:50 +02:00
VkDevice device = vulkan - > GetDevice ( ) ;
2024-01-17 21:34:31 +01:00
VkSemaphoreCreateInfo semaphoreCreateInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO } ;
semaphoreCreateInfo . flags = 0 ;
VkResult res = vkCreateSemaphore ( vulkan - > GetDevice ( ) , & semaphoreCreateInfo , nullptr , & acquireSemaphore ) ;
_dbg_assert_ ( res = = VK_SUCCESS ) ;
res = vkCreateSemaphore ( vulkan - > GetDevice ( ) , & semaphoreCreateInfo , nullptr , & renderingCompleteSemaphore ) ;
_dbg_assert_ ( res = = VK_SUCCESS ) ;
2022-09-19 18:07:50 +02:00
VkCommandPoolCreateInfo cmd_pool_info = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO } ;
cmd_pool_info . queueFamilyIndex = vulkan - > GetGraphicsQueueFamilyIndex ( ) ;
cmd_pool_info . flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT ;
2024-01-17 21:34:31 +01:00
res = vkCreateCommandPool ( device , & cmd_pool_info , nullptr , & cmdPoolInit ) ;
2022-09-19 18:07:50 +02:00
_dbg_assert_ ( res = = VK_SUCCESS ) ;
res = vkCreateCommandPool ( device , & cmd_pool_info , nullptr , & cmdPoolMain ) ;
_dbg_assert_ ( res = = VK_SUCCESS ) ;
VkCommandBufferAllocateInfo cmd_alloc = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO } ;
cmd_alloc . commandPool = cmdPoolInit ;
cmd_alloc . level = VK_COMMAND_BUFFER_LEVEL_PRIMARY ;
cmd_alloc . commandBufferCount = 1 ;
res = vkAllocateCommandBuffers ( device , & cmd_alloc , & initCmd ) ;
_dbg_assert_ ( res = = VK_SUCCESS ) ;
cmd_alloc . commandPool = cmdPoolMain ;
res = vkAllocateCommandBuffers ( device , & cmd_alloc , & mainCmd ) ;
res = vkAllocateCommandBuffers ( device , & cmd_alloc , & presentCmd ) ;
_dbg_assert_ ( res = = VK_SUCCESS ) ;
2022-09-25 23:24:29 +02:00
vulkan - > SetDebugName ( initCmd , VK_OBJECT_TYPE_COMMAND_BUFFER , StringFromFormat ( " initCmd%d " , index ) . c_str ( ) ) ;
vulkan - > SetDebugName ( mainCmd , VK_OBJECT_TYPE_COMMAND_BUFFER , StringFromFormat ( " mainCmd%d " , index ) . c_str ( ) ) ;
vulkan - > SetDebugName ( presentCmd , VK_OBJECT_TYPE_COMMAND_BUFFER , StringFromFormat ( " presentCmd%d " , index ) . c_str ( ) ) ;
2022-09-19 18:07:50 +02:00
// Creating the frame fence with true so they can be instantly waited on the first frame
fence = vulkan - > CreateFence ( true ) ;
2022-09-25 23:24:29 +02:00
vulkan - > SetDebugName ( fence , VK_OBJECT_TYPE_FENCE , StringFromFormat ( " fence%d " , index ) . c_str ( ) ) ;
2022-09-23 20:44:47 +02:00
readyForFence = true ;
2022-09-19 18:07:50 +02:00
VkQueryPoolCreateInfo query_ci { VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO } ;
query_ci . queryCount = MAX_TIMESTAMP_QUERIES ;
query_ci . queryType = VK_QUERY_TYPE_TIMESTAMP ;
res = vkCreateQueryPool ( device , & query_ci , nullptr , & profile . queryPool ) ;
}
void FrameData : : Destroy ( VulkanContext * vulkan ) {
VkDevice device = vulkan - > GetDevice ( ) ;
vkDestroyCommandPool ( device , cmdPoolInit , nullptr ) ;
vkDestroyCommandPool ( device , cmdPoolMain , nullptr ) ;
vkDestroyFence ( device , fence , nullptr ) ;
vkDestroyQueryPool ( device , profile . queryPool , nullptr ) ;
2024-01-17 21:34:31 +01:00
vkDestroySemaphore ( device , acquireSemaphore , nullptr ) ;
vkDestroySemaphore ( device , renderingCompleteSemaphore , nullptr ) ;
2023-02-05 16:59:23 +01:00
readbacks_ . IterateMut ( [ = ] ( const ReadbackKey & key , CachedReadback * value ) {
value - > Destroy ( vulkan ) ;
delete value ;
} ) ;
readbacks_ . Clear ( ) ;
2022-09-19 18:07:50 +02:00
}
2024-01-17 21:34:31 +01:00
void FrameData : : AcquireNextImage ( VulkanContext * vulkan ) {
2022-09-20 16:27:05 +02:00
_dbg_assert_ ( ! hasAcquired ) ;
2022-09-17 01:34:38 +02:00
// Get the index of the next available swapchain image, and a semaphore to block command buffer execution on.
2024-01-17 21:34:31 +01:00
VkResult res = vkAcquireNextImageKHR ( vulkan - > GetDevice ( ) , vulkan - > GetSwapchain ( ) , UINT64_MAX , acquireSemaphore , ( VkFence ) VK_NULL_HANDLE , & curSwapchainImage ) ;
2022-09-20 16:27:05 +02:00
switch ( res ) {
case VK_SUCCESS :
hasAcquired = true ;
break ;
case VK_SUBOPTIMAL_KHR :
hasAcquired = true ;
2022-09-17 01:34:38 +02:00
// Hopefully the resize will happen shortly. Ignore - one frame might look bad or something.
2024-07-14 14:42:59 +02:00
WARN_LOG ( Log : : G3D , " VK_SUBOPTIMAL_KHR returned - ignoring " ) ;
2022-09-20 16:27:05 +02:00
break ;
case VK_ERROR_OUT_OF_DATE_KHR :
2023-01-12 08:58:40 +01:00
case VK_TIMEOUT :
case VK_NOT_READY :
2022-09-20 16:27:05 +02:00
// We do not set hasAcquired here!
2024-07-14 14:42:59 +02:00
WARN_LOG ( Log : : G3D , " %s returned from AcquireNextImage - processing the frame, but not presenting " , VulkanResultToString ( res ) ) ;
2022-09-17 01:34:38 +02:00
skipSwap = true ;
2022-09-20 16:27:05 +02:00
break ;
2023-10-09 18:24:43 +02:00
case VK_ERROR_SURFACE_LOST_KHR :
2024-07-14 14:42:59 +02:00
ERROR_LOG ( Log : : G3D , " %s returned from AcquireNextImage - ignoring, but this better be during shutdown " , VulkanResultToString ( res ) ) ;
2023-10-09 18:24:43 +02:00
skipSwap = true ;
break ;
2022-09-20 16:27:05 +02:00
default :
// Weird, shouldn't get any other values. Maybe lost device?
_assert_msg_ ( false , " vkAcquireNextImageKHR failed! result=%s " , VulkanResultToString ( res ) ) ;
break ;
2022-09-17 01:34:38 +02:00
}
}
2022-09-19 18:07:50 +02:00
2022-09-20 14:49:39 +02:00
VkResult FrameData : : QueuePresent ( VulkanContext * vulkan , FrameDataShared & shared ) {
_dbg_assert_ ( hasAcquired ) ;
hasAcquired = false ;
2022-09-20 16:27:05 +02:00
_dbg_assert_ ( ! skipSwap ) ;
2022-09-19 18:07:50 +02:00
2022-09-20 14:49:39 +02:00
VkSwapchainKHR swapchain = vulkan - > GetSwapchain ( ) ;
VkPresentInfoKHR present = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR } ;
present . swapchainCount = 1 ;
present . pSwapchains = & swapchain ;
present . pImageIndices = & curSwapchainImage ;
2024-01-17 21:34:31 +01:00
present . pWaitSemaphores = & renderingCompleteSemaphore ;
2022-09-20 14:49:39 +02:00
present . waitSemaphoreCount = 1 ;
2022-09-19 18:07:50 +02:00
2023-08-03 11:11:16 +02:00
// Can't move these into the if.
2023-08-01 18:04:44 +02:00
VkPresentIdKHR presentID { VK_STRUCTURE_TYPE_PRESENT_ID_KHR } ;
2023-08-03 11:11:16 +02:00
VkPresentTimesInfoGOOGLE presentGOOGLE { VK_STRUCTURE_TYPE_PRESENT_TIMES_INFO_GOOGLE } ;
2023-08-08 14:00:58 +02:00
uint64_t frameId = this - > frameId ;
2023-08-03 12:59:25 +02:00
VkPresentTimeGOOGLE presentTimeGOOGLE { ( uint32_t ) frameId , 0 } ; // it's ok to truncate this. it'll wrap around and work (if we ever reach 4 billion frames..)
2023-08-30 10:47:20 +02:00
if ( shared . measurePresentTime ) {
if ( vulkan - > Extensions ( ) . KHR_present_id & & vulkan - > GetDeviceFeatures ( ) . enabled . presentId . presentId ) {
presentID . pPresentIds = & frameId ;
presentID . swapchainCount = 1 ;
present . pNext = & presentID ;
} else if ( vulkan - > Extensions ( ) . GOOGLE_display_timing ) {
presentGOOGLE . pTimes = & presentTimeGOOGLE ;
presentGOOGLE . swapchainCount = 1 ;
present . pNext = & presentGOOGLE ;
}
2023-08-01 18:04:44 +02:00
}
2022-09-20 14:49:39 +02:00
return vkQueuePresentKHR ( vulkan - > GetGraphicsQueue ( ) , & present ) ;
2022-09-19 18:07:50 +02:00
}
2022-09-20 14:49:39 +02:00
VkCommandBuffer FrameData : : GetInitCmd ( VulkanContext * vulkan ) {
if ( ! hasInitCommands ) {
VkCommandBufferBeginInfo begin = {
VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO ,
nullptr ,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
} ;
vkResetCommandPool ( vulkan - > GetDevice ( ) , cmdPoolInit , 0 ) ;
VkResult res = vkBeginCommandBuffer ( initCmd , & begin ) ;
if ( res ! = VK_SUCCESS ) {
return VK_NULL_HANDLE ;
}
2022-10-03 12:13:11 +02:00
// Good spot to reset the query pool.
2023-06-14 00:19:58 +02:00
if ( profile . enabled ) {
2022-12-15 10:45:45 +01:00
vkCmdResetQueryPool ( initCmd , profile . queryPool , 0 , MAX_TIMESTAMP_QUERIES ) ;
vkCmdWriteTimestamp ( initCmd , VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT , profile . queryPool , 0 ) ;
}
2022-10-03 12:13:11 +02:00
2022-09-20 14:49:39 +02:00
hasInitCommands = true ;
}
return initCmd ;
}
2024-01-30 11:14:21 +01:00
void FrameData : : Submit ( VulkanContext * vulkan , FrameSubmitType type , FrameDataShared & sharedData ) {
2022-12-03 07:17:12 -08:00
VkCommandBuffer cmdBufs [ 3 ] ;
2022-09-19 18:07:50 +02:00
int numCmdBufs = 0 ;
2022-09-20 14:49:39 +02:00
VkFence fenceToTrigger = VK_NULL_HANDLE ;
if ( hasInitCommands ) {
2023-06-14 00:19:58 +02:00
if ( profile . enabled ) {
2022-09-20 14:49:39 +02:00
// Pre-allocated query ID 1 - end of init cmdbuf.
vkCmdWriteTimestamp ( initCmd , VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT , profile . queryPool , 1 ) ;
}
VkResult res = vkEndCommandBuffer ( initCmd ) ;
cmdBufs [ numCmdBufs + + ] = initCmd ;
_assert_msg_ ( res = = VK_SUCCESS , " vkEndCommandBuffer failed (init)! result=%s " , VulkanResultToString ( res ) ) ;
hasInitCommands = false ;
}
2022-09-20 17:52:35 +02:00
if ( ( hasMainCommands | | hasPresentCommands ) & & type = = FrameSubmitType : : Sync ) {
2023-02-05 00:35:30 +01:00
fenceToTrigger = sharedData . readbackFence ;
2022-09-20 17:52:35 +02:00
}
2022-09-20 14:49:39 +02:00
if ( hasMainCommands ) {
VkResult res = vkEndCommandBuffer ( mainCmd ) ;
_assert_msg_ ( res = = VK_SUCCESS , " vkEndCommandBuffer failed (main)! result=%s " , VulkanResultToString ( res ) ) ;
cmdBufs [ numCmdBufs + + ] = mainCmd ;
hasMainCommands = false ;
}
2024-01-30 11:14:21 +01:00
if ( hasPresentCommands ) {
2024-02-01 11:40:41 +01:00
_dbg_assert_ ( type ! = FrameSubmitType : : Pending ) ;
2022-09-20 14:49:39 +02:00
VkResult res = vkEndCommandBuffer ( presentCmd ) ;
2024-01-30 11:14:21 +01:00
2022-09-20 14:49:39 +02:00
_assert_msg_ ( res = = VK_SUCCESS , " vkEndCommandBuffer failed (present)! result=%s " , VulkanResultToString ( res ) ) ;
2022-09-19 18:07:50 +02:00
cmdBufs [ numCmdBufs + + ] = presentCmd ;
2022-09-20 14:49:39 +02:00
hasPresentCommands = false ;
2024-06-19 10:50:41 +02:00
}
2022-09-20 14:49:39 +02:00
2024-06-19 10:50:41 +02:00
if ( type = = FrameSubmitType : : FinishFrame ) {
_dbg_assert_ ( ! fenceToTrigger ) ;
fenceToTrigger = fence ;
2022-09-20 14:49:39 +02:00
}
if ( ! numCmdBufs & & fenceToTrigger = = VK_NULL_HANDLE ) {
// Nothing to do.
return ;
2022-09-19 18:07:50 +02:00
}
VkSubmitInfo submit_info { VK_STRUCTURE_TYPE_SUBMIT_INFO } ;
VkPipelineStageFlags waitStage [ 1 ] { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT } ;
2024-01-30 11:14:21 +01:00
if ( type = = FrameSubmitType : : FinishFrame & & ! skipSwap ) {
2022-09-20 16:27:05 +02:00
_dbg_assert_ ( hasAcquired ) ;
2022-09-19 18:07:50 +02:00
submit_info . waitSemaphoreCount = 1 ;
2024-01-17 21:34:31 +01:00
submit_info . pWaitSemaphores = & acquireSemaphore ;
2022-09-19 18:07:50 +02:00
submit_info . pWaitDstStageMask = waitStage ;
}
submit_info . commandBufferCount = ( uint32_t ) numCmdBufs ;
submit_info . pCommandBuffers = cmdBufs ;
2024-01-30 11:14:21 +01:00
if ( type = = FrameSubmitType : : FinishFrame & & ! skipSwap ) {
2022-09-19 18:07:50 +02:00
submit_info . signalSemaphoreCount = 1 ;
2024-01-17 21:34:31 +01:00
submit_info . pSignalSemaphores = & renderingCompleteSemaphore ;
2022-09-19 18:07:50 +02:00
}
2022-09-23 20:44:47 +02:00
VkResult res ;
if ( fenceToTrigger = = fence ) {
2023-07-23 19:20:55 +02:00
VLOG ( " Doing queue submit, fencing frame %d " , this - > index ) ;
2022-09-23 20:44:47 +02:00
// The fence is waited on by the main thread, they are not allowed to access it simultaneously.
res = vkQueueSubmit ( vulkan - > GetGraphicsQueue ( ) , 1 , & submit_info , fenceToTrigger ) ;
2023-07-23 19:20:55 +02:00
if ( sharedData . useMultiThreading ) {
std : : lock_guard < std : : mutex > lock ( fenceMutex ) ;
readyForFence = true ;
fenceCondVar . notify_one ( ) ;
}
2022-09-23 20:44:47 +02:00
} else {
2023-07-23 19:20:55 +02:00
VLOG ( " Doing queue submit, fencing something (%p) " , fenceToTrigger ) ;
2022-09-23 20:44:47 +02:00
res = vkQueueSubmit ( vulkan - > GetGraphicsQueue ( ) , 1 , & submit_info , fenceToTrigger ) ;
}
2022-09-19 18:07:50 +02:00
if ( res = = VK_ERROR_DEVICE_LOST ) {
_assert_msg_ ( false , " Lost the Vulkan device in vkQueueSubmit! If this happens again, switch Graphics Backend away from Vulkan " ) ;
} else {
_assert_msg_ ( res = = VK_SUCCESS , " vkQueueSubmit failed (main)! result=%s " , VulkanResultToString ( res ) ) ;
}
2022-09-20 16:27:05 +02:00
if ( type = = FrameSubmitType : : Sync ) {
// Hard stall of the GPU, not ideal, but necessary so the CPU has the contents of the readback.
2023-02-05 00:35:30 +01:00
vkWaitForFences ( vulkan - > GetDevice ( ) , 1 , & sharedData . readbackFence , true , UINT64_MAX ) ;
vkResetFences ( vulkan - > GetDevice ( ) , 1 , & sharedData . readbackFence ) ;
2022-09-23 19:45:50 +02:00
syncDone = true ;
2022-09-20 16:27:05 +02:00
}
2022-09-19 18:07:50 +02:00
}
2023-08-30 10:47:20 +02:00
void FrameDataShared : : Init ( VulkanContext * vulkan , bool useMultiThreading , bool measurePresentTime ) {
2023-02-05 00:35:30 +01:00
// This fence is used for synchronizing readbacks. Does not need preinitialization.
readbackFence = vulkan - > CreateFence ( false ) ;
vulkan - > SetDebugName ( readbackFence , VK_OBJECT_TYPE_FENCE , " readbackFence " ) ;
2023-07-23 19:20:55 +02:00
this - > useMultiThreading = useMultiThreading ;
2023-08-30 10:47:20 +02:00
this - > measurePresentTime = measurePresentTime ;
2022-09-19 18:07:50 +02:00
}
void FrameDataShared : : Destroy ( VulkanContext * vulkan ) {
VkDevice device = vulkan - > GetDevice ( ) ;
2023-02-05 00:35:30 +01:00
vkDestroyFence ( device , readbackFence , nullptr ) ;
2022-09-19 18:07:50 +02:00
}