Atlas - SDL_offscreenvulkan.c
Home / ext / SDL / src / video / offscreen Lines: 1 | Size: 10581 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_OFFSCREEN) 24 25#include "../SDL_vulkan_internal.h" 26#include "../SDL_sysvideo.h" 27 28 29static const char *s_defaultPaths[] = { 30#if defined(SDL_PLATFORM_WINDOWS) 31 "vulkan-1.dll" 32#elif defined(SDL_PLATFORM_APPLE) 33 "vulkan.framework/vulkan", 34 "libvulkan.1.dylib", 35 "libvulkan.dylib", 36 "MoltenVK.framework/MoltenVK", 37 "libMoltenVK.dylib" 38#elif defined(SDL_PLATFORM_OPENBSD) 39 "libvulkan.so" 40#else 41 "libvulkan.so.1" 42#endif 43}; 44 45SDL_ELF_NOTE_DLOPEN( 46 "offscreen-vulkan", 47 "Support for offscreen Vulkan", 48 SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, 49 "libvulkan.so.1" 50) 51 52#if defined( SDL_PLATFORM_APPLE ) 53#include <dlfcn.h> 54 55// Since libSDL is most likely a .dylib, need RTLD_DEFAULT not RTLD_SELF. 56#define DEFAULT_HANDLE RTLD_DEFAULT 57#endif 58 59/*Should the whole driver fail if it can't create a surface? Rendering to an offscreen buffer is still possible without a surface. 60 At the time of writing. I need the driver to minimally work even if the surface extension isn't present. 61 And account for the inability to create a surface on the consumer side. 62 So for now I'm targeting my specific use case -Dave Kircher*/ 63#define HEADLESS_SURFACE_EXTENSION_REQUIRED_TO_LOAD 0 64 65 66bool OFFSCREEN_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) 67{ 68 VkExtensionProperties *extensions = NULL; 69 Uint32 extensionCount = 0; 70 bool hasSurfaceExtension = false; 71 bool hasHeadlessSurfaceExtension = false; 72 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL; 73 Uint32 i; 74 const char **paths; 75 const char *foundPath = NULL; 76 Uint32 numPaths; 77 78 if (_this->vulkan_config.loader_handle) { 79 return SDL_SetError("Vulkan already loaded"); 80 } 81 82 // Load the Vulkan loader library 83 if (!path) { 84 path = SDL_GetHint(SDL_HINT_VULKAN_LIBRARY); 85 } 86 87#if defined(SDL_PLATFORM_APPLE) 88 if (!path) { 89 // Handle the case where Vulkan Portability is linked statically. 90 vkGetInstanceProcAddr = 91 (PFN_vkGetInstanceProcAddr)dlsym(DEFAULT_HANDLE, 92 "vkGetInstanceProcAddr"); 93 } 94 95 if (vkGetInstanceProcAddr) { 96 _this->vulkan_config.loader_handle = DEFAULT_HANDLE; 97 } else 98#endif 99 { 100 if (path) { 101 paths = &path; 102 numPaths = 1; 103 } else { 104 paths = s_defaultPaths; 105 numPaths = SDL_arraysize(s_defaultPaths); 106 } 107 108 for (i = 0; i < numPaths && _this->vulkan_config.loader_handle == NULL; i++) { 109 foundPath = paths[i]; 110 _this->vulkan_config.loader_handle = SDL_LoadObject(foundPath); 111 } 112 113 if (_this->vulkan_config.loader_handle == NULL) { 114 return SDL_SetError("Failed to load Vulkan Portability library"); 115 } 116 117 SDL_strlcpy(_this->vulkan_config.loader_path, foundPath, 118 SDL_arraysize(_this->vulkan_config.loader_path)); 119 vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_LoadFunction( 120 _this->vulkan_config.loader_handle, "vkGetInstanceProcAddr"); 121 122 if (!vkGetInstanceProcAddr) { 123 SDL_SetError("Failed to load vkGetInstanceProcAddr from Vulkan Portability library"); 124 goto fail; 125 } 126 } 127 128 _this->vulkan_config.vkGetInstanceProcAddr = (SDL_FunctionPointer)vkGetInstanceProcAddr; 129 _this->vulkan_config.vkEnumerateInstanceExtensionProperties = 130 (SDL_FunctionPointer)((PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr)( 131 VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"); 132 if (!_this->vulkan_config.vkEnumerateInstanceExtensionProperties) { 133 goto fail; 134 } 135 extensions = SDL_Vulkan_CreateInstanceExtensionsList( 136 (PFN_vkEnumerateInstanceExtensionProperties) 137 _this->vulkan_config.vkEnumerateInstanceExtensionProperties, 138 &extensionCount); 139 if (!extensions) { 140 goto fail; 141 } 142 for (i = 0; i < extensionCount; i++) { 143 if (SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) { 144 hasSurfaceExtension = true; 145 } else if (SDL_strcmp(VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) { 146 hasHeadlessSurfaceExtension = true; 147 } 148 } 149 SDL_free(extensions); 150 if (!hasSurfaceExtension) { 151 SDL_SetError("Installed Vulkan doesn't implement the " VK_KHR_SURFACE_EXTENSION_NAME " extension"); 152 goto fail; 153 } 154 if (!hasHeadlessSurfaceExtension) { 155#if (HEADLESS_SURFACE_EXTENSION_REQUIRED_TO_LOAD != 0) 156 SDL_SetError("Installed Vulkan doesn't implement the " VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME " extension"); 157 goto fail; 158#else 159 // Let's at least leave a breadcrumb for people to find if they have issues 160 SDL_Log("Installed Vulkan doesn't implement the " VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME " extension"); 161#endif 162 } 163 return true; 164 165fail: 166 SDL_UnloadObject(_this->vulkan_config.loader_handle); 167 _this->vulkan_config.loader_handle = NULL; 168 return false; 169} 170 171void OFFSCREEN_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) 172{ 173 if (_this->vulkan_config.loader_handle) { 174 SDL_UnloadObject(_this->vulkan_config.loader_handle); 175 _this->vulkan_config.loader_handle = NULL; 176 } 177} 178 179char const *const *OFFSCREEN_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, 180 Uint32 *count) 181{ 182#if (HEADLESS_SURFACE_EXTENSION_REQUIRED_TO_LOAD == 0) 183 VkExtensionProperties *enumerateExtensions = NULL; 184 Uint32 enumerateExtensionCount = 0; 185 bool hasHeadlessSurfaceExtension = false; 186 Uint32 i; 187#endif 188 189 static const char *const returnExtensions[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME }; 190 if (count) { 191# if (HEADLESS_SURFACE_EXTENSION_REQUIRED_TO_LOAD == 0) 192 { 193 /* In optional mode, only return VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME if it's already supported by the instance 194 There's probably a better way to cache the presence of the extension during OFFSCREEN_Vulkan_LoadLibrary(). 195 But both SDL_VideoData and SDL_VideoDevice::vulkan_config seem like I'd need to touch a bunch of code to do properly. 196 And I want a smaller footprint for the first pass*/ 197 if ( _this->vulkan_config.vkEnumerateInstanceExtensionProperties ) { 198 enumerateExtensions = SDL_Vulkan_CreateInstanceExtensionsList( 199 (PFN_vkEnumerateInstanceExtensionProperties) 200 _this->vulkan_config.vkEnumerateInstanceExtensionProperties, 201 &enumerateExtensionCount); 202 for (i = 0; i < enumerateExtensionCount; i++) { 203 if (SDL_strcmp(VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME, enumerateExtensions[i].extensionName) == 0) { 204 hasHeadlessSurfaceExtension = true; 205 } 206 } 207 SDL_free(enumerateExtensions); 208 } 209 if ( hasHeadlessSurfaceExtension == true ) { 210 *count = SDL_arraysize(returnExtensions); 211 } else { 212 *count = SDL_arraysize(returnExtensions) - 1; // assumes VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME is last 213 } 214 } 215# else 216 { 217 *count = SDL_arraysize(returnExtensions); 218 } 219# endif 220 } 221 return returnExtensions; 222} 223 224bool OFFSCREEN_Vulkan_CreateSurface(SDL_VideoDevice *_this, 225 SDL_Window *window, 226 VkInstance instance, 227 const struct VkAllocationCallbacks *allocator, 228 VkSurfaceKHR *surface) 229{ 230 *surface = VK_NULL_HANDLE; 231 232 if (!_this->vulkan_config.loader_handle) { 233 return SDL_SetError("Vulkan is not loaded"); 234 } 235 236 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr; 237 PFN_vkCreateHeadlessSurfaceEXT vkCreateHeadlessSurfaceEXT = 238 (PFN_vkCreateHeadlessSurfaceEXT)vkGetInstanceProcAddr(instance, "vkCreateHeadlessSurfaceEXT"); 239 VkHeadlessSurfaceCreateInfoEXT createInfo; 240 VkResult result; 241 if (!vkCreateHeadlessSurfaceEXT) { 242 /* This may be surprising to the consumer when HEADLESS_SURFACE_EXTENSION_REQUIRED_TO_LOAD == 0 243 But this is the tradeoff for allowing offscreen rendering to a buffer to continue working without requiring the extension during driver load */ 244 return SDL_SetError(VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME 245 " extension is not enabled in the Vulkan instance."); 246 } 247 SDL_zero(createInfo); 248 createInfo.sType = VK_STRUCTURE_TYPE_HEADLESS_SURFACE_CREATE_INFO_EXT; 249 createInfo.pNext = NULL; 250 createInfo.flags = 0; 251 result = vkCreateHeadlessSurfaceEXT(instance, &createInfo, allocator, surface); 252 if (result != VK_SUCCESS) { 253 return SDL_SetError("vkCreateHeadlessSurfaceEXT failed: %s", SDL_Vulkan_GetResultString(result)); 254 } 255 return true; 256} 257 258void OFFSCREEN_Vulkan_DestroySurface(SDL_VideoDevice *_this, 259 VkInstance instance, 260 VkSurfaceKHR surface, 261 const struct VkAllocationCallbacks *allocator) 262{ 263 if (_this->vulkan_config.loader_handle) { 264 SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator); 265 } 266} 267 268#endif 269[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.