| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- /*
- Simple DirectMedia Layer
- Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
- 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 <redwindwanderer@gmail.com>.
- * 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 <SDL3/SDL_syswm.h>
- #include <sys/ioctl.h>
- #ifdef __OpenBSD__
- #define DEFAULT_VULKAN "libvulkan.so"
- #else
- #define DEFAULT_VULKAN "libvulkan.so.1"
- #endif
- int KMSDRM_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path)
- {
- VkExtensionProperties *extensions = NULL;
- Uint32 i, extensionCount = 0;
- SDL_bool hasSurfaceExtension = SDL_FALSE;
- SDL_bool hasDisplayExtension = SDL_FALSE;
- PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL;
- if (_this->vulkan_config.loader_handle) {
- return SDL_SetError("Vulkan already loaded");
- }
- /* Load the Vulkan library */
- if (path == NULL) {
- path = SDL_getenv("SDL_VULKAN_LIBRARY");
- }
- if (path == NULL) {
- path = DEFAULT_VULKAN;
- }
- _this->vulkan_config.loader_handle = SDL_LoadObject(path);
- if (!_this->vulkan_config.loader_handle) {
- return -1;
- }
- 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 == NULL) {
- goto fail;
- }
- for (i = 0; i < extensionCount; i++) {
- if (SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
- hasSurfaceExtension = SDL_TRUE;
- } else if (SDL_strcmp(VK_KHR_DISPLAY_EXTENSION_NAME, extensions[i].extensionName) == 0) {
- hasDisplayExtension = SDL_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 0;
- fail:
- SDL_UnloadObject(_this->vulkan_config.loader_handle);
- _this->vulkan_config.loader_handle = NULL;
- return -1;
- }
- 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. */
- /***********************************************************************/
- SDL_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;
- SDL_bool ret = SDL_FALSE;
- SDL_bool valid_gpu = SDL_FALSE;
- SDL_bool mode_found = SDL_FALSE;
- SDL_bool plane_supports_display = SDL_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 = SDL_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)\n", 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 = SDL_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 = 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 = SDL_FALSE;
- for (j = 0; j < supported_displays_count; j++) {
- if (supported_displays[j] == display) {
- plane_supports_display = SDL_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 = SDL_TRUE;
- 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;
- }
- #endif
|