From 5a079a2d114f96d4847d1ee305d5b7c16eeec50e Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 27 Dec 2025 12:03:39 -0800 Subject: Initial commit --- .../SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvulkan.c | 520 +++++++++++++++++++++ 1 file changed, 520 insertions(+) create mode 100644 contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvulkan.c (limited to 'contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvulkan.c') diff --git a/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvulkan.c b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvulkan.c new file mode 100644 index 0000000..d58277b --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/kmsdrm/SDL_kmsdrmvulkan.c @@ -0,0 +1,520 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * @author Manuel Alfayate Corchere . + * Based on Jacob Lifshay's SDL_x11vulkan.c. + */ + +#include "SDL_internal.h" + +#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_KMSDRM) + +#include "../SDL_vulkan_internal.h" + +#include "SDL_kmsdrmvideo.h" +#include "SDL_kmsdrmdyn.h" +#include "SDL_kmsdrmvulkan.h" + +#include + +#ifdef SDL_PLATFORM_OPENBSD +#define DEFAULT_VULKAN "libvulkan.so" +#else +#define DEFAULT_VULKAN "libvulkan.so.1" +#endif + +bool KMSDRM_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) +{ + VkExtensionProperties *extensions = NULL; + Uint32 i, extensionCount = 0; + bool hasSurfaceExtension = false; + bool hasDisplayExtension = false; + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL; + + if (_this->vulkan_config.loader_handle) { + return SDL_SetError("Vulkan already loaded"); + } + + // Load the Vulkan library + if (!path) { + path = SDL_GetHint(SDL_HINT_VULKAN_LIBRARY); + } + if (!path) { + path = DEFAULT_VULKAN; + } + + _this->vulkan_config.loader_handle = SDL_LoadObject(path); + + if (!_this->vulkan_config.loader_handle) { + return false; + } + + SDL_strlcpy(_this->vulkan_config.loader_path, path, + SDL_arraysize(_this->vulkan_config.loader_path)); + + vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_LoadFunction( + _this->vulkan_config.loader_handle, "vkGetInstanceProcAddr"); + + if (!vkGetInstanceProcAddr) { + goto fail; + } + + _this->vulkan_config.vkGetInstanceProcAddr = (void *)vkGetInstanceProcAddr; + _this->vulkan_config.vkEnumerateInstanceExtensionProperties = + (void *)((PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr)( + VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"); + + if (!_this->vulkan_config.vkEnumerateInstanceExtensionProperties) { + goto fail; + } + + extensions = SDL_Vulkan_CreateInstanceExtensionsList( + (PFN_vkEnumerateInstanceExtensionProperties) + _this->vulkan_config.vkEnumerateInstanceExtensionProperties, + &extensionCount); + + if (!extensions) { + goto fail; + } + + for (i = 0; i < extensionCount; i++) { + if (SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) { + hasSurfaceExtension = true; + } else if (SDL_strcmp(VK_KHR_DISPLAY_EXTENSION_NAME, extensions[i].extensionName) == 0) { + hasDisplayExtension = true; + } + } + + SDL_free(extensions); + + if (!hasSurfaceExtension) { + SDL_SetError("Installed Vulkan doesn't implement the " VK_KHR_SURFACE_EXTENSION_NAME " extension"); + goto fail; + } else if (!hasDisplayExtension) { + SDL_SetError("Installed Vulkan doesn't implement the " VK_KHR_DISPLAY_EXTENSION_NAME "extension"); + goto fail; + } + + return true; + +fail: + SDL_UnloadObject(_this->vulkan_config.loader_handle); + _this->vulkan_config.loader_handle = NULL; + return false; +} + +void KMSDRM_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) +{ + if (_this->vulkan_config.loader_handle) { + SDL_UnloadObject(_this->vulkan_config.loader_handle); + _this->vulkan_config.loader_handle = NULL; + } +} + +/*********************************************************************/ +// Here we can put whatever Vulkan extensions we want to be enabled +// at instance creation, which is done in the programs, not in SDL. +// So: programs call SDL_Vulkan_GetInstanceExtensions() and here +// we put the extensions specific to this backend so the programs +// get a list with the extension we want, so they can include that +// list in the ppEnabledExtensionNames and EnabledExtensionCount +// members of the VkInstanceCreateInfo struct passed to +// vkCreateInstance(). +/*********************************************************************/ +char const* const* KMSDRM_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count) +{ + static const char *const extensionsForKMSDRM[] = { + VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_DISPLAY_EXTENSION_NAME + }; + if (count) { + *count = SDL_arraysize(extensionsForKMSDRM); + } + return extensionsForKMSDRM; +} + +/***********************************************************************/ +// First thing to know is that we don't call vkCreateInstance() here. +// Instead, programs using SDL and Vulkan create their Vulkan instance +// and we get it here, ready to use. +// Extensions specific for this platform are activated in +// KMSDRM_Vulkan_GetInstanceExtensions(), like we do with +// VK_KHR_DISPLAY_EXTENSION_NAME, which is what we need for x-less VK. +/***********************************************************************/ +bool KMSDRM_Vulkan_CreateSurface(SDL_VideoDevice *_this, + SDL_Window *window, + VkInstance instance, + const struct VkAllocationCallbacks *allocator, + VkSurfaceKHR *surface) +{ + VkPhysicalDevice gpu = NULL; + uint32_t gpu_count; + uint32_t display_count; + uint32_t mode_count; + uint32_t plane_count; + uint32_t plane = UINT32_MAX; + + VkPhysicalDevice *physical_devices = NULL; + VkPhysicalDeviceProperties *device_props = NULL; + VkDisplayPropertiesKHR *display_props = NULL; + VkDisplayModePropertiesKHR *mode_props = NULL; + VkDisplayPlanePropertiesKHR *plane_props = NULL; + VkDisplayPlaneCapabilitiesKHR plane_caps; + + VkDisplayModeCreateInfoKHR display_mode_create_info; + VkDisplaySurfaceCreateInfoKHR display_plane_surface_create_info; + + VkExtent2D image_size; + VkDisplayKHR display; + VkDisplayModeKHR display_mode = (VkDisplayModeKHR)0; + VkDisplayModePropertiesKHR display_mode_props = { 0 }; + VkDisplayModeParametersKHR new_mode_parameters = { { 0, 0 }, 0 }; + // Prefer a plane that supports per-pixel alpha. + VkDisplayPlaneAlphaFlagBitsKHR alpha_mode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR; + + VkResult result; + bool ret = false; + bool valid_gpu = false; + bool mode_found = false; + bool plane_supports_display = false; + + // Get the display index from the display being used by the window. + int display_index = SDL_GetDisplayIndex(SDL_GetDisplayForWindow(window)); + int i, j; + + // Get the function pointers for the functions we will use. + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = + (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr; + + PFN_vkCreateDisplayPlaneSurfaceKHR vkCreateDisplayPlaneSurfaceKHR = + (PFN_vkCreateDisplayPlaneSurfaceKHR)vkGetInstanceProcAddr( + instance, "vkCreateDisplayPlaneSurfaceKHR"); + + PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices = + (PFN_vkEnumeratePhysicalDevices)vkGetInstanceProcAddr( + instance, "vkEnumeratePhysicalDevices"); + + PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties = + (PFN_vkGetPhysicalDeviceProperties)vkGetInstanceProcAddr( + instance, "vkGetPhysicalDeviceProperties"); + + PFN_vkGetPhysicalDeviceDisplayPropertiesKHR vkGetPhysicalDeviceDisplayPropertiesKHR = + (PFN_vkGetPhysicalDeviceDisplayPropertiesKHR)vkGetInstanceProcAddr( + instance, "vkGetPhysicalDeviceDisplayPropertiesKHR"); + + PFN_vkGetDisplayModePropertiesKHR vkGetDisplayModePropertiesKHR = + (PFN_vkGetDisplayModePropertiesKHR)vkGetInstanceProcAddr( + instance, "vkGetDisplayModePropertiesKHR"); + + PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vkGetPhysicalDeviceDisplayPlanePropertiesKHR = + (PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR)vkGetInstanceProcAddr( + instance, "vkGetPhysicalDeviceDisplayPlanePropertiesKHR"); + + PFN_vkGetDisplayPlaneSupportedDisplaysKHR vkGetDisplayPlaneSupportedDisplaysKHR = + (PFN_vkGetDisplayPlaneSupportedDisplaysKHR)vkGetInstanceProcAddr( + instance, "vkGetDisplayPlaneSupportedDisplaysKHR"); + + PFN_vkGetDisplayPlaneCapabilitiesKHR vkGetDisplayPlaneCapabilitiesKHR = + (PFN_vkGetDisplayPlaneCapabilitiesKHR)vkGetInstanceProcAddr( + instance, "vkGetDisplayPlaneCapabilitiesKHR"); + + PFN_vkCreateDisplayModeKHR vkCreateDisplayModeKHR = + (PFN_vkCreateDisplayModeKHR)vkGetInstanceProcAddr( + instance, "vkCreateDisplayModeKHR"); + + if (!_this->vulkan_config.loader_handle) { + SDL_SetError("Vulkan is not loaded"); + goto clean; + } + + /*************************************/ + // Block for vulkan surface creation + /*************************************/ + + /****************************************************************/ + // If we got vkCreateDisplayPlaneSurfaceKHR() pointer, it means + // that the VK_KHR_Display extension is active on the instance. + // That's the central extension we need for x-less VK! + /****************************************************************/ + if (!vkCreateDisplayPlaneSurfaceKHR) { + SDL_SetError(VK_KHR_DISPLAY_EXTENSION_NAME + " extension is not enabled in the Vulkan instance."); + goto clean; + } + + /* A GPU (or physical_device, in vkcube terms) is a physical GPU. + A machine with more than one video output doesn't need to have more than one GPU, + like the Pi4 which has 1 GPU and 2 video outputs. + Just in case, we test that the GPU we choose is Vulkan-capable. + If there are new reports about VK init failures, hardcode + gpu = physical_devices[0], instead of probing, and go with that. + */ + + // Get the physical device count. + vkEnumeratePhysicalDevices(instance, &gpu_count, NULL); + + if (gpu_count == 0) { + SDL_SetError("Vulkan can't find physical devices (gpus)."); + goto clean; + } + + // Get the physical devices. + physical_devices = SDL_malloc(sizeof(VkPhysicalDevice) * gpu_count); + device_props = SDL_malloc(sizeof(VkPhysicalDeviceProperties)); + vkEnumeratePhysicalDevices(instance, &gpu_count, physical_devices); + + // Iterate on the physical devices. + for (i = 0; i < gpu_count; i++) { + + // Get the physical device properties. + vkGetPhysicalDeviceProperties( + physical_devices[i], + device_props); + + // Is this device a real GPU that supports API version 1 at least? + if (device_props->apiVersion >= 1 && + (device_props->deviceType == 1 || device_props->deviceType == 2)) { + gpu = physical_devices[i]; + valid_gpu = true; + break; + } + } + + if (!valid_gpu) { + SDL_SetError("Vulkan can't find a valid physical device (gpu)."); + goto clean; + } + + /* A display is a video output. 1 GPU can have N displays. + Vulkan only counts the connected displays. + Get the display count of the GPU. */ + vkGetPhysicalDeviceDisplayPropertiesKHR(gpu, &display_count, NULL); + if (display_count == 0) { + SDL_SetError("Vulkan can't find any displays."); + goto clean; + } + + // Get the props of the displays of the physical device. + display_props = (VkDisplayPropertiesKHR *)SDL_malloc(display_count * sizeof(*display_props)); + vkGetPhysicalDeviceDisplayPropertiesKHR(gpu, + &display_count, + display_props); + + // Get the chosen display based on the display index. + display = display_props[display_index].display; + + // Get the list of the display videomodes. + vkGetDisplayModePropertiesKHR(gpu, + display, + &mode_count, NULL); + + if (mode_count == 0) { + SDL_SetError("Vulkan can't find any video modes for display %i (%s)", 0, + display_props[display_index].displayName); + goto clean; + } + + mode_props = (VkDisplayModePropertiesKHR *)SDL_malloc(mode_count * sizeof(*mode_props)); + vkGetDisplayModePropertiesKHR(gpu, + display, + &mode_count, mode_props); + + /* Get a video mode equal to the window size among the predefined ones, + if possible. + REMEMBER: We have to get a small enough videomode for the window size, + because videomode determines how big the scanout region is and we can't + scanout a region bigger than the window (we would be reading past the + buffer, and Vulkan would give us a confusing VK_ERROR_SURFACE_LOST_KHR). */ + for (i = 0; i < mode_count; i++) { + if (mode_props[i].parameters.visibleRegion.width == window->w && + mode_props[i].parameters.visibleRegion.height == window->h) { + display_mode_props = mode_props[i]; + mode_found = true; + break; + } + } + + if (mode_found && + display_mode_props.parameters.visibleRegion.width > 0 && + display_mode_props.parameters.visibleRegion.height > 0) { + // Found a suitable mode among the predefined ones. Use that. + display_mode = display_mode_props.displayMode; + } else { + + /* Couldn't find a suitable mode among the predefined ones, so try to create our own. + This won't work for some video chips atm (like Pi's VideoCore) so these are limited + to supported resolutions. Don't try to use "closest" resolutions either, because + those are often bigger than the window size, thus causing out-of-bunds scanout. */ + new_mode_parameters.visibleRegion.width = window->w; + new_mode_parameters.visibleRegion.height = window->h; + /* SDL (and DRM, if we look at drmModeModeInfo vrefresh) uses plain integer Hz for + display mode refresh rate, but Vulkan expects higher precision. */ + new_mode_parameters.refreshRate = (uint32_t)(window->current_fullscreen_mode.refresh_rate * 1000); + + SDL_zero(display_mode_create_info); + display_mode_create_info.sType = VK_STRUCTURE_TYPE_DISPLAY_MODE_CREATE_INFO_KHR; + display_mode_create_info.parameters = new_mode_parameters; + result = vkCreateDisplayModeKHR(gpu, + display, + &display_mode_create_info, + NULL, &display_mode); + if (result != VK_SUCCESS) { + SDL_SetError("Vulkan couldn't find a predefined mode for that window size and couldn't create a suitable mode."); + goto clean; + } + } + + // Just in case we get here without a display_mode. + if (!display_mode) { + SDL_SetError("Vulkan couldn't get a display mode."); + goto clean; + } + + // Get the list of the physical device planes. + vkGetPhysicalDeviceDisplayPlanePropertiesKHR(gpu, &plane_count, NULL); + if (plane_count == 0) { + SDL_SetError("Vulkan can't find any planes."); + goto clean; + } + plane_props = SDL_malloc(sizeof(VkDisplayPlanePropertiesKHR) * plane_count); + vkGetPhysicalDeviceDisplayPlanePropertiesKHR(gpu, &plane_count, plane_props); + + /* Iterate on the list of planes of the physical device + to find a plane that matches these criteria: + -It must be compatible with the chosen display + mode. + -It isn't currently bound to another display. + -It supports per-pixel alpha, if possible. */ + for (i = 0; i < plane_count; i++) { + + uint32_t supported_displays_count = 0; + VkDisplayKHR *supported_displays; + + // See if the plane is compatible with the current display. + vkGetDisplayPlaneSupportedDisplaysKHR(gpu, i, &supported_displays_count, NULL); + if (supported_displays_count == 0) { + // This plane doesn't support any displays. Continue to the next plane. + continue; + } + + // Get the list of displays supported by this plane. + supported_displays = (VkDisplayKHR *)SDL_malloc(sizeof(VkDisplayKHR) * supported_displays_count); + vkGetDisplayPlaneSupportedDisplaysKHR(gpu, i, + &supported_displays_count, supported_displays); + + /* The plane must be bound to the chosen display, or not in use. + If none of these is true, iterate to another plane. */ + if (!((plane_props[i].currentDisplay == display) || (plane_props[i].currentDisplay == VK_NULL_HANDLE))) { + continue; + } + + /* Iterate the list of displays supported by this plane + in order to find out if the chosen display is among them. */ + plane_supports_display = false; + for (j = 0; j < supported_displays_count; j++) { + if (supported_displays[j] == display) { + plane_supports_display = true; + break; + } + } + + // Free the list of displays supported by this plane. + if (supported_displays) { + SDL_free(supported_displays); + } + + // If the display is not supported by this plane, iterate to the next plane. + if (!plane_supports_display) { + continue; + } + + // Want a plane that supports the alpha mode we have chosen. + vkGetDisplayPlaneCapabilitiesKHR(gpu, display_mode, i, &plane_caps); + if (plane_caps.supportedAlpha == alpha_mode) { + // Yep, this plane is alright. + plane = i; + break; + } + } + + // If we couldn't find an appropriate plane, error out. + if (plane == UINT32_MAX) { + SDL_SetError("Vulkan couldn't find an appropriate plane."); + goto clean; + } + + /********************************************/ + // Let's finally create the Vulkan surface! + /********************************************/ + + image_size.width = window->w; + image_size.height = window->h; + + SDL_zero(display_plane_surface_create_info); + display_plane_surface_create_info.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR; + display_plane_surface_create_info.displayMode = display_mode; + display_plane_surface_create_info.planeIndex = plane; + display_plane_surface_create_info.imageExtent = image_size; + display_plane_surface_create_info.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + display_plane_surface_create_info.alphaMode = alpha_mode; + result = vkCreateDisplayPlaneSurfaceKHR(instance, + &display_plane_surface_create_info, + allocator, + surface); + if (result != VK_SUCCESS) { + SDL_SetError("vkCreateDisplayPlaneSurfaceKHR failed: %s", + SDL_Vulkan_GetResultString(result)); + goto clean; + } + + ret = true; // success! + +clean: + if (physical_devices) { + SDL_free(physical_devices); + } + if (display_props) { + SDL_free(display_props); + } + if (device_props) { + SDL_free(device_props); + } + if (plane_props) { + SDL_free(plane_props); + } + if (mode_props) { + SDL_free(mode_props); + } + + return ret; +} + +void KMSDRM_Vulkan_DestroySurface(SDL_VideoDevice *_this, + VkInstance instance, + VkSurfaceKHR surface, + const struct VkAllocationCallbacks *allocator) +{ + if (_this->vulkan_config.loader_handle) { + SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator); + } +} + +#endif -- cgit v1.2.3