mirror of
https://github.com/encounter/SDL.git
synced 2025-05-28 02:01:25 +00:00
530 lines
20 KiB
C
530 lines
20 KiB
C
/*
|
|
Simple DirectMedia Layer
|
|
Copyright (C) 1997-2021 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 SDL_VIDEO_VULKAN && SDL_VIDEO_DRIVER_KMSDRM
|
|
|
|
#include "SDL_kmsdrmvideo.h"
|
|
#include "SDL_kmsdrmdyn.h"
|
|
#include "SDL_assert.h"
|
|
|
|
#include "SDL_loadso.h"
|
|
#include "SDL_kmsdrmvulkan.h"
|
|
#include "SDL_syswm.h"
|
|
#include "sys/ioctl.h"
|
|
|
|
#if defined(__OpenBSD__)
|
|
#define DEFAULT_VULKAN "libvulkan.so"
|
|
#else
|
|
#define DEFAULT_VULKAN "libvulkan.so.1"
|
|
#endif
|
|
|
|
int KMSDRM_Vulkan_LoadLibrary(_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)
|
|
path = SDL_getenv("SDL_VULKAN_LIBRARY");
|
|
if(!path)
|
|
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)
|
|
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(_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(). */
|
|
/*********************************************************************/
|
|
SDL_bool KMSDRM_Vulkan_GetInstanceExtensions(_THIS,
|
|
SDL_Window *window,
|
|
unsigned *count,
|
|
const char **names)
|
|
{
|
|
static const char *const extensionsForKMSDRM[] = {
|
|
VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_DISPLAY_EXTENSION_NAME
|
|
};
|
|
if(!_this->vulkan_config.loader_handle)
|
|
{
|
|
SDL_SetError("Vulkan is not loaded");
|
|
return SDL_FALSE;
|
|
}
|
|
return SDL_Vulkan_GetInstanceExtensions_Helper(
|
|
count, names, SDL_arraysize(extensionsForKMSDRM),
|
|
extensionsForKMSDRM);
|
|
}
|
|
|
|
void KMSDRM_Vulkan_GetDrawableSize(_THIS, SDL_Window *window, int *w, int *h)
|
|
{
|
|
if (w) {
|
|
*w = window->w;
|
|
}
|
|
|
|
if (h) {
|
|
*h = window->h;
|
|
}
|
|
}
|
|
|
|
/***********************************************************************/
|
|
/* 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(_THIS,
|
|
SDL_Window *window,
|
|
VkInstance instance,
|
|
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_atoi(SDL_GetDisplayForWindow(window)->name);
|
|
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->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 appropiate plane, error out. */
|
|
if (plane == UINT32_MAX) {
|
|
SDL_SetError("Vulkan couldn't find an appropiate 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,
|
|
NULL,
|
|
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
|
|
|
|
/* vim: set ts=4 sw=4 expandtab: */
|