Added SDL_AddVulkanRenderSemaphores() for external synchronization with SDL rendering

This commit is contained in:
Sam Lantinga 2024-03-02 10:03:37 -08:00
parent 504d8c2fc0
commit 48471f7dbd
7 changed files with 129 additions and 5 deletions

View File

@ -407,6 +407,7 @@ extern DECLSPEC int SDLCALL SDL_GetRendererInfo(SDL_Renderer *renderer, SDL_Rend
* family index used for rendering
* - `SDL_PROP_RENDERER_VULKAN_PRESENT_QUEUE_FAMILY_INDEX_NUMBER`: the queue
* family index used for presentation
* - `SDL_PROP_RENDERER_VULKAN_SWAPCHAIN_IMAGE_COUNT_NUMBER`: the number of swapchain images, or potential frames in flight, used by the Vulkan renderer
*
* \param renderer the rendering context
* \returns a valid property ID on success or 0 on failure; call
@ -436,6 +437,7 @@ extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetRendererProperties(SDL_Renderer
#define SDL_PROP_RENDERER_VULKAN_DEVICE_POINTER "SDL.renderer.vulkan.device"
#define SDL_PROP_RENDERER_VULKAN_GRAPHICS_QUEUE_FAMILY_INDEX_NUMBER "SDL.renderer.vulkan.graphics_queue_family_index"
#define SDL_PROP_RENDERER_VULKAN_PRESENT_QUEUE_FAMILY_INDEX_NUMBER "SDL.renderer.vulkan.present_queue_family_index"
#define SDL_PROP_RENDERER_VULKAN_SWAPCHAIN_IMAGE_COUNT_NUMBER "SDL.renderer.vulkan.swapchain_image_count"
/**
* Get the output size in pixels of a rendering context.
@ -2104,6 +2106,25 @@ extern DECLSPEC void *SDLCALL SDL_GetRenderMetalLayer(SDL_Renderer *renderer);
*/
extern DECLSPEC void *SDLCALL SDL_GetRenderMetalCommandEncoder(SDL_Renderer *renderer);
/**
* Add a set of synchronization semaphores for the current frame.
*
* The Vulkan renderer will wait for `wait_semaphore` before submitting rendering commands and signal `signal_semaphore` after rendering commands are complete for this frame.
*
* This should be called each frame that you want semaphore synchronization. The Vulkan renderer may have multiple frames in flight on the GPU, so you should have multiple semaphores that are used for synchronization. Querying SDL_PROP_RENDERER_VULKAN_SWAPCHAIN_IMAGE_COUNT_NUMBER will give you the maximum number of semaphores you'll need.
*
* \param renderer the rendering context
* \param wait_stage_mask the VkPipelineStageFlags for the wait
* \param wait_semaphore a VkSempahore to wait on before rendering the current frame, or 0 if not needed
* \param signal_semaphore a VkSempahore that SDL will signal when rendering for the current frame is complete, or 0 if not needed
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*/
extern DECLSPEC int SDLCALL SDL_AddVulkanRenderSemaphores(SDL_Renderer *renderer, Uint32 wait_stage_mask, Sint64 wait_semaphore, Sint64 signal_semaphore);
/**
* Toggle VSync of the given renderer.
*

View File

@ -973,6 +973,7 @@ SDL3_0.0.0 {
SDL_GetCameraDevicePosition;
SDL_qsort_r;
SDL_bsearch_r;
SDL_AddVulkanRenderSemaphores;
# extra symbols go here (don't modify this line)
local: *;
};

View File

@ -998,3 +998,4 @@
#define SDL_GetCameraDevicePosition SDL_GetCameraDevicePosition_REAL
#define SDL_qsort_r SDL_qsort_r_REAL
#define SDL_bsearch_r SDL_bsearch_r_REAL
#define SDL_AddVulkanRenderSemaphores SDL_AddVulkanRenderSemaphores_REAL

View File

@ -1023,3 +1023,4 @@ SDL_DYNAPI_PROC(int,SDL_GetCameraPermissionState,(SDL_Camera *a),(a),return)
SDL_DYNAPI_PROC(SDL_CameraPosition,SDL_GetCameraDevicePosition,(SDL_CameraDeviceID a),(a),return)
SDL_DYNAPI_PROC(void,SDL_qsort_r,(void *a, size_t b, size_t c, int (SDLCALL *d)(void *, const void *, const void *), void *e),(a,b,c,d,e),)
SDL_DYNAPI_PROC(void*,SDL_bsearch_r,(const void *a, const void *b, size_t c, size_t d, int (SDLCALL *e)(void *, const void *, const void *), void *f),(a,b,c,d,e,f),return)
SDL_DYNAPI_PROC(int,SDL_AddVulkanRenderSemaphores,(SDL_Renderer *a, Uint32 b, Sint64 c, Sint64 d),(a,b,c,d),return)

View File

@ -4565,6 +4565,16 @@ void *SDL_GetRenderMetalCommandEncoder(SDL_Renderer *renderer)
return NULL;
}
int SDL_AddVulkanRenderSemaphores(SDL_Renderer *renderer, Uint32 wait_stage_mask, Sint64 wait_semaphore, Sint64 signal_semaphore)
{
CHECK_RENDERER_MAGIC(renderer, -1);
if (!renderer->AddVulkanRenderSemaphores) {
return SDL_Unsupported();
}
return renderer->AddVulkanRenderSemaphores(renderer, wait_stage_mask, wait_semaphore, signal_semaphore);
}
static SDL_BlendMode SDL_GetShortBlendMode(SDL_BlendMode blendMode)
{
if (blendMode == SDL_BLENDMODE_NONE_FULL) {

View File

@ -216,6 +216,8 @@ struct SDL_Renderer
void *(*GetMetalLayer)(SDL_Renderer *renderer);
void *(*GetMetalCommandEncoder)(SDL_Renderer *renderer);
int (*AddVulkanRenderSemaphores)(SDL_Renderer *renderer, Uint32 wait_stage_mask, Sint64 wait_semaphore, Sint64 signal_semaphore);
/* The current renderer info */
SDL_RendererInfo info;

View File

@ -343,6 +343,14 @@ typedef struct
VkSemaphore *renderingFinishedSemaphores;
uint32_t currentSwapchainImageIndex;
VkPipelineStageFlags *waitDestStageMasks;
VkSemaphore *waitRenderSemaphores;
uint32_t waitRenderSemaphoreCount;
uint32_t waitRenderSemaphoreMax;
VkSemaphore *signalRenderSemaphores;
uint32_t signalRenderSemaphoreCount;
uint32_t signalRenderSemaphoreMax;
/* Cached renderer properties */
VULKAN_TextureData *textureRenderTarget;
SDL_bool cliprectDirty;
@ -454,6 +462,18 @@ static void VULKAN_DestroyAll(SDL_Renderer *renderer)
return;
}
if (rendererData->waitDestStageMasks) {
SDL_free(rendererData->waitDestStageMasks);
rendererData->waitDestStageMasks = NULL;
}
if (rendererData->waitRenderSemaphores) {
SDL_free(rendererData->waitRenderSemaphores);
rendererData->waitRenderSemaphores = NULL;
}
if (rendererData->signalRenderSemaphores) {
SDL_free(rendererData->signalRenderSemaphores);
rendererData->signalRenderSemaphores = NULL;
}
if (rendererData->surfaceFormats != NULL) {
SDL_free(rendererData->surfaceFormats);
rendererData->surfaceFormats = NULL;
@ -1009,6 +1029,12 @@ static VkResult VULKAN_IssueBatch(VULKAN_RenderData *rendererData)
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &rendererData->currentCommandBuffer;
if (rendererData->waitRenderSemaphoreCount > 0) {
submitInfo.waitSemaphoreCount = rendererData->waitRenderSemaphoreCount;
submitInfo.pWaitSemaphores = rendererData->waitRenderSemaphores;
submitInfo.pWaitDstStageMask = rendererData->waitDestStageMasks;
rendererData->waitRenderSemaphoreCount = 0;
}
result = vkQueueSubmit(rendererData->graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
VULKAN_WaitForGPU(rendererData);
@ -2351,6 +2377,9 @@ static VkResult VULKAN_CreateSwapChain(SDL_Renderer *renderer, int w, int h)
VULKAN_AcquireNextSwapchainImage(renderer);
SDL_PropertiesID props = SDL_GetRendererProperties(renderer);
SDL_SetNumberProperty(props, SDL_PROP_RENDERER_VULKAN_SWAPCHAIN_IMAGE_COUNT_NUMBER, rendererData->swapchainImageCount);
return result;
}
@ -3833,6 +3862,48 @@ static SDL_Surface* VULKAN_RenderReadPixels(SDL_Renderer *renderer, const SDL_Re
return output;
}
static int VULKAN_AddVulkanRenderSemaphores(SDL_Renderer *renderer, Uint32 wait_stage_mask, Sint64 wait_semaphore, Sint64 signal_semaphore)
{
VULKAN_RenderData *rendererData = (VULKAN_RenderData *)renderer->driverdata;
if (wait_semaphore) {
if (rendererData->waitRenderSemaphoreCount == rendererData->waitRenderSemaphoreMax) {
/* Allocate an additional one at the end for the normal present wait */
VkPipelineStageFlags *waitDestStageMasks = (VkPipelineStageFlags *)SDL_realloc(rendererData->waitDestStageMasks, (rendererData->waitRenderSemaphoreMax + 2) * sizeof(*waitDestStageMasks));
if (!waitDestStageMasks) {
return -1;
}
rendererData->waitDestStageMasks = waitDestStageMasks;
VkSemaphore *semaphores = (VkSemaphore *)SDL_realloc(rendererData->waitRenderSemaphores, (rendererData->waitRenderSemaphoreMax + 2) * sizeof(*semaphores));
if (!semaphores) {
return -1;
}
rendererData->waitRenderSemaphores = semaphores;
++rendererData->waitRenderSemaphoreMax;
}
rendererData->waitDestStageMasks[rendererData->waitRenderSemaphoreCount] = wait_stage_mask;
rendererData->waitRenderSemaphores[rendererData->waitRenderSemaphoreCount] = (VkSemaphore)wait_semaphore;
++rendererData->waitRenderSemaphoreCount;
}
if (signal_semaphore) {
if (rendererData->signalRenderSemaphoreCount == rendererData->signalRenderSemaphoreMax) {
/* Allocate an additional one at the end for the normal present signal */
VkSemaphore *semaphores = (VkSemaphore *)SDL_realloc(rendererData->signalRenderSemaphores, (rendererData->signalRenderSemaphoreMax + 2) * sizeof(*semaphores));
if (!semaphores) {
return -1;
}
rendererData->signalRenderSemaphores = semaphores;
++rendererData->signalRenderSemaphoreMax;
}
rendererData->signalRenderSemaphores[rendererData->signalRenderSemaphoreCount] = (VkSemaphore)signal_semaphore;
++rendererData->signalRenderSemaphoreCount;
}
return 0;
}
static int VULKAN_RenderPresent(SDL_Renderer *renderer)
{
VULKAN_RenderData *rendererData = (VULKAN_RenderData *)renderer->driverdata;
@ -3863,13 +3934,29 @@ static int VULKAN_RenderPresent(SDL_Renderer *renderer)
VkPipelineStageFlags waitDestStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo submitInfo = { 0 };
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &rendererData->imageAvailableSemaphores[rendererData->currentCommandBufferIndex];
submitInfo.pWaitDstStageMask = &waitDestStageMask;
if (rendererData->waitRenderSemaphoreCount > 0) {
submitInfo.waitSemaphoreCount = rendererData->waitRenderSemaphoreCount + 1;
rendererData->waitRenderSemaphores[rendererData->waitRenderSemaphoreCount] = rendererData->imageAvailableSemaphores[rendererData->currentCommandBufferIndex];
rendererData->waitDestStageMasks[rendererData->waitRenderSemaphoreCount] = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
submitInfo.pWaitSemaphores = rendererData->waitRenderSemaphores;
submitInfo.pWaitDstStageMask = rendererData->waitDestStageMasks;
rendererData->waitRenderSemaphoreCount = 0;
} else {
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &rendererData->imageAvailableSemaphores[rendererData->currentCommandBufferIndex];
submitInfo.pWaitDstStageMask = &waitDestStageMask;
}
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &rendererData->currentCommandBuffer;
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &rendererData->renderingFinishedSemaphores[rendererData->currentCommandBufferIndex];
if (rendererData->signalRenderSemaphoreCount > 0) {
submitInfo.signalSemaphoreCount = rendererData->signalRenderSemaphoreCount + 1;
rendererData->signalRenderSemaphores[rendererData->signalRenderSemaphoreCount] = rendererData->renderingFinishedSemaphores[rendererData->currentCommandBufferIndex];
submitInfo.pSignalSemaphores = rendererData->signalRenderSemaphores;
rendererData->signalRenderSemaphoreCount = 0;
} else {
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &rendererData->renderingFinishedSemaphores[rendererData->currentCommandBufferIndex];
}
result = vkQueueSubmit(rendererData->graphicsQueue, 1, &submitInfo, rendererData->fences[rendererData->currentCommandBufferIndex]);
if (result != VK_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_RENDER, "vkQueueSubmit(): %s\n", SDL_Vulkan_GetResultString(result));
@ -3973,6 +4060,7 @@ SDL_Renderer *VULKAN_CreateRenderer(SDL_Window *window, SDL_PropertiesID create_
renderer->InvalidateCachedState = VULKAN_InvalidateCachedState;
renderer->RunCommandQueue = VULKAN_RunCommandQueue;
renderer->RenderReadPixels = VULKAN_RenderReadPixels;
renderer->AddVulkanRenderSemaphores = VULKAN_AddVulkanRenderSemaphores;
renderer->RenderPresent = VULKAN_RenderPresent;
renderer->DestroyTexture = VULKAN_DestroyTexture;
renderer->DestroyRenderer = VULKAN_DestroyRenderer;