diff --git a/include/Refresh.h b/include/Refresh.h index 509ad21..74f76e0 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 @@ -594,9 +602,24 @@ 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. + * + * 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. diff --git a/src/Refresh.c b/src/Refresh.c index ee24022..053ca12 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,18 +130,55 @@ 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 (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( + return backends[selectedBackend]->CreateDevice( presentationParameters, debugMode ); diff --git a/src/Refresh_Driver.h b/src/Refresh_Driver.h index 7b5a1c6..9308372 100644 --- a/src/Refresh_Driver.h +++ b/src/Refresh_Driver.h @@ -542,6 +542,7 @@ struct Refresh_Device typedef struct Refresh_Driver { const char *Name; + uint8_t (*PrepareDriver)(uint32_t *flags); Refresh_Device* (*CreateDevice)( Refresh_PresentationParameters *presentationParameters, uint8_t debugMode diff --git a/src/Refresh_Driver_Vulkan.c b/src/Refresh_Driver_Vulkan.c index 8c5cc64..ecd407e 100644 --- a/src/Refresh_Driver_Vulkan.c +++ b/src/Refresh_Driver_Vulkan.c @@ -10285,9 +10285,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 +10511,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,6 +10543,83 @@ static void VULKAN_INTERNAL_LoadEntryPoints( #include "Refresh_Driver_Vulkan_vkfuncs.h" } +static uint8_t VULKAN_PrepareDriver(uint32_t *flags) +{ + SDL_Window *dummyWindowHandle; + VulkanRenderer *renderer; + VkSurfaceKHR surface; + uint8_t result; + + VULKAN_INTERNAL_LoadEntryPoints(); + + /* Test if we can create a Vulkan device */ + + 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; + } + + /* partially set up VulkanRenderer so we can fall back in case of device non-compliance */ + renderer = (VulkanRenderer*) SDL_malloc(sizeof(VulkanRenderer)); + SDL_memset(renderer, '\0', sizeof(VulkanRenderer)); + + 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" + + result = VULKAN_INTERNAL_DeterminePhysicalDevice(renderer, surface); + + renderer->vkDestroySurfaceKHR( + renderer->instance, + surface, + NULL + ); + renderer->vkDestroyInstance(renderer->instance, NULL); + SDL_DestroyWindow(dummyWindowHandle); + SDL_free(renderer); + + if (!result) + { + Refresh_LogWarn("Vulkan: Failed to determine a suitable physical device"); + } + else + { + *flags = SDL_WINDOW_VULKAN; + } + + return result; +} + static Refresh_Device* VULKAN_CreateDevice( Refresh_PresentationParameters *presentationParameters, uint8_t debugMode @@ -10609,7 +10683,7 @@ static Refresh_Device* VULKAN_CreateDevice( */ #define VULKAN_INSTANCE_FUNCTION(ext, ret, func, params) \ - renderer->func = (vkfntype_##func) vkGetInstanceProcAddr(renderer->instance, #func); + renderer->func = (vkfntype_##func) vkGetInstanceProcAddr(renderer->instance, #func); #include "Refresh_Driver_Vulkan_vkfuncs.h" /* @@ -10622,9 +10696,7 @@ static Refresh_Device* VULKAN_CreateDevice( } if (!VULKAN_INTERNAL_DeterminePhysicalDevice( renderer, - surface, - deviceExtensionNames, - deviceExtensionCount + surface )) { Refresh_LogError("Failed to determine a suitable physical device"); return NULL; @@ -11124,6 +11196,7 @@ static Refresh_Device* VULKAN_CreateDevice( Refresh_Driver VulkanDriver = { "Vulkan", + VULKAN_PrepareDriver, VULKAN_CreateDevice };