From 1a0beea4028cc5d308539dfd07d25099f3055b32 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Thu, 29 Sep 2022 21:11:25 +0000 Subject: [PATCH] Backend selection + swapchain API revision (#23) - The application must now call `Refresh_SetBackend` before creating the device - `Refresh_SetBackend` can set a preferred backend, but will fall back if it is not available - Device creation no longer takes presentation parameters - Windows must now be explicitly claimed by the device - Windows can be unclaimed by the device - Windows can have their swapchain present mode changed after creation Reviewed-on: https://gitea.moonside.games/MoonsideGames/Refresh/pulls/23 --- include/Refresh.h | 73 ++++++++--- src/Refresh.c | 85 ++++++++++++- src/Refresh_Driver.h | 22 +++- src/Refresh_Driver_Vulkan.c | 243 +++++++++++++++++++++++------------- 4 files changed, 315 insertions(+), 108 deletions(-) diff --git a/include/Refresh.h b/include/Refresh.h index 509ad21..1248072 100644 --- a/include/Refresh.h +++ b/include/Refresh.h @@ -332,6 +332,14 @@ typedef enum Refresh_BorderColor REFRESH_BORDERCOLOR_INT_OPAQUE_WHITE } Refresh_BorderColor; +typedef enum Refresh_Backend +{ + REFRESH_BACKEND_DONTCARE, + REFRESH_BACKEND_VULKAN, + REFRESH_BACKEND_PS5, + REFRESH_BACKEND_INVALID +} Refresh_Backend; + /* Structures */ typedef struct Refresh_DepthStencilValue @@ -375,12 +383,6 @@ typedef struct Refresh_TextureSlice uint32_t level; } Refresh_TextureSlice; -typedef struct Refresh_PresentationParameters -{ - void* deviceWindowHandle; - Refresh_PresentMode presentMode; -} Refresh_PresentationParameters; - /* State structures */ typedef struct Refresh_SamplerStateCreateInfo @@ -594,15 +596,29 @@ REFRESHAPI void Refresh_HookLogFunctions( Refresh_LogFunc error ); +/* Backend selection */ + +/* Select the graphics API backend that Refresh should use. + * + * Note that Refresh is not required to select your preferred backend + * if it detects an incompatibility. + * + * Returns the backend that will actually be used, and fills in a window flag bitmask. + * This bitmask should be used to create all windows that the device claims. + * + * preferredBackend: The preferred backend that Refresh should select. + * flags: A pointer to a bitflag value that will be filled in with required SDL_WindowFlags masks. + */ +REFRESHAPI Refresh_Backend Refresh_SelectBackend(Refresh_Backend preferredBackend, uint32_t *flags); + /* Device */ /* Create a rendering context for use on the calling thread. + * You MUST have called Refresh_SelectDriver prior to calling this function. * - * presentationParameters: A window handle and presentation mode. * debugMode: Enable debug mode properties. */ REFRESHAPI Refresh_Device* Refresh_CreateDevice( - Refresh_PresentationParameters *presentationParameters, uint8_t debugMode ); @@ -1115,6 +1131,41 @@ REFRESHAPI void Refresh_BindComputeTextures( /* Submission/Presentation */ +/* Claims a window, creating a swapchain structure for it. + * This function MUST be called before any swapchain functions + * are called using the window. + * + * Returns 0 on swapchain creation failure. + */ +REFRESHAPI uint8_t Refresh_ClaimWindow( + Refresh_Device *device, + void *windowHandle, + Refresh_PresentMode presentMode +); + +/* Unclaims a window, destroying the swapchain structure for it. + * It is good practice to call this when a window is closed to + * prevent memory bloat, but windows are automatically unclaimed + * by DestroyDevice. + */ +REFRESHAPI void Refresh_UnclaimWindow( + Refresh_Device *device, + void *windowHandle +); + +/* Changes the present mode of the swapchain for the given window. */ +REFRESHAPI void Refresh_SetSwapchainPresentMode( + Refresh_Device *device, + void *windowHandle, + Refresh_PresentMode presentMode +); + +/* Returns the format of the swapchain for the given window. */ +REFRESHAPI Refresh_TextureFormat Refresh_GetSwapchainFormat( + Refresh_Device *device, + void *windowHandle +); + /* Returns an allocated Refresh_CommandBuffer* object. * This command buffer is managed by the implementation and * should NOT be freed by the user. @@ -1154,12 +1205,6 @@ REFRESHAPI Refresh_Texture* Refresh_AcquireSwapchainTexture( uint32_t *pHeight ); -/* Returns the format of the swapchain for the given window. */ -REFRESHAPI Refresh_TextureFormat Refresh_GetSwapchainFormat( - Refresh_Device *device, - void *windowHandle -); - /* Submits all of the enqueued commands. */ REFRESHAPI void Refresh_Submit( Refresh_Device* device, diff --git a/src/Refresh.c b/src/Refresh.c index ee24022..40eb7f3 100644 --- a/src/Refresh.c +++ b/src/Refresh.c @@ -33,7 +33,8 @@ /* Drivers */ -static const Refresh_Driver *drivers[] = { +static const Refresh_Driver *backends[] = { + NULL, #ifdef REFRESH_DRIVER_VULKAN &VulkanDriver, #endif @@ -129,19 +130,54 @@ uint32_t Refresh_LinkedVersion(void) /* Driver Functions */ -static int32_t selectedDriver = 0; +static Refresh_Backend selectedBackend = REFRESH_BACKEND_INVALID; + +Refresh_Backend Refresh_SelectBackend(Refresh_Backend preferredBackend, uint32_t *flags) +{ + uint32_t backendIndex, i; + + if (preferredBackend != REFRESH_BACKEND_DONTCARE) + { + /* Try to force it! */ + backendIndex = preferredBackend; + + if (backends[backendIndex]->PrepareDriver(flags)) + { + selectedBackend = preferredBackend; + return selectedBackend; + } + } + + /* Iterate until we find an appropriate backend. */ + + for (i = 1; backends[i] != NULL; i += 1) + { + if (i != preferredBackend && backends[i]->PrepareDriver(flags)) + { + selectedBackend = i; + return i; + } + } + + if (backends[i] == NULL) + { + Refresh_LogError("No supported Refresh backend found!"); + } + + selectedBackend = REFRESH_BACKEND_INVALID; + return REFRESH_BACKEND_INVALID; +} Refresh_Device* Refresh_CreateDevice( - Refresh_PresentationParameters *presentationParameters, uint8_t debugMode ) { - if (selectedDriver < 0) + if (selectedBackend == REFRESH_BACKEND_INVALID) { + Refresh_LogError("Invalid backend selection. Did you call Refresh_SelectBackend?"); return NULL; } - return drivers[selectedDriver]->CreateDevice( - presentationParameters, + return backends[selectedBackend]->CreateDevice( debugMode ); } @@ -722,6 +758,30 @@ void Refresh_BindComputeTextures( ); } +uint8_t Refresh_ClaimWindow( + Refresh_Device *device, + void *windowHandle, + Refresh_PresentMode presentMode +) { + if (device == NULL) { return 0; } + return device->ClaimWindow( + device->driverData, + windowHandle, + presentMode + ); +} + +void Refresh_UnclaimWindow( + Refresh_Device *device, + void *windowHandle +) { + NULL_RETURN(device); + device->UnclaimWindow( + device->driverData, + windowHandle + ); +} + Refresh_CommandBuffer* Refresh_AcquireCommandBuffer( Refresh_Device *device, uint8_t fixed @@ -761,6 +821,19 @@ Refresh_TextureFormat Refresh_GetSwapchainFormat( ); } +void Refresh_SetSwapchainPresentMode( + Refresh_Device *device, + void *windowHandle, + Refresh_PresentMode presentMode +) { + NULL_RETURN(device); + device->SetSwapchainPresentMode( + device->driverData, + windowHandle, + presentMode + ); +} + void Refresh_Submit( Refresh_Device *device, uint32_t commandBufferCount, diff --git a/src/Refresh_Driver.h b/src/Refresh_Driver.h index 7b5a1c6..e3c83c3 100644 --- a/src/Refresh_Driver.h +++ b/src/Refresh_Driver.h @@ -459,6 +459,17 @@ struct Refresh_Device Refresh_Texture **pTextures ); + uint8_t (*ClaimWindow)( + Refresh_Renderer *driverData, + void *windowHandle, + Refresh_PresentMode presentMode + ); + + void(*UnclaimWindow)( + Refresh_Renderer *driverData, + void *windowHandle + ); + Refresh_CommandBuffer* (*AcquireCommandBuffer)( Refresh_Renderer *driverData, uint8_t fixed @@ -477,6 +488,12 @@ struct Refresh_Device void *windowHandle ); + void (*SetSwapchainPresentMode)( + Refresh_Renderer *driverData, + void *windowHandle, + Refresh_PresentMode presentMode + ); + void(*Submit)( Refresh_Renderer *driverData, uint32_t commandBufferCount, @@ -533,17 +550,20 @@ struct Refresh_Device ASSIGN_DRIVER_FUNC(BindComputePipeline, name) \ ASSIGN_DRIVER_FUNC(BindComputeBuffers, name) \ ASSIGN_DRIVER_FUNC(BindComputeTextures, name) \ + ASSIGN_DRIVER_FUNC(ClaimWindow, name) \ + ASSIGN_DRIVER_FUNC(UnclaimWindow, name) \ ASSIGN_DRIVER_FUNC(AcquireCommandBuffer, name) \ ASSIGN_DRIVER_FUNC(AcquireSwapchainTexture, name) \ ASSIGN_DRIVER_FUNC(GetSwapchainFormat, name) \ + ASSIGN_DRIVER_FUNC(SetSwapchainPresentMode, name) \ ASSIGN_DRIVER_FUNC(Submit, name) \ ASSIGN_DRIVER_FUNC(Wait, name) typedef struct Refresh_Driver { const char *Name; + uint8_t (*PrepareDriver)(uint32_t *flags); Refresh_Device* (*CreateDevice)( - Refresh_PresentationParameters *presentationParameters, uint8_t debugMode ); } Refresh_Driver; diff --git a/src/Refresh_Driver_Vulkan.c b/src/Refresh_Driver_Vulkan.c index 8c5cc64..d53e1be 100644 --- a/src/Refresh_Driver_Vulkan.c +++ b/src/Refresh_Driver_Vulkan.c @@ -1691,8 +1691,6 @@ typedef struct VulkanRenderer VkQueue computeQueue; VkQueue transferQueue; - Refresh_PresentMode presentMode; - VulkanCommandBuffer **submittedCommandBuffers; uint32_t submittedCommandBufferCount; uint32_t submittedCommandBufferCapacity; @@ -4252,7 +4250,8 @@ static uint8_t VULKAN_INTERNAL_ChooseSwapPresentMode( static uint8_t VULKAN_INTERNAL_CreateSwapchain( VulkanRenderer *renderer, - void *windowHandle + void *windowHandle, + Refresh_PresentMode presentMode ) { VkResult vulkanResult; VulkanSwapchainData *swapchainData; @@ -4351,7 +4350,7 @@ static uint8_t VULKAN_INTERNAL_CreateSwapchain( } if (!VULKAN_INTERNAL_ChooseSwapPresentMode( - renderer->presentMode, + presentMode, swapchainSupportDetails.presentModes, swapchainSupportDetails.presentModesLength, &swapchainData->presentMode @@ -4642,11 +4641,12 @@ static uint8_t VULKAN_INTERNAL_CreateSwapchain( static void VULKAN_INTERNAL_RecreateSwapchain( VulkanRenderer* renderer, - void *windowHandle + void *windowHandle, + Refresh_PresentMode presentMode ) { VULKAN_Wait((Refresh_Renderer*) renderer); VULKAN_INTERNAL_DestroySwapchain(renderer, windowHandle); - VULKAN_INTERNAL_CreateSwapchain(renderer, windowHandle); + VULKAN_INTERNAL_CreateSwapchain(renderer, windowHandle, presentMode); } /* Command Buffers */ @@ -9265,22 +9265,39 @@ static Refresh_CommandBuffer* VULKAN_AcquireCommandBuffer( } static VulkanSwapchainData* VULKAN_INTERNAL_FetchSwapchainData( - VulkanRenderer *renderer, void *windowHandle ) { - VulkanSwapchainData *swapchainData = NULL; + return (VulkanSwapchainData*) SDL_GetWindowData(windowHandle, WINDOW_SWAPCHAIN_DATA); +} - swapchainData = (VulkanSwapchainData*) SDL_GetWindowData(windowHandle, WINDOW_SWAPCHAIN_DATA); +static uint8_t VULKAN_ClaimWindow( + Refresh_Renderer *driverData, + void *windowHandle, + Refresh_PresentMode presentMode +) { + VulkanRenderer *renderer = (VulkanRenderer*) driverData; + VulkanSwapchainData *swapchainData = VULKAN_INTERNAL_FetchSwapchainData(windowHandle); if (swapchainData == NULL) { - if (VULKAN_INTERNAL_CreateSwapchain(renderer, windowHandle)) - { - swapchainData = (VulkanSwapchainData*) SDL_GetWindowData(windowHandle, WINDOW_SWAPCHAIN_DATA); - } + return VULKAN_INTERNAL_CreateSwapchain(renderer, windowHandle, presentMode); } + else + { + Refresh_LogWarn("Window already claimed!"); + return 0; + } +} - return swapchainData; +static void VULKAN_UnclaimWindow( + Refresh_Renderer *driverData, + void *windowHandle +) { + VULKAN_Wait(driverData); + VULKAN_INTERNAL_DestroySwapchain( + (VulkanRenderer*) driverData, + windowHandle + ); } static Refresh_Texture* VULKAN_AcquireSwapchainTexture( @@ -9298,10 +9315,11 @@ static Refresh_Texture* VULKAN_AcquireSwapchainTexture( VulkanTexture *swapchainTexture = NULL; VulkanPresentData *presentData; - swapchainData = VULKAN_INTERNAL_FetchSwapchainData(renderer, windowHandle); + swapchainData = VULKAN_INTERNAL_FetchSwapchainData(windowHandle); if (swapchainData == NULL) { + Refresh_LogError("Cannot acquire swapchain texture, window has not been claimed!"); return NULL; } @@ -9317,12 +9335,15 @@ static Refresh_Texture* VULKAN_AcquireSwapchainTexture( /* Swapchain is invalid, let's try to recreate */ if (acquireResult != VK_SUCCESS && acquireResult != VK_SUBOPTIMAL_KHR) { - VULKAN_INTERNAL_RecreateSwapchain(renderer, windowHandle); + swapchainData = VULKAN_INTERNAL_FetchSwapchainData(windowHandle); - swapchainData = VULKAN_INTERNAL_FetchSwapchainData(renderer, windowHandle); + VULKAN_INTERNAL_RecreateSwapchain(renderer, windowHandle, swapchainData->presentMode); + + swapchainData = VULKAN_INTERNAL_FetchSwapchainData(windowHandle); if (swapchainData == NULL) { + Refresh_LogWarn("Failed to recreate swapchain!"); return NULL; } @@ -9433,6 +9454,26 @@ static Refresh_TextureFormat VULKAN_GetSwapchainFormat( } } +static void VULKAN_SetSwapchainPresentMode( + Refresh_Renderer *driverData, + void *windowHandle, + Refresh_PresentMode presentMode +) { + VulkanSwapchainData *swapchainData = (VulkanSwapchainData*) SDL_GetWindowData(windowHandle, WINDOW_SWAPCHAIN_DATA); + + if (swapchainData == NULL) + { + Refresh_LogWarn("Cannot set present mode, window has not been claimed!"); + return; + } + + VULKAN_INTERNAL_RecreateSwapchain( + (VulkanRenderer *)driverData, + windowHandle, + presentMode + ); +} + /* Submission structure */ static void VULKAN_INTERNAL_PerformPendingDestroys( @@ -9842,7 +9883,11 @@ static void VULKAN_Submit( if (presentResult != VK_SUCCESS) { - VULKAN_INTERNAL_RecreateSwapchain(renderer, presentData->swapchainData->windowHandle); + VULKAN_INTERNAL_RecreateSwapchain( + renderer, + presentData->swapchainData->windowHandle, + presentData->swapchainData->presentMode + ); } } } @@ -10285,9 +10330,7 @@ static void VULKAN_INTERNAL_GetPhysicalDeviceProperties( static uint8_t VULKAN_INTERNAL_DeterminePhysicalDevice( VulkanRenderer *renderer, - VkSurfaceKHR surface, - const char **deviceExtensionNames, - uint32_t deviceExtensionCount + VkSurfaceKHR surface ) { VkResult vulkanResult; VkPhysicalDevice *physicalDevices; @@ -10513,9 +10556,8 @@ static uint8_t VULKAN_INTERNAL_CreateLogicalDevice( return 1; } -static void VULKAN_INTERNAL_LoadEntryPoints( - VulkanRenderer *renderer -) { +static void VULKAN_INTERNAL_LoadEntryPoints() +{ /* Load Vulkan entry points */ if (SDL_Vulkan_LoadLibrary(NULL) < 0) { @@ -10546,12 +10588,96 @@ static void VULKAN_INTERNAL_LoadEntryPoints( #include "Refresh_Driver_Vulkan_vkfuncs.h" } +static uint8_t VULKAN_INTERNAL_PrepareVulkan( + VulkanRenderer *renderer +) { + SDL_Window *dummyWindowHandle; + VkSurfaceKHR surface; + + VULKAN_INTERNAL_LoadEntryPoints(); + + dummyWindowHandle = SDL_CreateWindow( + "Refresh Vulkan", + 0, 0, + 128, 128, + SDL_WINDOW_VULKAN | SDL_WINDOW_HIDDEN + ); + + if (dummyWindowHandle == NULL) + { + Refresh_LogWarn("Vulkan: Could not create dummy window"); + return 0; + } + + if (!VULKAN_INTERNAL_CreateInstance(renderer, dummyWindowHandle)) + { + SDL_DestroyWindow(dummyWindowHandle); + SDL_free(renderer); + Refresh_LogWarn("Vulkan: Could not create Vulkan instance"); + return 0; + } + + if (!SDL_Vulkan_CreateSurface( + (SDL_Window*) dummyWindowHandle, + renderer->instance, + &surface + )) { + SDL_DestroyWindow(dummyWindowHandle); + SDL_free(renderer); + Refresh_LogWarn( + "SDL_Vulkan_CreateSurface failed: %s", + SDL_GetError() + ); + return 0; + } + + #define VULKAN_INSTANCE_FUNCTION(ext, ret, func, params) \ + renderer->func = (vkfntype_##func) vkGetInstanceProcAddr(renderer->instance, #func); + #include "Refresh_Driver_Vulkan_vkfuncs.h" + + if (!VULKAN_INTERNAL_DeterminePhysicalDevice(renderer, surface)) + { + return 0; + } + + renderer->vkDestroySurfaceKHR( + renderer->instance, + surface, + NULL + ); + SDL_DestroyWindow(dummyWindowHandle); + + return 1; +} + +static uint8_t VULKAN_PrepareDriver(uint32_t *flags) +{ + /* Set up dummy VulkanRenderer */ + VulkanRenderer *renderer = (VulkanRenderer*) SDL_malloc(sizeof(VulkanRenderer)); + uint8_t result; + + SDL_memset(renderer, '\0', sizeof(VulkanRenderer)); + + result = VULKAN_INTERNAL_PrepareVulkan(renderer); + + if (!result) + { + Refresh_LogWarn("Vulkan: Failed to determine a suitable physical device"); + } + else + { + *flags = SDL_WINDOW_VULKAN; + } + + renderer->vkDestroyInstance(renderer->instance, NULL); + SDL_free(renderer); + return result; +} + static Refresh_Device* VULKAN_CreateDevice( - Refresh_PresentationParameters *presentationParameters, uint8_t debugMode ) { VulkanRenderer *renderer = (VulkanRenderer*) SDL_malloc(sizeof(VulkanRenderer)); - VkSurfaceKHR surface; Refresh_Device *result; VkResult vulkanResult; @@ -10576,66 +10702,14 @@ static Refresh_Device* VULKAN_CreateDevice( /* Variables: Image Format Detection */ VkImageFormatProperties imageFormatProperties; - VULKAN_INTERNAL_LoadEntryPoints(renderer); - - renderer->presentMode = presentationParameters->presentMode; renderer->debugMode = debugMode; - /* Create the VkInstance */ - if (!VULKAN_INTERNAL_CreateInstance(renderer, presentationParameters->deviceWindowHandle)) + if (!VULKAN_INTERNAL_PrepareVulkan(renderer)) { - Refresh_LogError("Error creating vulkan instance"); + Refresh_LogError("Failed to initialize Vulkan!"); return NULL; } - /* - * Create the WSI vkSurface - */ - - if (!SDL_Vulkan_CreateSurface( - (SDL_Window*)presentationParameters->deviceWindowHandle, - renderer->instance, - &surface - )) { - Refresh_LogError( - "SDL_Vulkan_CreateSurface failed: %s", - SDL_GetError() - ); - return NULL; - } - - /* - * Get vkInstance entry points - */ - - #define VULKAN_INSTANCE_FUNCTION(ext, ret, func, params) \ - renderer->func = (vkfntype_##func) vkGetInstanceProcAddr(renderer->instance, #func); - #include "Refresh_Driver_Vulkan_vkfuncs.h" - - /* - * Choose/Create vkDevice - */ - - if (SDL_strcmp(SDL_GetPlatform(), "Stadia") != 0) - { - deviceExtensionCount -= 1; - } - if (!VULKAN_INTERNAL_DeterminePhysicalDevice( - renderer, - surface, - deviceExtensionNames, - deviceExtensionCount - )) { - Refresh_LogError("Failed to determine a suitable physical device"); - return NULL; - } - - renderer->vkDestroySurfaceKHR( - renderer->instance, - surface, - NULL - ); - Refresh_LogInfo("Refresh Driver: Vulkan"); Refresh_LogInfo( "Vulkan Device: %s", @@ -10677,7 +10751,7 @@ static Refresh_Device* VULKAN_CreateDevice( result->driverData = (Refresh_Renderer*) renderer; /* - * Create initial swapchain + * Create initial swapchain array */ renderer->swapchainDataCapacity = 1; @@ -10686,12 +10760,6 @@ static Refresh_Device* VULKAN_CreateDevice( renderer->swapchainDataCapacity * sizeof(VulkanSwapchainData*) ); - if (!VULKAN_INTERNAL_CreateSwapchain(renderer, presentationParameters->deviceWindowHandle)) - { - Refresh_LogError("Failed to create swapchain"); - return NULL; - } - /* Threading */ renderer->allocatorLock = SDL_CreateMutex(); @@ -11124,6 +11192,7 @@ static Refresh_Device* VULKAN_CreateDevice( Refresh_Driver VulkanDriver = { "Vulkan", + VULKAN_PrepareDriver, VULKAN_CreateDevice };