#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 <sys/ioctl.h>
#ifdef SDL_PLATFORM_OPENBSD
#define DEFAULT_VULKAN "libvulkan.so"
#else
#define DEFAULT_VULKAN "libvulkan.so.1"
#endif
SDL_ELF_NOTE_DLOPEN(
"kmsdrm-vulkan",
"Support for Vulkan on KMSDRM",
SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
DEFAULT_VULKAN
)
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");
}
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;
}
}
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;
}
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 };
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;
int display_index = SDL_GetDisplayIndex(SDL_GetDisplayForWindow(window));
int i, j;
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;
}
if (!vkCreateDisplayPlaneSurfaceKHR) {
SDL_SetError(VK_KHR_DISPLAY_EXTENSION_NAME
" extension is not enabled in the Vulkan instance.");
goto clean;
}
vkEnumeratePhysicalDevices(instance, &gpu_count, NULL);
if (gpu_count == 0) {
SDL_SetError("Vulkan can't find physical devices (gpus).");
goto clean;
}
physical_devices = SDL_malloc(sizeof(VkPhysicalDevice) * gpu_count);
device_props = SDL_malloc(sizeof(VkPhysicalDeviceProperties));
vkEnumeratePhysicalDevices(instance, &gpu_count, physical_devices);
for (i = 0; i < gpu_count; i++) {
vkGetPhysicalDeviceProperties(
physical_devices[i],
device_props);
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;
}
vkGetPhysicalDeviceDisplayPropertiesKHR(gpu, &display_count, NULL);
if (display_count == 0) {
SDL_SetError("Vulkan can't find any displays.");
goto clean;
}
display_props = (VkDisplayPropertiesKHR *)SDL_malloc(display_count * sizeof(*display_props));
vkGetPhysicalDeviceDisplayPropertiesKHR(gpu,
&display_count,
display_props);
display = display_props[display_index].display;
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);
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) {
display_mode = display_mode_props.displayMode;
} else {
new_mode_parameters.visibleRegion.width = window->w;
new_mode_parameters.visibleRegion.height = window->h;
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;
}
}
if (!display_mode) {
SDL_SetError("Vulkan couldn't get a display mode.");
goto clean;
}
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);
for (i = 0; i < plane_count; i++) {
uint32_t supported_displays_count = 0;
VkDisplayKHR *supported_displays;
vkGetDisplayPlaneSupportedDisplaysKHR(gpu, i, &supported_displays_count, NULL);
if (supported_displays_count == 0) {
continue;
}
supported_displays = (VkDisplayKHR *)SDL_malloc(sizeof(VkDisplayKHR) * supported_displays_count);
vkGetDisplayPlaneSupportedDisplaysKHR(gpu, i,
&supported_displays_count, supported_displays);
if (!((plane_props[i].currentDisplay == display) || (plane_props[i].currentDisplay == VK_NULL_HANDLE))) {
continue;
}
plane_supports_display = false;
for (j = 0; j < supported_displays_count; j++) {
if (supported_displays[j] == display) {
plane_supports_display = true;
break;
}
}
SDL_free(supported_displays);
if (!plane_supports_display) {
continue;
}
vkGetDisplayPlaneCapabilitiesKHR(gpu, display_mode, i, &plane_caps);
if (plane_caps.supportedAlpha == alpha_mode) {
_plane = i;
break;
}
}
if (_plane == UINT32_MAX) {
SDL_SetError("Vulkan couldn't find an appropriate plane.");
goto clean;
}
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;
clean:
SDL_free(physical_devices);
SDL_free(display_props);
SDL_free(device_props);
SDL_free(plane_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