From 32a4fe25c1c8b8fe92bc1522b4a37cb042765c3b Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Fri, 10 Oct 2025 16:49:30 +0200 Subject: [PATCH 01/24] Add hint to specify Vulkan API version to use This adds SDL_HINT_VULKAN_REQUEST_API_VERSION to allow requesting a specific API version when SDL creates the Vulkan instance. --- include/SDL3/SDL_hints.h | 10 ++++++++++ src/gpu/vulkan/SDL_gpu_vulkan.c | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 331a83258aba1..b53e97ff2d71a 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -4080,6 +4080,16 @@ extern "C" { */ #define SDL_HINT_VULKAN_LIBRARY "SDL_VULKAN_LIBRARY" +/** + * Specify the Vulkan API version to create the instance with. + * + * This hint should be set before creating a Vulkan window. Expects a + * version string delimited by underscores. E.g. 1_0_0 or 1_3_0 + * + * \since This hint is available since SDL 3.xx. + */ +#define SDL_HINT_VULKAN_REQUEST_API_VERSION "SDL_VULKAN_REQUEST_API_VERSION" + /** * A variable controlling how the fact chunk affects the loading of a WAVE * file. diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index e366db6a520b3..e11e7157c4d52 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -11205,6 +11205,20 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) appInfo.pEngineName = "SDLGPU"; appInfo.engineVersion = SDL_VERSION; appInfo.apiVersion = VK_MAKE_VERSION(1, 0, 0); + const char *hint = SDL_GetHint(SDL_HINT_VULKAN_REQUEST_API_VERSION); + if (hint) { + int numFound = 0; + int version[3] = { 0, 0, 0 }; + char *saveptr = NULL; + char *token = SDL_strtok_r(hint, "_", &saveptr); + while (token != NULL && numFound < 3) { + version[numFound] = SDL_atoi(token); + numFound++; + token = SDL_strtok_r(NULL, "_", &saveptr); + } + + appInfo.apiVersion = VK_MAKE_VERSION(version[0], version[1], version[2]); + } createFlags = 0; From c0ea6bfc010b146eefaeeeb1c0a8cf5362083b5c Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Fri, 10 Oct 2025 18:32:36 +0200 Subject: [PATCH 02/24] Fix incorrect use of strings --- src/gpu/vulkan/SDL_gpu_vulkan.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index e11e7157c4d52..5d080c84a80f3 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -11207,10 +11207,11 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) appInfo.apiVersion = VK_MAKE_VERSION(1, 0, 0); const char *hint = SDL_GetHint(SDL_HINT_VULKAN_REQUEST_API_VERSION); if (hint) { + char *text = SDL_strdup(hint); int numFound = 0; int version[3] = { 0, 0, 0 }; char *saveptr = NULL; - char *token = SDL_strtok_r(hint, "_", &saveptr); + char *token = SDL_strtok_r(text, "_", &saveptr); while (token != NULL && numFound < 3) { version[numFound] = SDL_atoi(token); numFound++; @@ -11218,6 +11219,7 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) } appInfo.apiVersion = VK_MAKE_VERSION(version[0], version[1], version[2]); + SDL_free(text); } createFlags = 0; From 6c4d2c004f079edf9f01dd9a99a875e8b2b38b39 Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Fri, 10 Oct 2025 22:22:22 +0200 Subject: [PATCH 03/24] Change hint to only specify minor version The patch version shouldn't be relevant to the API and a new major version will likely require an entirely new renderer anyway. The benefit of this approach is that it massively simplifies parsing of the hint. --- include/SDL3/SDL_hints.h | 8 +++---- src/gpu/vulkan/SDL_gpu_vulkan.c | 41 ++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index b53e97ff2d71a..bf593db2d5d32 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -4081,14 +4081,14 @@ extern "C" { #define SDL_HINT_VULKAN_LIBRARY "SDL_VULKAN_LIBRARY" /** - * Specify the Vulkan API version to create the instance with. + * Specify the Vulkan API minor version to create the instance with. * - * This hint should be set before creating a Vulkan window. Expects a - * version string delimited by underscores. E.g. 1_0_0 or 1_3_0 + * This hint should be set before creating a Vulkan window. Expects a positive + * integer. E.g. 3 for Vulkan 1.3. * * \since This hint is available since SDL 3.xx. */ -#define SDL_HINT_VULKAN_REQUEST_API_VERSION "SDL_VULKAN_REQUEST_API_VERSION" +#define SDL_HINT_VULKAN_REQUEST_API_MINOR_VERSION "SDL_VULKAN_REQUEST_API_MINOR_VERSION" /** * A variable controlling how the fact chunk affects the loading of a WAVE diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index 5d080c84a80f3..87161ea21498f 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -11205,21 +11205,34 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) appInfo.pEngineName = "SDLGPU"; appInfo.engineVersion = SDL_VERSION; appInfo.apiVersion = VK_MAKE_VERSION(1, 0, 0); - const char *hint = SDL_GetHint(SDL_HINT_VULKAN_REQUEST_API_VERSION); + + // Handle application requesting a specific Vulkan API minor version + const char *hint = SDL_GetHint(SDL_HINT_VULKAN_REQUEST_API_MINOR_VERSION); if (hint) { - char *text = SDL_strdup(hint); - int numFound = 0; - int version[3] = { 0, 0, 0 }; - char *saveptr = NULL; - char *token = SDL_strtok_r(text, "_", &saveptr); - while (token != NULL && numFound < 3) { - version[numFound] = SDL_atoi(token); - numFound++; - token = SDL_strtok_r(NULL, "_", &saveptr); - } - - appInfo.apiVersion = VK_MAKE_VERSION(version[0], version[1], version[2]); - SDL_free(text); + size_t len = SDL_strnlen(hint, 16); + if (len > 0) { + char *endptr = NULL; + long minor = SDL_strtol(hint, &endptr, 10); + if (minor >= 0 && endptr == hint + len) { + appInfo.apiVersion = VK_MAKE_VERSION(1, minor, 0); + } else { + SDL_LogError( + SDL_LOG_CATEGORY_GPU, + "VULKAN_INTERNAL_CreateInstance: Failed to parse requested API minor version. Expected positive integer. Got '%s'.", + hint); + SDL_SetError( + "VULKAN_INTERNAL_CreateInstance: Failed to parse requested API minor version. Expected positive integer. Got '%s'.", + hint); + return 0; + } + } else { + SDL_LogError( + SDL_LOG_CATEGORY_GPU, + "VULKAN_INTERNAL_CreateInstance: Requested API minor version was empty."); + SDL_SetError( + "VULKAN_INTERNAL_CreateInstance: Requested API minor version was empty."); + return 0; + } } createFlags = 0; From 90926f7c84d41b7e4967102e683921231c287514 Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Fri, 10 Oct 2025 23:08:08 +0200 Subject: [PATCH 04/24] Update include/SDL3/SDL_hints.h Co-authored-by: Sam Lantinga --- include/SDL3/SDL_hints.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index bf593db2d5d32..a4848de1ef1bb 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -4086,7 +4086,7 @@ extern "C" { * This hint should be set before creating a Vulkan window. Expects a positive * integer. E.g. 3 for Vulkan 1.3. * - * \since This hint is available since SDL 3.xx. + * \since This hint is available since SDL 3.4.0. */ #define SDL_HINT_VULKAN_REQUEST_API_MINOR_VERSION "SDL_VULKAN_REQUEST_API_MINOR_VERSION" From db9532a154daa9c409498144dc7c779a481de366 Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Fri, 10 Oct 2025 23:07:59 +0200 Subject: [PATCH 05/24] Revert "Change hint to only specify minor version" This reverts commit 6c4d2c004f079edf9f01dd9a99a875e8b2b38b39. --- include/SDL3/SDL_hints.h | 8 +++---- src/gpu/vulkan/SDL_gpu_vulkan.c | 41 +++++++++++---------------------- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index a4848de1ef1bb..740094d386da5 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -4081,14 +4081,14 @@ extern "C" { #define SDL_HINT_VULKAN_LIBRARY "SDL_VULKAN_LIBRARY" /** - * Specify the Vulkan API minor version to create the instance with. + * Specify the Vulkan API version to create the instance with. * - * This hint should be set before creating a Vulkan window. Expects a positive - * integer. E.g. 3 for Vulkan 1.3. + * This hint should be set before creating a Vulkan window. Expects a + * version string delimited by underscores. E.g. 1_0_0 or 1_3_0 * * \since This hint is available since SDL 3.4.0. */ -#define SDL_HINT_VULKAN_REQUEST_API_MINOR_VERSION "SDL_VULKAN_REQUEST_API_MINOR_VERSION" +#define SDL_HINT_VULKAN_REQUEST_API_VERSION "SDL_VULKAN_REQUEST_API_VERSION" /** * A variable controlling how the fact chunk affects the loading of a WAVE diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index 87161ea21498f..5d080c84a80f3 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -11205,34 +11205,21 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) appInfo.pEngineName = "SDLGPU"; appInfo.engineVersion = SDL_VERSION; appInfo.apiVersion = VK_MAKE_VERSION(1, 0, 0); - - // Handle application requesting a specific Vulkan API minor version - const char *hint = SDL_GetHint(SDL_HINT_VULKAN_REQUEST_API_MINOR_VERSION); + const char *hint = SDL_GetHint(SDL_HINT_VULKAN_REQUEST_API_VERSION); if (hint) { - size_t len = SDL_strnlen(hint, 16); - if (len > 0) { - char *endptr = NULL; - long minor = SDL_strtol(hint, &endptr, 10); - if (minor >= 0 && endptr == hint + len) { - appInfo.apiVersion = VK_MAKE_VERSION(1, minor, 0); - } else { - SDL_LogError( - SDL_LOG_CATEGORY_GPU, - "VULKAN_INTERNAL_CreateInstance: Failed to parse requested API minor version. Expected positive integer. Got '%s'.", - hint); - SDL_SetError( - "VULKAN_INTERNAL_CreateInstance: Failed to parse requested API minor version. Expected positive integer. Got '%s'.", - hint); - return 0; - } - } else { - SDL_LogError( - SDL_LOG_CATEGORY_GPU, - "VULKAN_INTERNAL_CreateInstance: Requested API minor version was empty."); - SDL_SetError( - "VULKAN_INTERNAL_CreateInstance: Requested API minor version was empty."); - return 0; - } + char *text = SDL_strdup(hint); + int numFound = 0; + int version[3] = { 0, 0, 0 }; + char *saveptr = NULL; + char *token = SDL_strtok_r(text, "_", &saveptr); + while (token != NULL && numFound < 3) { + version[numFound] = SDL_atoi(token); + numFound++; + token = SDL_strtok_r(NULL, "_", &saveptr); + } + + appInfo.apiVersion = VK_MAKE_VERSION(version[0], version[1], version[2]); + SDL_free(text); } createFlags = 0; From 4b66c161144f24d1b6b80020b1113eb3a85f1244 Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Fri, 10 Oct 2025 23:28:42 +0200 Subject: [PATCH 06/24] Handle errors when parsing hint --- include/SDL3/SDL_hints.h | 1 + src/gpu/vulkan/SDL_gpu_vulkan.c | 40 ++++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 740094d386da5..0c870cd0532e4 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -4085,6 +4085,7 @@ extern "C" { * * This hint should be set before creating a Vulkan window. Expects a * version string delimited by underscores. E.g. 1_0_0 or 1_3_0 + * Lowest supported version is 1_0_0. * * \since This hint is available since SDL 3.4.0. */ diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index 5d080c84a80f3..41c4cb30ff668 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -11207,19 +11207,33 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) appInfo.apiVersion = VK_MAKE_VERSION(1, 0, 0); const char *hint = SDL_GetHint(SDL_HINT_VULKAN_REQUEST_API_VERSION); if (hint) { - char *text = SDL_strdup(hint); - int numFound = 0; - int version[3] = { 0, 0, 0 }; - char *saveptr = NULL; - char *token = SDL_strtok_r(text, "_", &saveptr); - while (token != NULL && numFound < 3) { - version[numFound] = SDL_atoi(token); - numFound++; - token = SDL_strtok_r(NULL, "_", &saveptr); - } - - appInfo.apiVersion = VK_MAKE_VERSION(version[0], version[1], version[2]); - SDL_free(text); + int major, minor, patch; + if (SDL_sscanf(hint, "%d_%d_%d", &major, &minor, &patch) == 3) { + bool isValid = major >= 1 && + minor >= 0 && + patch >= 0; + if (isValid) { + appInfo.apiVersion = VK_MAKE_VERSION(major, minor, patch); + } else { + SDL_LogError( + SDL_LOG_CATEGORY_GPU, + "VULKAN_INTERNAL_CreateInstance: Requested Vulkan API version was invalid. Must be at least 1.0.0. Got '%s'", + hint); + SDL_SetError( + "VULKAN_INTERNAL_CreateInstance: Requested Vulkan API version was invalid. Must be at least 1.0.0. Got '%s'", + hint); + return 0; + } + } else { + SDL_LogError( + SDL_LOG_CATEGORY_GPU, + "VULKAN_INTERNAL_CreateInstance: Failed to parse requested Vulkan API version. Expected 'MAJOR_MINOR_PATCH'. Got '%s'", + hint); + SDL_SetError( + "VULKAN_INTERNAL_CreateInstance: Failed to parse requested Vulkan API version. Expected 'MAJOR_MINOR_PATCH'. Got '%s'", + hint); + return 0; + } } createFlags = 0; From 1721814f0561b8bf24c5324590eb046960b693e6 Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Sat, 11 Oct 2025 14:23:49 +0200 Subject: [PATCH 07/24] Use standard version string format --- include/SDL3/SDL_hints.h | 4 ++-- src/gpu/vulkan/SDL_gpu_vulkan.c | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 0c870cd0532e4..f92eb9e548f42 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -4084,8 +4084,8 @@ extern "C" { * Specify the Vulkan API version to create the instance with. * * This hint should be set before creating a Vulkan window. Expects a - * version string delimited by underscores. E.g. 1_0_0 or 1_3_0 - * Lowest supported version is 1_0_0. + * version string. E.g. 1.0.0 or 1.3.0 + * Lowest supported version is 1.0.0. * * \since This hint is available since SDL 3.4.0. */ diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index 41c4cb30ff668..cab7f4f40c5db 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -11208,7 +11208,7 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) const char *hint = SDL_GetHint(SDL_HINT_VULKAN_REQUEST_API_VERSION); if (hint) { int major, minor, patch; - if (SDL_sscanf(hint, "%d_%d_%d", &major, &minor, &patch) == 3) { + if (SDL_sscanf(hint, "%d.%d.%d", &major, &minor, &patch) == 3) { bool isValid = major >= 1 && minor >= 0 && patch >= 0; @@ -11227,10 +11227,10 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) } else { SDL_LogError( SDL_LOG_CATEGORY_GPU, - "VULKAN_INTERNAL_CreateInstance: Failed to parse requested Vulkan API version. Expected 'MAJOR_MINOR_PATCH'. Got '%s'", + "VULKAN_INTERNAL_CreateInstance: Failed to parse requested Vulkan API version. Expected 'MAJOR.MINOR.PATCH'. Got '%s'", hint); SDL_SetError( - "VULKAN_INTERNAL_CreateInstance: Failed to parse requested Vulkan API version. Expected 'MAJOR_MINOR_PATCH'. Got '%s'", + "VULKAN_INTERNAL_CreateInstance: Failed to parse requested Vulkan API version. Expected 'MAJOR.MINOR.PATCH'. Got '%s'", hint); return 0; } From 0310602801d1c3e77100a9078dba95fbe5f77fdc Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Sat, 11 Oct 2025 17:03:54 +0200 Subject: [PATCH 08/24] Allow requesting additional Vulkan device features Introduces a new property to use when creating a Vulkan renderer. SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER --- include/SDL3/SDL_gpu.h | 11 ++++++++++- src/gpu/vulkan/SDL_gpu_vulkan.c | 29 ++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index 18ee000869629..e92416beb553c 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -2306,7 +2306,15 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice( * useful for targeting Intel Haswell and Broadwell GPUs; other hardware * either supports Tier 2 Resource Binding or does not support D3D12 in any * capacity. Defaults to false. - * + * + * With the Vulkan renderer: + * + * - `SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER`: pointer + * to a Vulkan structure to be appended to SDL's VkDeviceCreateInfo during + * device creation. + * This allows passing a list of VkPhysicalDeviceFeature structures to + * opt-into features aside from the minimal set SDL requires. + * * \param props the properties to use. * \returns a GPU context on success or NULL on failure; call SDL_GetError() * for more information. @@ -2337,6 +2345,7 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDeviceWithProperties( #define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN "SDL.gpu.device.create.shaders.metallib" #define SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN "SDL.gpu.device.create.d3d12.allowtier1resourcebinding" #define SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING "SDL.gpu.device.create.d3d12.semantic" +#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER "SDL.gpu.device.create.vulkan.additional_features" /** * Destroys a GPU context previously returned by SDL_CreateGPUDevice. diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index cab7f4f40c5db..d1534ad3e979c 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -11694,7 +11694,19 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( deviceCreateInfo.enabledExtensionCount); CreateDeviceExtensionArray(&renderer->supports, deviceExtensions); deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions; - deviceCreateInfo.pEnabledFeatures = &renderer->desiredDeviceFeatures; + + // Look for opt-in features + VkPhysicalDeviceFeatures2 featureList; + if (SDL_HasProperty(renderer->props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER)) { + deviceCreateInfo.pEnabledFeatures = NULL; + deviceCreateInfo.pNext = &featureList; + + featureList.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + featureList.features = renderer->desiredDeviceFeatures; + featureList.pNext = SDL_GetPointerProperty(renderer->props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER, NULL); + } else { + deviceCreateInfo.pEnabledFeatures = &renderer->desiredDeviceFeatures; + } vulkanResult = renderer->vkCreateDevice( renderer->physicalDevice, @@ -11966,6 +11978,21 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S } } + // Pass opt-in device features to renderer creation + if (SDL_HasProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER)) { + void *ptr = SDL_GetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER, NULL); + if (!SDL_SetPointerProperty(renderer->props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER, ptr)) { + SDL_LogError( + SDL_LOG_CATEGORY_GPU, + "VULKAN_CreateDevice: Failed to set additional Vulkan features: %s", + SDL_GetError()); + SDL_SetError( + "VULKAN_CreateDevice: Failed to set additional Vulkan features: %s", + SDL_GetError()); + return NULL; + } + } + if (!VULKAN_INTERNAL_CreateLogicalDevice( renderer)) { SET_STRING_ERROR("Failed to create logical device!"); From 0bddc9c9365c98c682eeca195612654258972760 Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Sat, 11 Oct 2025 17:07:51 +0200 Subject: [PATCH 09/24] Rename VulkanRenderer.desiredDeviceFeatures Since the developer can now pass additional features to be activated, this name makes more obvious that these get enabled by SDL regardless. --- src/gpu/vulkan/SDL_gpu_vulkan.c | 52 ++++++++++++++++----------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index d1534ad3e979c..d19e3c52d3197 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -1101,7 +1101,7 @@ struct VulkanRenderer VkPhysicalDevice physicalDevice; VkPhysicalDeviceProperties2KHR physicalDeviceProperties; VkPhysicalDeviceDriverPropertiesKHR physicalDeviceDriverProperties; - VkPhysicalDeviceFeatures desiredDeviceFeatures; + VkPhysicalDeviceFeatures desiredBaselineDeviceFeatures; VkDevice logicalDevice; Uint8 integratedMemoryNotification; Uint8 outOfDeviceLocalMemoryWarning; @@ -11378,13 +11378,13 @@ static Uint8 VULKAN_INTERNAL_IsDeviceSuitable( physicalDevice, &deviceFeatures); - if ((!deviceFeatures.independentBlend && renderer->desiredDeviceFeatures.independentBlend) || - (!deviceFeatures.imageCubeArray && renderer->desiredDeviceFeatures.imageCubeArray) || - (!deviceFeatures.depthClamp && renderer->desiredDeviceFeatures.depthClamp) || - (!deviceFeatures.shaderClipDistance && renderer->desiredDeviceFeatures.shaderClipDistance) || - (!deviceFeatures.drawIndirectFirstInstance && renderer->desiredDeviceFeatures.drawIndirectFirstInstance) || - (!deviceFeatures.sampleRateShading && renderer->desiredDeviceFeatures.sampleRateShading) || - (!deviceFeatures.samplerAnisotropy && renderer->desiredDeviceFeatures.samplerAnisotropy)) { + if ((!deviceFeatures.independentBlend && renderer->desiredBaselineDeviceFeatures.independentBlend) || + (!deviceFeatures.imageCubeArray && renderer->desiredBaselineDeviceFeatures.imageCubeArray) || + (!deviceFeatures.depthClamp && renderer->desiredBaselineDeviceFeatures.depthClamp) || + (!deviceFeatures.shaderClipDistance && renderer->desiredBaselineDeviceFeatures.shaderClipDistance) || + (!deviceFeatures.drawIndirectFirstInstance && renderer->desiredBaselineDeviceFeatures.drawIndirectFirstInstance) || + (!deviceFeatures.sampleRateShading && renderer->desiredBaselineDeviceFeatures.sampleRateShading) || + (!deviceFeatures.samplerAnisotropy && renderer->desiredBaselineDeviceFeatures.samplerAnisotropy)) { return 0; } @@ -11648,12 +11648,12 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( // specifying used device features if (haveDeviceFeatures.fillModeNonSolid) { - renderer->desiredDeviceFeatures.fillModeNonSolid = VK_TRUE; + renderer->desiredBaselineDeviceFeatures.fillModeNonSolid = VK_TRUE; renderer->supportsFillModeNonSolid = true; } if (haveDeviceFeatures.multiDrawIndirect) { - renderer->desiredDeviceFeatures.multiDrawIndirect = VK_TRUE; + renderer->desiredBaselineDeviceFeatures.multiDrawIndirect = VK_TRUE; renderer->supportsMultiDrawIndirect = true; } @@ -11702,10 +11702,10 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( deviceCreateInfo.pNext = &featureList; featureList.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; - featureList.features = renderer->desiredDeviceFeatures; + featureList.features = renderer->desiredBaselineDeviceFeatures; featureList.pNext = SDL_GetPointerProperty(renderer->props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER, NULL); } else { - deviceCreateInfo.pEnabledFeatures = &renderer->desiredDeviceFeatures; + deviceCreateInfo.pEnabledFeatures = &renderer->desiredBaselineDeviceFeatures; } vulkanResult = renderer->vkCreateDevice( @@ -11812,15 +11812,15 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) renderer = (VulkanRenderer *)SDL_calloc(1, sizeof(*renderer)); if (renderer) { // Opt out device features (higher compatibility in exchange for reduced functionality) - renderer->desiredDeviceFeatures.samplerAnisotropy = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN, true) ? VK_TRUE : VK_FALSE; - renderer->desiredDeviceFeatures.depthClamp = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN, true) ? VK_TRUE : VK_FALSE; - renderer->desiredDeviceFeatures.shaderClipDistance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; - renderer->desiredDeviceFeatures.drawIndirectFirstInstance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredBaselineDeviceFeatures.samplerAnisotropy = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredBaselineDeviceFeatures.depthClamp = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredBaselineDeviceFeatures.shaderClipDistance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredBaselineDeviceFeatures.drawIndirectFirstInstance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; // These features have near universal support so they are always enabled - renderer->desiredDeviceFeatures.independentBlend = VK_TRUE; - renderer->desiredDeviceFeatures.sampleRateShading = VK_TRUE; - renderer->desiredDeviceFeatures.imageCubeArray = VK_TRUE; + renderer->desiredBaselineDeviceFeatures.independentBlend = VK_TRUE; + renderer->desiredBaselineDeviceFeatures.sampleRateShading = VK_TRUE; + renderer->desiredBaselineDeviceFeatures.imageCubeArray = VK_TRUE; result = VULKAN_INTERNAL_PrepareVulkan(renderer); if (result) { @@ -11861,15 +11861,15 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer->allowedFramesInFlight = 2; // Opt out device features (higher compatibility in exchange for reduced functionality) - renderer->desiredDeviceFeatures.samplerAnisotropy = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN, true) ? VK_TRUE : VK_FALSE; - renderer->desiredDeviceFeatures.depthClamp = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN, true) ? VK_TRUE : VK_FALSE; - renderer->desiredDeviceFeatures.shaderClipDistance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; - renderer->desiredDeviceFeatures.drawIndirectFirstInstance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredBaselineDeviceFeatures.samplerAnisotropy = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredBaselineDeviceFeatures.depthClamp = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredBaselineDeviceFeatures.shaderClipDistance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredBaselineDeviceFeatures.drawIndirectFirstInstance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; // These features have near universal support so they are always enabled - renderer->desiredDeviceFeatures.independentBlend = VK_TRUE; - renderer->desiredDeviceFeatures.sampleRateShading = VK_TRUE; - renderer->desiredDeviceFeatures.imageCubeArray = VK_TRUE; + renderer->desiredBaselineDeviceFeatures.independentBlend = VK_TRUE; + renderer->desiredBaselineDeviceFeatures.sampleRateShading = VK_TRUE; + renderer->desiredBaselineDeviceFeatures.imageCubeArray = VK_TRUE; if (!VULKAN_INTERNAL_PrepareVulkan(renderer)) { SET_STRING_ERROR("Failed to initialize Vulkan!"); From 962026f489c0633a0aba08551c65386530392c5a Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Sat, 11 Oct 2025 17:56:51 +0200 Subject: [PATCH 10/24] Handle multiple chaining of structures KHR_portability_subset structure was being overwritten by opt-in features. --- src/gpu/vulkan/SDL_gpu_vulkan.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index d19e3c52d3197..abc3feb23ac6e 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -11699,7 +11699,14 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( VkPhysicalDeviceFeatures2 featureList; if (SDL_HasProperty(renderer->props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER)) { deviceCreateInfo.pEnabledFeatures = NULL; - deviceCreateInfo.pNext = &featureList; + + // Walk the list of structures + VkBaseOutStructure *nextPtr = (VkBaseOutStructure *)&deviceCreateInfo; + while (nextPtr->pNext) { + nextPtr = (VkBaseOutStructure *)nextPtr->pNext; + } + + nextPtr->pNext = &featureList; featureList.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; featureList.features = renderer->desiredBaselineDeviceFeatures; From 9cd9bbd050927ef05a0b6ba6d93a50a125858328 Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Sun, 12 Oct 2025 17:39:14 +0200 Subject: [PATCH 11/24] Add Vulkan options property Also replaces the previously created hint --- include/SDL3/SDL_gpu.h | 23 ++ include/SDL3/SDL_hints.h | 11 - src/gpu/vulkan/SDL_gpu_vulkan.c | 454 +++++++++++++++++++----- src/gpu/vulkan/SDL_gpu_vulkan_vkfuncs.h | 3 + 4 files changed, 392 insertions(+), 99 deletions(-) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index e92416beb553c..ad75b56e603e7 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -2346,6 +2346,29 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDeviceWithProperties( #define SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN "SDL.gpu.device.create.d3d12.allowtier1resourcebinding" #define SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING "SDL.gpu.device.create.d3d12.semantic" #define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER "SDL.gpu.device.create.vulkan.additional_features" +#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER "SDL.gpu.device.create.vulkan.options" + + + + +/** + * A structure specifying additional options when using Vulkan. + * + * When no such structure is provided, SDL will use Vulkan API version 1.0 and a minimal set of features. + * The feature list is only allowed to contain the following structures: + * - VkPhysicalDeviceFeatures2 + * - VkPhysicalDeviceVulkan11Features + * - VkPhysicalDeviceVulkan12Features + * - VkPhysicalDeviceVulkan13Features + * + * \since This struct is available since SDL 3.4.0. + * + */ +typedef struct SDL_GPUVulkanOptions +{ + Uint32 vulkan_api_version; /**< The Vulkan API version to request for the instance. Use Vulkan's VK_MAKE_VERSION or VK_MAKE_API_VERSION. */ + void *feature_list; /**< Pointer to the first element of a list of Vulkan physical device feature structs. */ +} SDL_GPUVulkanOptions; /** * Destroys a GPU context previously returned by SDL_CreateGPUDevice. diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index f92eb9e548f42..331a83258aba1 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -4080,17 +4080,6 @@ extern "C" { */ #define SDL_HINT_VULKAN_LIBRARY "SDL_VULKAN_LIBRARY" -/** - * Specify the Vulkan API version to create the instance with. - * - * This hint should be set before creating a Vulkan window. Expects a - * version string. E.g. 1.0.0 or 1.3.0 - * Lowest supported version is 1.0.0. - * - * \since This hint is available since SDL 3.4.0. - */ -#define SDL_HINT_VULKAN_REQUEST_API_VERSION "SDL_VULKAN_REQUEST_API_VERSION" - /** * A variable controlling how the fact chunk affects the loading of a WAVE * file. diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index abc3feb23ac6e..e1c8a41cfa720 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -1101,13 +1101,19 @@ struct VulkanRenderer VkPhysicalDevice physicalDevice; VkPhysicalDeviceProperties2KHR physicalDeviceProperties; VkPhysicalDeviceDriverPropertiesKHR physicalDeviceDriverProperties; - VkPhysicalDeviceFeatures desiredBaselineDeviceFeatures; + VkPhysicalDeviceFeatures desiredVulkan10DeviceFeatures; VkDevice logicalDevice; Uint8 integratedMemoryNotification; Uint8 outOfDeviceLocalMemoryWarning; Uint8 outofBARMemoryWarning; Uint8 fillModeOnlyWarning; + bool usesCustomVulkanOptions; + Uint32 desiredApiVersion; + VkPhysicalDeviceVulkan11Features desiredVulkan11DeviceFeatures; + VkPhysicalDeviceVulkan12Features desiredVulkan12DeviceFeatures; + VkPhysicalDeviceVulkan13Features desiredVulkan13DeviceFeatures; + bool debugMode; bool preferLowPower; SDL_PropertiesID props; @@ -11187,6 +11193,281 @@ static Uint8 VULKAN_INTERNAL_CheckValidationLayers( return layerFound; } +#define CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, feature, result) \ + if (requested->feature && !supported->feature) { \ + SDL_LogVerbose( \ + SDL_LOG_CATEGORY_GPU, \ + "SDL GPU Vulkan: Application requested unsupported physical device feature '" #feature "'"); \ + result = false; \ + } + +static bool VULKAN_INTERNAL_ValidateOptInVulkan10Features(VkPhysicalDeviceFeatures *requested, VkPhysicalDeviceFeatures *supported) +{ + if (requested && supported) { + bool result = true; + + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, robustBufferAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, fullDrawIndexUint32, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, imageCubeArray, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, independentBlend, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, geometryShader, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, tessellationShader, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sampleRateShading, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, dualSrcBlend, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, logicOp, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, multiDrawIndirect, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, drawIndirectFirstInstance, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, depthClamp, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, depthBiasClamp, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, fillModeNonSolid, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, depthBounds, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, wideLines, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, largePoints, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, alphaToOne, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, multiViewport, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, samplerAnisotropy, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, textureCompressionETC2, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, textureCompressionASTC_LDR, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, textureCompressionBC, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, occlusionQueryPrecise, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, pipelineStatisticsQuery, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, vertexPipelineStoresAndAtomics, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, fragmentStoresAndAtomics, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderTessellationAndGeometryPointSize, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderImageGatherExtended, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageExtendedFormats, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageMultisample, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageReadWithoutFormat, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageWriteWithoutFormat, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderUniformBufferArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderSampledImageArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageBufferArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderClipDistance, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderCullDistance, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderFloat64, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderInt64, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderInt16, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderResourceResidency, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderResourceMinLod, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseBinding, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidencyBuffer, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidencyImage2D, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidencyImage3D, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidency2Samples, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidency4Samples, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidency8Samples, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidency16Samples, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, sparseResidencyAliased, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, variableMultisampleRate, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, inheritedQueries, result) + + return result; + } else { + return false; + } +} + +static bool VULKAN_INTERNAL_ValidateOptInVulkan11Features(VkPhysicalDeviceVulkan11Features *requested, VkPhysicalDeviceVulkan11Features *supported) +{ + if (requested && supported) { + bool result = true; + + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, storageBuffer16BitAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, uniformAndStorageBuffer16BitAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, storagePushConstant16, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, storageInputOutput16, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, multiview, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, multiviewGeometryShader, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, multiviewTessellationShader, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, variablePointersStorageBuffer, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, variablePointers, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, protectedMemory, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, samplerYcbcrConversion, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderDrawParameters, result) + + return result; + } else { + return false; + } +} + +static bool VULKAN_INTERNAL_ValidateOptInVulkan12Features(VkPhysicalDeviceVulkan12Features *requested, VkPhysicalDeviceVulkan12Features *supported) +{ + if (requested && supported) { + bool result = true; + + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, samplerMirrorClampToEdge, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, drawIndirectCount, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, storageBuffer8BitAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, uniformAndStorageBuffer8BitAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, storagePushConstant8, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderBufferInt64Atomics, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderSharedInt64Atomics, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderFloat16, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderInt8, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderInputAttachmentArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderUniformTexelBufferArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageTexelBufferArrayDynamicIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderUniformBufferArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderSampledImageArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageBufferArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageImageArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderInputAttachmentArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderUniformTexelBufferArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderStorageTexelBufferArrayNonUniformIndexing, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingUniformBufferUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingSampledImageUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingStorageImageUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingStorageBufferUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingUniformTexelBufferUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingStorageTexelBufferUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingUpdateUnusedWhilePending, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingPartiallyBound, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingVariableDescriptorCount, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, runtimeDescriptorArray, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, samplerFilterMinmax, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, scalarBlockLayout, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, imagelessFramebuffer, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, uniformBufferStandardLayout, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderSubgroupExtendedTypes, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, separateDepthStencilLayouts, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, hostQueryReset, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, timelineSemaphore, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, bufferDeviceAddress, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, bufferDeviceAddressCaptureReplay, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, bufferDeviceAddressMultiDevice, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, vulkanMemoryModel, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, vulkanMemoryModelDeviceScope, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, vulkanMemoryModelAvailabilityVisibilityChains, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderOutputViewportIndex, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderOutputLayer, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, subgroupBroadcastDynamicId, result) + + return result; + } else { + return false; + } +} + +static bool VULKAN_INTERNAL_ValidateOptInVulkan13Features(VkPhysicalDeviceVulkan13Features *requested, VkPhysicalDeviceVulkan13Features *supported) +{ + if (requested && supported) { + bool result = true; + + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, robustImageAccess, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, inlineUniformBlock, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, descriptorBindingInlineUniformBlockUpdateAfterBind, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, pipelineCreationCacheControl, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, privateData, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderDemoteToHelperInvocation, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderTerminateInvocation, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, subgroupSizeControl, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, computeFullSubgroups, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, synchronization2, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, textureCompressionASTC_HDR, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderZeroInitializeWorkgroupMemory, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, dynamicRendering, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, shaderIntegerDotProduct, result) + CHECK_OPTIONAL_DEVICE_FEATURE(requested, supported, maintenance4, result) + + return result; + } else { + return false; + } +} + +#undef CHECK_OPTIONAL_DEVICE_FEATURE + +static void VULKAN_INTERNAL_AddDeviceFeatures(VkBool32 *firstFeature, VkBool32 *lastFeature, VkBool32 *firstFeatureToAdd) +{ + while (firstFeature <= lastFeature) { + *firstFeature = (*firstFeature | *firstFeatureToAdd); + firstFeature++; + firstFeatureToAdd++; + } +} + +static bool VULKAN_INTERNAL_AddOptInVulkanOptions(SDL_PropertiesID props, VulkanRenderer *renderer) +{ + if (SDL_HasProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER)) { + SDL_GPUVulkanOptions *options = (SDL_GPUVulkanOptions *)SDL_GetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER, NULL); + if (options) { + renderer->usesCustomVulkanOptions = true; + renderer->desiredApiVersion = options->vulkan_api_version; + renderer->desiredVulkan11DeviceFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; + renderer->desiredVulkan12DeviceFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + renderer->desiredVulkan13DeviceFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + + if (options->feature_list) { + VkPhysicalDeviceFeatures *vk10Features = &renderer->desiredVulkan10DeviceFeatures; + VkPhysicalDeviceVulkan11Features *vk11Features = &renderer->desiredVulkan11DeviceFeatures; + VkPhysicalDeviceVulkan12Features *vk12Features = &renderer->desiredVulkan12DeviceFeatures; + VkPhysicalDeviceVulkan13Features *vk13Features = &renderer->desiredVulkan13DeviceFeatures; + + // Iterate through the entire list and combine all requested features + VkBaseOutStructure *nextStructure = (VkBaseOutStructure *)options->feature_list; + while (nextStructure) { + switch (nextStructure->sType) { + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2: + { + VkPhysicalDeviceFeatures2 *newFeatures = (VkPhysicalDeviceFeatures2 *)nextStructure; + VULKAN_INTERNAL_AddDeviceFeatures(&vk10Features->robustBufferAccess, + &vk10Features->inheritedQueries, + &newFeatures->features.robustBufferAccess); + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES: + { + VkPhysicalDeviceVulkan11Features *newFeatures = (VkPhysicalDeviceVulkan11Features *)nextStructure; + VULKAN_INTERNAL_AddDeviceFeatures(&vk11Features->storageBuffer16BitAccess, + &vk11Features->shaderDrawParameters, + &newFeatures->storageBuffer16BitAccess); + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES: + { + VkPhysicalDeviceVulkan12Features *newFeatures = (VkPhysicalDeviceVulkan12Features *)nextStructure; + VULKAN_INTERNAL_AddDeviceFeatures(&vk12Features->samplerMirrorClampToEdge, + &vk12Features->subgroupBroadcastDynamicId, + &newFeatures->samplerMirrorClampToEdge); + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES: + { + VkPhysicalDeviceVulkan13Features *newFeatures = (VkPhysicalDeviceVulkan13Features *)nextStructure; + VULKAN_INTERNAL_AddDeviceFeatures(&vk13Features->robustImageAccess, + &vk13Features->maintenance4, + &newFeatures->robustImageAccess); + } break; + + default: + { + // It's very important that this routine aborts when an unhandled type is encountered. + // Not all Vulkan structures (especially Vulkan 1.0 ones) cleanly cast to VkBaseOutStructure. + // If we don't bail out, nextStructure would get set to some garbage memory location and + // this loop would do very bad things. + SDL_LogError( + SDL_LOG_CATEGORY_GPU, + "Unhandled Vulkan structure type in requested device feature list! %d", + nextStructure->sType); + SDL_SetError( + "Unhandled Vulkan structure type in requested device feature list! %d", + nextStructure->sType); + return false; + } + } + + nextStructure = nextStructure->pNext; + } + } + } + } + + return true; +} + static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) { VkResult vulkanResult; @@ -11204,37 +11485,9 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) appInfo.applicationVersion = 0; appInfo.pEngineName = "SDLGPU"; appInfo.engineVersion = SDL_VERSION; - appInfo.apiVersion = VK_MAKE_VERSION(1, 0, 0); - const char *hint = SDL_GetHint(SDL_HINT_VULKAN_REQUEST_API_VERSION); - if (hint) { - int major, minor, patch; - if (SDL_sscanf(hint, "%d.%d.%d", &major, &minor, &patch) == 3) { - bool isValid = major >= 1 && - minor >= 0 && - patch >= 0; - if (isValid) { - appInfo.apiVersion = VK_MAKE_VERSION(major, minor, patch); - } else { - SDL_LogError( - SDL_LOG_CATEGORY_GPU, - "VULKAN_INTERNAL_CreateInstance: Requested Vulkan API version was invalid. Must be at least 1.0.0. Got '%s'", - hint); - SDL_SetError( - "VULKAN_INTERNAL_CreateInstance: Requested Vulkan API version was invalid. Must be at least 1.0.0. Got '%s'", - hint); - return 0; - } - } else { - SDL_LogError( - SDL_LOG_CATEGORY_GPU, - "VULKAN_INTERNAL_CreateInstance: Failed to parse requested Vulkan API version. Expected 'MAJOR.MINOR.PATCH'. Got '%s'", - hint); - SDL_SetError( - "VULKAN_INTERNAL_CreateInstance: Failed to parse requested Vulkan API version. Expected 'MAJOR.MINOR.PATCH'. Got '%s'", - hint); - return 0; - } - } + appInfo.apiVersion = renderer->usesCustomVulkanOptions + ? renderer->desiredApiVersion + : VK_MAKE_VERSION(1, 0, 0); createFlags = 0; @@ -11378,16 +11631,48 @@ static Uint8 VULKAN_INTERNAL_IsDeviceSuitable( physicalDevice, &deviceFeatures); - if ((!deviceFeatures.independentBlend && renderer->desiredBaselineDeviceFeatures.independentBlend) || - (!deviceFeatures.imageCubeArray && renderer->desiredBaselineDeviceFeatures.imageCubeArray) || - (!deviceFeatures.depthClamp && renderer->desiredBaselineDeviceFeatures.depthClamp) || - (!deviceFeatures.shaderClipDistance && renderer->desiredBaselineDeviceFeatures.shaderClipDistance) || - (!deviceFeatures.drawIndirectFirstInstance && renderer->desiredBaselineDeviceFeatures.drawIndirectFirstInstance) || - (!deviceFeatures.sampleRateShading && renderer->desiredBaselineDeviceFeatures.sampleRateShading) || - (!deviceFeatures.samplerAnisotropy && renderer->desiredBaselineDeviceFeatures.samplerAnisotropy)) { + if ((!deviceFeatures.independentBlend && renderer->desiredVulkan10DeviceFeatures.independentBlend) || + (!deviceFeatures.imageCubeArray && renderer->desiredVulkan10DeviceFeatures.imageCubeArray) || + (!deviceFeatures.depthClamp && renderer->desiredVulkan10DeviceFeatures.depthClamp) || + (!deviceFeatures.shaderClipDistance && renderer->desiredVulkan10DeviceFeatures.shaderClipDistance) || + (!deviceFeatures.drawIndirectFirstInstance && renderer->desiredVulkan10DeviceFeatures.drawIndirectFirstInstance) || + (!deviceFeatures.sampleRateShading && renderer->desiredVulkan10DeviceFeatures.sampleRateShading) || + (!deviceFeatures.samplerAnisotropy && renderer->desiredVulkan10DeviceFeatures.samplerAnisotropy)) { return 0; } + // Check opt-in device features + if (renderer->usesCustomVulkanOptions) { + VkPhysicalDeviceFeatures2 featureList = { 0 }; + featureList.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + + VkPhysicalDeviceVulkan11Features vk11Features = { 0 }; + vk11Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; + + VkPhysicalDeviceVulkan12Features vk12Features = { 0 }; + vk12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + + VkPhysicalDeviceVulkan13Features vk13Features = { 0 }; + vk13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + + featureList.pNext = &vk11Features; + vk11Features.pNext = &vk12Features; + vk12Features.pNext = &vk13Features; + + renderer->vkGetPhysicalDeviceFeatures2( + physicalDevice, + &featureList); + + bool supportsAllFeatures = VULKAN_INTERNAL_ValidateOptInVulkan10Features(&renderer->desiredVulkan10DeviceFeatures, &featureList.features); + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan11Features(&renderer->desiredVulkan11DeviceFeatures, &vk11Features); + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan12Features(&renderer->desiredVulkan12DeviceFeatures, &vk12Features); + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan13Features(&renderer->desiredVulkan13DeviceFeatures, &vk13Features); + + if (!supportsAllFeatures) { + return 0; + } + } + if (!VULKAN_INTERNAL_CheckDeviceExtensions( renderer, physicalDevice, @@ -11648,12 +11933,12 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( // specifying used device features if (haveDeviceFeatures.fillModeNonSolid) { - renderer->desiredBaselineDeviceFeatures.fillModeNonSolid = VK_TRUE; + renderer->desiredVulkan10DeviceFeatures.fillModeNonSolid = VK_TRUE; renderer->supportsFillModeNonSolid = true; } if (haveDeviceFeatures.multiDrawIndirect) { - renderer->desiredBaselineDeviceFeatures.multiDrawIndirect = VK_TRUE; + renderer->desiredVulkan10DeviceFeatures.multiDrawIndirect = VK_TRUE; renderer->supportsMultiDrawIndirect = true; } @@ -11695,24 +11980,18 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice( CreateDeviceExtensionArray(&renderer->supports, deviceExtensions); deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions; - // Look for opt-in features VkPhysicalDeviceFeatures2 featureList; - if (SDL_HasProperty(renderer->props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER)) { - deviceCreateInfo.pEnabledFeatures = NULL; - - // Walk the list of structures - VkBaseOutStructure *nextPtr = (VkBaseOutStructure *)&deviceCreateInfo; - while (nextPtr->pNext) { - nextPtr = (VkBaseOutStructure *)nextPtr->pNext; - } - - nextPtr->pNext = &featureList; - + if (renderer->usesCustomVulkanOptions) { featureList.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; - featureList.features = renderer->desiredBaselineDeviceFeatures; - featureList.pNext = SDL_GetPointerProperty(renderer->props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER, NULL); + featureList.features = renderer->desiredVulkan10DeviceFeatures; + featureList.pNext = &renderer->desiredVulkan11DeviceFeatures; + renderer->desiredVulkan11DeviceFeatures.pNext = &renderer->desiredVulkan12DeviceFeatures; + renderer->desiredVulkan12DeviceFeatures.pNext = &renderer->desiredVulkan13DeviceFeatures; + renderer->desiredVulkan13DeviceFeatures.pNext = (void *)deviceCreateInfo.pNext; + deviceCreateInfo.pEnabledFeatures = NULL; + deviceCreateInfo.pNext = &featureList; } else { - deviceCreateInfo.pEnabledFeatures = &renderer->desiredBaselineDeviceFeatures; + deviceCreateInfo.pEnabledFeatures = &renderer->desiredVulkan10DeviceFeatures; } vulkanResult = renderer->vkCreateDevice( @@ -11819,20 +12098,26 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) renderer = (VulkanRenderer *)SDL_calloc(1, sizeof(*renderer)); if (renderer) { // Opt out device features (higher compatibility in exchange for reduced functionality) - renderer->desiredBaselineDeviceFeatures.samplerAnisotropy = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN, true) ? VK_TRUE : VK_FALSE; - renderer->desiredBaselineDeviceFeatures.depthClamp = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN, true) ? VK_TRUE : VK_FALSE; - renderer->desiredBaselineDeviceFeatures.shaderClipDistance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; - renderer->desiredBaselineDeviceFeatures.drawIndirectFirstInstance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredVulkan10DeviceFeatures.samplerAnisotropy = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredVulkan10DeviceFeatures.depthClamp = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredVulkan10DeviceFeatures.shaderClipDistance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredVulkan10DeviceFeatures.drawIndirectFirstInstance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; // These features have near universal support so they are always enabled - renderer->desiredBaselineDeviceFeatures.independentBlend = VK_TRUE; - renderer->desiredBaselineDeviceFeatures.sampleRateShading = VK_TRUE; - renderer->desiredBaselineDeviceFeatures.imageCubeArray = VK_TRUE; + renderer->desiredVulkan10DeviceFeatures.independentBlend = VK_TRUE; + renderer->desiredVulkan10DeviceFeatures.sampleRateShading = VK_TRUE; + renderer->desiredVulkan10DeviceFeatures.imageCubeArray = VK_TRUE; + + // Handle opt-in device features + bool featuresInitialized = VULKAN_INTERNAL_AddOptInVulkanOptions(props, renderer); - result = VULKAN_INTERNAL_PrepareVulkan(renderer); - if (result) { - renderer->vkDestroyInstance(renderer->instance, NULL); + if (featuresInitialized) { + result = VULKAN_INTERNAL_PrepareVulkan(renderer); + if (result) { + renderer->vkDestroyInstance(renderer->instance, NULL); + } } + SDL_free(renderer); } SDL_Vulkan_UnloadLibrary(); @@ -11868,15 +12153,23 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer->allowedFramesInFlight = 2; // Opt out device features (higher compatibility in exchange for reduced functionality) - renderer->desiredBaselineDeviceFeatures.samplerAnisotropy = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN, true) ? VK_TRUE : VK_FALSE; - renderer->desiredBaselineDeviceFeatures.depthClamp = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN, true) ? VK_TRUE : VK_FALSE; - renderer->desiredBaselineDeviceFeatures.shaderClipDistance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; - renderer->desiredBaselineDeviceFeatures.drawIndirectFirstInstance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredVulkan10DeviceFeatures.samplerAnisotropy = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_ANISOTROPY_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredVulkan10DeviceFeatures.depthClamp = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredVulkan10DeviceFeatures.shaderClipDistance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; + renderer->desiredVulkan10DeviceFeatures.drawIndirectFirstInstance = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_INDIRECT_DRAW_FIRST_INSTANCE_BOOLEAN, true) ? VK_TRUE : VK_FALSE; // These features have near universal support so they are always enabled - renderer->desiredBaselineDeviceFeatures.independentBlend = VK_TRUE; - renderer->desiredBaselineDeviceFeatures.sampleRateShading = VK_TRUE; - renderer->desiredBaselineDeviceFeatures.imageCubeArray = VK_TRUE; + renderer->desiredVulkan10DeviceFeatures.independentBlend = VK_TRUE; + renderer->desiredVulkan10DeviceFeatures.sampleRateShading = VK_TRUE; + renderer->desiredVulkan10DeviceFeatures.imageCubeArray = VK_TRUE; + + // Handle opt-in device features + if (!VULKAN_INTERNAL_AddOptInVulkanOptions(props, renderer)) { + SET_STRING_ERROR("Failed to initialize additional Vulkan options!"); + SDL_free(renderer); + SDL_Vulkan_UnloadLibrary(); + return NULL; + } if (!VULKAN_INTERNAL_PrepareVulkan(renderer)) { SET_STRING_ERROR("Failed to initialize Vulkan!"); @@ -11985,21 +12278,6 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S } } - // Pass opt-in device features to renderer creation - if (SDL_HasProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER)) { - void *ptr = SDL_GetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER, NULL); - if (!SDL_SetPointerProperty(renderer->props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER, ptr)) { - SDL_LogError( - SDL_LOG_CATEGORY_GPU, - "VULKAN_CreateDevice: Failed to set additional Vulkan features: %s", - SDL_GetError()); - SDL_SetError( - "VULKAN_CreateDevice: Failed to set additional Vulkan features: %s", - SDL_GetError()); - return NULL; - } - } - if (!VULKAN_INTERNAL_CreateLogicalDevice( renderer)) { SET_STRING_ERROR("Failed to create logical device!"); diff --git a/src/gpu/vulkan/SDL_gpu_vulkan_vkfuncs.h b/src/gpu/vulkan/SDL_gpu_vulkan_vkfuncs.h index 7316eb9188759..aa7845fcd9323 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan_vkfuncs.h +++ b/src/gpu/vulkan/SDL_gpu_vulkan_vkfuncs.h @@ -51,6 +51,9 @@ VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceImageFormatProperties) VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceMemoryProperties) VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceProperties) +// Vulkan 1.1 (Needed for opt-in feature checks) +VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceFeatures2) + // VK_KHR_get_physical_device_properties2, needed for KHR_driver_properties VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceProperties2KHR) From 9dac2f6499b05edb4b62a70a216a848538c5da89 Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Mon, 13 Oct 2025 00:26:10 +0200 Subject: [PATCH 12/24] Handle Vulkan 1.1 features properly --- include/SDL3/SDL_gpu.h | 10 +- src/gpu/vulkan/SDL_gpu_vulkan.c | 201 +++++++++++++++++++++++--------- 2 files changed, 147 insertions(+), 64 deletions(-) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index ad75b56e603e7..1372b8f80382b 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -2355,11 +2355,8 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDeviceWithProperties( * A structure specifying additional options when using Vulkan. * * When no such structure is provided, SDL will use Vulkan API version 1.0 and a minimal set of features. - * The feature list is only allowed to contain the following structures: - * - VkPhysicalDeviceFeatures2 - * - VkPhysicalDeviceVulkan11Features - * - VkPhysicalDeviceVulkan12Features - * - VkPhysicalDeviceVulkan13Features + * The feature list gets passed to the vkCreateInstance function and allows requesting additional + * features. * * \since This struct is available since SDL 3.4.0. * @@ -2367,7 +2364,8 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDeviceWithProperties( typedef struct SDL_GPUVulkanOptions { Uint32 vulkan_api_version; /**< The Vulkan API version to request for the instance. Use Vulkan's VK_MAKE_VERSION or VK_MAKE_API_VERSION. */ - void *feature_list; /**< Pointer to the first element of a list of Vulkan physical device feature structs. */ + void *feature_list; /**< Pointer to the first element of a list of structs to be passed to device creation. */ + void *vulkan_10_physical_device_features; /**< Pointer to a VkPhysicalDeviceFeatures struct to enable additional Vulkan 1.0 features. */ } SDL_GPUVulkanOptions; /** diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index e1c8a41cfa720..ecce1f25f539b 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -11389,6 +11389,114 @@ static void VULKAN_INTERNAL_AddDeviceFeatures(VkBool32 *firstFeature, VkBool32 * } } +static bool VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_11(VkPhysicalDeviceFeatures *dst10, + VkPhysicalDeviceVulkan11Features *dst11, + VkBaseOutStructure *src) +{ + bool hasAdded = false; + switch (src->sType) { + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2: + { + VkPhysicalDeviceFeatures2 *newFeatures = (VkPhysicalDeviceFeatures2 *)src; + VULKAN_INTERNAL_AddDeviceFeatures(&dst10->robustBufferAccess, + &dst10->inheritedQueries, + &newFeatures->features.robustBufferAccess); + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES: + { + VkPhysicalDevice16BitStorageFeatures *newFeatures = (VkPhysicalDevice16BitStorageFeatures *)src; + dst11->storageBuffer16BitAccess |= newFeatures->storageBuffer16BitAccess; + dst11->uniformAndStorageBuffer16BitAccess |= newFeatures->uniformAndStorageBuffer16BitAccess; + dst11->storagePushConstant16 |= newFeatures->storagePushConstant16; + dst11->storageInputOutput16 |= newFeatures->storageInputOutput16; + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES: + { + VkPhysicalDeviceMultiviewFeatures *newFeatures = (VkPhysicalDeviceMultiviewFeatures *)src; + dst11->multiview |= newFeatures->multiview; + dst11->multiviewGeometryShader |= newFeatures->multiviewGeometryShader; + dst11->multiviewTessellationShader |= newFeatures->multiviewTessellationShader; + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES: + { + VkPhysicalDeviceProtectedMemoryFeatures *newFeatures = (VkPhysicalDeviceProtectedMemoryFeatures *)src; + dst11->protectedMemory |= newFeatures->protectedMemory; + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES: + { + VkPhysicalDeviceSamplerYcbcrConversionFeatures *newFeatures = (VkPhysicalDeviceSamplerYcbcrConversionFeatures *)src; + dst11->samplerYcbcrConversion |= newFeatures->samplerYcbcrConversion; + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES: + { + VkPhysicalDeviceShaderDrawParametersFeatures *newFeatures = (VkPhysicalDeviceShaderDrawParametersFeatures *)src; + dst11->shaderDrawParameters |= newFeatures->shaderDrawParameters; + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES: + { + VkPhysicalDeviceVariablePointersFeatures *newFeatures = (VkPhysicalDeviceVariablePointersFeatures *)src; + dst11->variablePointers |= newFeatures->variablePointers; + dst11->variablePointersStorageBuffer |= newFeatures->variablePointersStorageBuffer; + hasAdded = true; + } break; + } + + return hasAdded; +} + +static bool VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_12_Or_Later(VkPhysicalDeviceFeatures *dst10, + VkPhysicalDeviceVulkan11Features *dst11, + VkPhysicalDeviceVulkan12Features *dst12, + VkPhysicalDeviceVulkan13Features *dst13, + VkBaseOutStructure *src) +{ + bool hasAdded = VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_11(dst10, dst11, src); + if (!hasAdded) { + switch (src->sType) { + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES: + { + VkPhysicalDeviceVulkan11Features *newFeatures = (VkPhysicalDeviceVulkan11Features *)src; + VULKAN_INTERNAL_AddDeviceFeatures(&dst11->storageBuffer16BitAccess, + &dst11->shaderDrawParameters, + &newFeatures->storageBuffer16BitAccess); + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES: + { + VkPhysicalDeviceVulkan12Features *newFeatures = (VkPhysicalDeviceVulkan12Features *)src; + VULKAN_INTERNAL_AddDeviceFeatures(&dst12->samplerMirrorClampToEdge, + &dst12->subgroupBroadcastDynamicId, + &newFeatures->samplerMirrorClampToEdge); + hasAdded = true; + } break; + + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES: + { + VkPhysicalDeviceVulkan13Features *newFeatures = (VkPhysicalDeviceVulkan13Features *)src; + VULKAN_INTERNAL_AddDeviceFeatures(&dst13->robustImageAccess, + &dst13->maintenance4, + &newFeatures->robustImageAccess); + hasAdded = true; + } break; + } + } + + return hasAdded; +} + static bool VULKAN_INTERNAL_AddOptInVulkanOptions(SDL_PropertiesID props, VulkanRenderer *renderer) { if (SDL_HasProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER)) { @@ -11396,70 +11504,47 @@ static bool VULKAN_INTERNAL_AddOptInVulkanOptions(SDL_PropertiesID props, Vulkan if (options) { renderer->usesCustomVulkanOptions = true; renderer->desiredApiVersion = options->vulkan_api_version; + + SDL_memset(&renderer->desiredVulkan11DeviceFeatures, 0, sizeof(VkPhysicalDeviceVulkan11Features)); + SDL_memset(&renderer->desiredVulkan12DeviceFeatures, 0, sizeof(VkPhysicalDeviceVulkan12Features)); + SDL_memset(&renderer->desiredVulkan13DeviceFeatures, 0, sizeof(VkPhysicalDeviceVulkan13Features)); renderer->desiredVulkan11DeviceFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; renderer->desiredVulkan12DeviceFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; renderer->desiredVulkan13DeviceFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; - if (options->feature_list) { - VkPhysicalDeviceFeatures *vk10Features = &renderer->desiredVulkan10DeviceFeatures; - VkPhysicalDeviceVulkan11Features *vk11Features = &renderer->desiredVulkan11DeviceFeatures; - VkPhysicalDeviceVulkan12Features *vk12Features = &renderer->desiredVulkan12DeviceFeatures; - VkPhysicalDeviceVulkan13Features *vk13Features = &renderer->desiredVulkan13DeviceFeatures; - - // Iterate through the entire list and combine all requested features - VkBaseOutStructure *nextStructure = (VkBaseOutStructure *)options->feature_list; - while (nextStructure) { - switch (nextStructure->sType) { - case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2: - { - VkPhysicalDeviceFeatures2 *newFeatures = (VkPhysicalDeviceFeatures2 *)nextStructure; - VULKAN_INTERNAL_AddDeviceFeatures(&vk10Features->robustBufferAccess, - &vk10Features->inheritedQueries, - &newFeatures->features.robustBufferAccess); - } break; - - case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES: - { - VkPhysicalDeviceVulkan11Features *newFeatures = (VkPhysicalDeviceVulkan11Features *)nextStructure; - VULKAN_INTERNAL_AddDeviceFeatures(&vk11Features->storageBuffer16BitAccess, - &vk11Features->shaderDrawParameters, - &newFeatures->storageBuffer16BitAccess); - } break; - - case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES: - { - VkPhysicalDeviceVulkan12Features *newFeatures = (VkPhysicalDeviceVulkan12Features *)nextStructure; - VULKAN_INTERNAL_AddDeviceFeatures(&vk12Features->samplerMirrorClampToEdge, - &vk12Features->subgroupBroadcastDynamicId, - &newFeatures->samplerMirrorClampToEdge); - } break; - - case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES: - { - VkPhysicalDeviceVulkan13Features *newFeatures = (VkPhysicalDeviceVulkan13Features *)nextStructure; - VULKAN_INTERNAL_AddDeviceFeatures(&vk13Features->robustImageAccess, - &vk13Features->maintenance4, - &newFeatures->robustImageAccess); - } break; - - default: - { - // It's very important that this routine aborts when an unhandled type is encountered. - // Not all Vulkan structures (especially Vulkan 1.0 ones) cleanly cast to VkBaseOutStructure. - // If we don't bail out, nextStructure would get set to some garbage memory location and - // this loop would do very bad things. - SDL_LogError( - SDL_LOG_CATEGORY_GPU, - "Unhandled Vulkan structure type in requested device feature list! %d", - nextStructure->sType); - SDL_SetError( - "Unhandled Vulkan structure type in requested device feature list! %d", - nextStructure->sType); - return false; + // Handle requested device features + VkPhysicalDeviceFeatures *vk10Features = &renderer->desiredVulkan10DeviceFeatures; + VkPhysicalDeviceVulkan11Features *vk11Features = &renderer->desiredVulkan11DeviceFeatures; + VkPhysicalDeviceVulkan12Features *vk12Features = &renderer->desiredVulkan12DeviceFeatures; + VkPhysicalDeviceVulkan13Features *vk13Features = &renderer->desiredVulkan13DeviceFeatures; + + if (options->vulkan_10_physical_device_features) { + VkPhysicalDeviceFeatures *features = (VkPhysicalDeviceFeatures *)options->vulkan_10_physical_device_features; + VULKAN_INTERNAL_AddDeviceFeatures(&vk10Features->robustBufferAccess, + &vk10Features->inheritedQueries, + &features->robustBufferAccess); + } + + bool supportsHigherLevelFeatures = renderer->desiredApiVersion != VK_API_VERSION_1_0; + if (supportsHigherLevelFeatures && options->feature_list) { + if (renderer->desiredApiVersion == VK_API_VERSION_1_1) { + // Iterate through the entire list and combine all requested features + VkBaseOutStructure *nextStructure = (VkBaseOutStructure *)options->feature_list; + while (nextStructure) { + VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_11(vk10Features, vk11Features, nextStructure); + nextStructure = nextStructure->pNext; } + } else { + // Iterate through the entire list and combine all requested features + VkBaseOutStructure *nextStructure = (VkBaseOutStructure *)options->feature_list; + while (nextStructure) { + VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_12_Or_Later(vk10Features, + vk11Features, + vk12Features, + vk13Features, + nextStructure); + nextStructure = nextStructure->pNext; } - - nextStructure = nextStructure->pNext; } } } From b938d6e5dcb719dee9d9f08fc8cb92fe82d2690d Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Mon, 13 Oct 2025 00:44:37 +0200 Subject: [PATCH 13/24] Gate Vulkan feature requests by API version E.g. Vulkan 1.3 features will be ignored if API version 1.2 or lower is requested. --- src/gpu/vulkan/SDL_gpu_vulkan.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index ecce1f25f539b..44581995b81d1 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -11460,8 +11460,11 @@ static bool VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_12_Or_Later(VkPhysicalDe VkPhysicalDeviceVulkan11Features *dst11, VkPhysicalDeviceVulkan12Features *dst12, VkPhysicalDeviceVulkan13Features *dst13, + Uint32 apiVersion, VkBaseOutStructure *src) { + SDL_assert(apiVersion >= VK_API_VERSION_1_2); + bool hasAdded = VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_11(dst10, dst11, src); if (!hasAdded) { switch (src->sType) { @@ -11485,11 +11488,13 @@ static bool VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_12_Or_Later(VkPhysicalDe case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES: { - VkPhysicalDeviceVulkan13Features *newFeatures = (VkPhysicalDeviceVulkan13Features *)src; - VULKAN_INTERNAL_AddDeviceFeatures(&dst13->robustImageAccess, - &dst13->maintenance4, - &newFeatures->robustImageAccess); - hasAdded = true; + if (apiVersion >= VK_API_VERSION_1_3) { + VkPhysicalDeviceVulkan13Features *newFeatures = (VkPhysicalDeviceVulkan13Features *)src; + VULKAN_INTERNAL_AddDeviceFeatures(&dst13->robustImageAccess, + &dst13->maintenance4, + &newFeatures->robustImageAccess); + hasAdded = true; + } } break; } } @@ -11542,6 +11547,7 @@ static bool VULKAN_INTERNAL_AddOptInVulkanOptions(SDL_PropertiesID props, Vulkan vk11Features, vk12Features, vk13Features, + renderer->desiredApiVersion, nextStructure); nextStructure = nextStructure->pNext; } From 872aa0ae0226694599ceaa2d54700164e6234d70 Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Mon, 13 Oct 2025 00:54:34 +0200 Subject: [PATCH 14/24] Fix CI errors --- src/gpu/vulkan/SDL_gpu_vulkan.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index 44581995b81d1..c5d099be05e2e 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -11451,6 +11451,9 @@ static bool VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_11(VkPhysicalDeviceFeatu dst11->variablePointersStorageBuffer |= newFeatures->variablePointersStorageBuffer; hasAdded = true; } break; + + default: + break; } return hasAdded; @@ -11496,6 +11499,9 @@ static bool VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_12_Or_Later(VkPhysicalDe hasAdded = true; } } break; + + default: + break; } } From a269ffb1b414f89c7d457ad335eab6e50169031f Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Sat, 18 Oct 2025 01:35:03 +0200 Subject: [PATCH 15/24] Validate requested extensions Separate code-paths for 1.0, 1.1 and 1.2+ --- src/gpu/vulkan/SDL_gpu_vulkan.c | 113 ++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 27 deletions(-) diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index c5d099be05e2e..c45cefdcb8d08 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -11380,6 +11380,89 @@ static bool VULKAN_INTERNAL_ValidateOptInVulkan13Features(VkPhysicalDeviceVulkan #undef CHECK_OPTIONAL_DEVICE_FEATURE +static bool VULKAN_INTERNAL_ValidateOptInFeatures(VulkanRenderer *renderer, VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures *vk10Features) +{ + bool supportsAllFeatures = true; + + if (renderer->desiredApiVersion < VK_API_VERSION_1_1) { + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan10Features(&renderer->desiredVulkan10DeviceFeatures, vk10Features); + } else if (renderer->desiredApiVersion < VK_API_VERSION_1_2) { + // Query device features using the pre-1.2 structures + VkPhysicalDevice16BitStorageFeatures storage = { 0 }; + storage.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES; + + VkPhysicalDeviceMultiviewFeatures multiview = { 0 }; + multiview.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES; + + VkPhysicalDeviceProtectedMemoryFeatures protectedMem = { 0 }; + protectedMem.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES; + + VkPhysicalDeviceSamplerYcbcrConversionFeatures ycbcr = { 0 }; + ycbcr.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES; + + VkPhysicalDeviceShaderDrawParametersFeatures drawParams = { 0 }; + drawParams.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES; + + VkPhysicalDeviceVariablePointersFeatures varPointers = { 0 }; + varPointers.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTERS_FEATURES; + + VkPhysicalDeviceFeatures2 supportedFeatureList = { 0 }; + supportedFeatureList.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + supportedFeatureList.pNext = &storage; + storage.pNext = &multiview; + multiview.pNext = &protectedMem; + protectedMem.pNext = &ycbcr; + ycbcr.pNext = &drawParams; + drawParams.pNext = &varPointers; + + renderer->vkGetPhysicalDeviceFeatures2(physicalDevice, &supportedFeatureList); + + // Pack the results into the post-1.2 structure for easier checking + VkPhysicalDeviceVulkan11Features vk11Features = { 0 }; + vk11Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; + vk11Features.storageBuffer16BitAccess = storage.storageBuffer16BitAccess; + vk11Features.uniformAndStorageBuffer16BitAccess = storage.uniformAndStorageBuffer16BitAccess; + vk11Features.storagePushConstant16 = storage.storagePushConstant16; + vk11Features.storageInputOutput16 = storage.storageInputOutput16; + vk11Features.multiview = multiview.multiview; + vk11Features.multiviewGeometryShader = multiview.multiviewGeometryShader; + vk11Features.multiviewTessellationShader = multiview.multiviewTessellationShader; + vk11Features.protectedMemory = protectedMem.protectedMemory; + vk11Features.samplerYcbcrConversion = ycbcr.samplerYcbcrConversion; + vk11Features.shaderDrawParameters = drawParams.shaderDrawParameters; + vk11Features.variablePointers = varPointers.variablePointers; + vk11Features.variablePointersStorageBuffer = varPointers.variablePointersStorageBuffer; + + // Check support + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan10Features(&renderer->desiredVulkan10DeviceFeatures, vk10Features); + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan11Features(&renderer->desiredVulkan11DeviceFeatures, &vk11Features); + } else { + VkPhysicalDeviceVulkan11Features vk11Features = { 0 }; + vk11Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; + + VkPhysicalDeviceVulkan12Features vk12Features = { 0 }; + vk12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + + VkPhysicalDeviceVulkan13Features vk13Features = { 0 }; + vk13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + + VkPhysicalDeviceFeatures2 supportedFeatureList = { 0 }; + supportedFeatureList.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + supportedFeatureList.pNext = &vk11Features; + vk11Features.pNext = &vk12Features; + vk12Features.pNext = &vk13Features; + + renderer->vkGetPhysicalDeviceFeatures2(physicalDevice, &supportedFeatureList); + + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan10Features(&renderer->desiredVulkan10DeviceFeatures, vk10Features); + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan11Features(&renderer->desiredVulkan11DeviceFeatures, &vk11Features); + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan12Features(&renderer->desiredVulkan12DeviceFeatures, &vk12Features); + supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan13Features(&renderer->desiredVulkan13DeviceFeatures, &vk13Features); + } + + return supportsAllFeatures; +} + static void VULKAN_INTERNAL_AddDeviceFeatures(VkBool32 *firstFeature, VkBool32 *lastFeature, VkBool32 *firstFeatureToAdd) { while (firstFeature <= lastFeature) { @@ -11536,9 +11619,9 @@ static bool VULKAN_INTERNAL_AddOptInVulkanOptions(SDL_PropertiesID props, Vulkan &features->robustBufferAccess); } - bool supportsHigherLevelFeatures = renderer->desiredApiVersion != VK_API_VERSION_1_0; + bool supportsHigherLevelFeatures = renderer->desiredApiVersion > VK_API_VERSION_1_0; if (supportsHigherLevelFeatures && options->feature_list) { - if (renderer->desiredApiVersion == VK_API_VERSION_1_1) { + if (renderer->desiredApiVersion < VK_API_VERSION_1_2) { // Iterate through the entire list and combine all requested features VkBaseOutStructure *nextStructure = (VkBaseOutStructure *)options->feature_list; while (nextStructure) { @@ -11740,31 +11823,7 @@ static Uint8 VULKAN_INTERNAL_IsDeviceSuitable( // Check opt-in device features if (renderer->usesCustomVulkanOptions) { - VkPhysicalDeviceFeatures2 featureList = { 0 }; - featureList.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; - - VkPhysicalDeviceVulkan11Features vk11Features = { 0 }; - vk11Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; - - VkPhysicalDeviceVulkan12Features vk12Features = { 0 }; - vk12Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; - - VkPhysicalDeviceVulkan13Features vk13Features = { 0 }; - vk13Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; - - featureList.pNext = &vk11Features; - vk11Features.pNext = &vk12Features; - vk12Features.pNext = &vk13Features; - - renderer->vkGetPhysicalDeviceFeatures2( - physicalDevice, - &featureList); - - bool supportsAllFeatures = VULKAN_INTERNAL_ValidateOptInVulkan10Features(&renderer->desiredVulkan10DeviceFeatures, &featureList.features); - supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan11Features(&renderer->desiredVulkan11DeviceFeatures, &vk11Features); - supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan12Features(&renderer->desiredVulkan12DeviceFeatures, &vk12Features); - supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan13Features(&renderer->desiredVulkan13DeviceFeatures, &vk13Features); - + bool supportsAllFeatures = VULKAN_INTERNAL_ValidateOptInFeatures(renderer, physicalDevice, &deviceFeatures); if (!supportsAllFeatures) { return 0; } From bada266a5a48abeb6da8c5e9fec8438947ae8991 Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Sat, 18 Oct 2025 13:44:30 +0200 Subject: [PATCH 16/24] Cleanup -Remove obsolete property -Clean up API version checks --- include/SDL3/SDL_gpu.h | 1 - src/gpu/vulkan/SDL_gpu_vulkan.c | 17 ++++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index 1372b8f80382b..9f8254b3beec0 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -2345,7 +2345,6 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDeviceWithProperties( #define SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN "SDL.gpu.device.create.shaders.metallib" #define SDL_PROP_GPU_DEVICE_CREATE_D3D12_ALLOW_FEWER_RESOURCE_SLOTS_BOOLEAN "SDL.gpu.device.create.d3d12.allowtier1resourcebinding" #define SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING "SDL.gpu.device.create.d3d12.semantic" -#define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_ADDITIONAL_FEATURES_POINTER "SDL.gpu.device.create.vulkan.additional_features" #define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER "SDL.gpu.device.create.vulkan.options" diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index c45cefdcb8d08..9307f6c0e9a06 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -11384,9 +11384,11 @@ static bool VULKAN_INTERNAL_ValidateOptInFeatures(VulkanRenderer *renderer, VkPh { bool supportsAllFeatures = true; - if (renderer->desiredApiVersion < VK_API_VERSION_1_1) { + int minorVersion = VK_API_VERSION_MINOR(renderer->desiredApiVersion); + + if (minorVersion < 1) { supportsAllFeatures &= VULKAN_INTERNAL_ValidateOptInVulkan10Features(&renderer->desiredVulkan10DeviceFeatures, vk10Features); - } else if (renderer->desiredApiVersion < VK_API_VERSION_1_2) { + } else if (minorVersion < 2) { // Query device features using the pre-1.2 structures VkPhysicalDevice16BitStorageFeatures storage = { 0 }; storage.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES; @@ -11549,8 +11551,8 @@ static bool VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_12_Or_Later(VkPhysicalDe Uint32 apiVersion, VkBaseOutStructure *src) { - SDL_assert(apiVersion >= VK_API_VERSION_1_2); - + int minorVersion = VK_API_VERSION_MINOR(apiVersion); + SDL_assert(apiVersion >= 2); bool hasAdded = VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_11(dst10, dst11, src); if (!hasAdded) { switch (src->sType) { @@ -11574,7 +11576,7 @@ static bool VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_12_Or_Later(VkPhysicalDe case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES: { - if (apiVersion >= VK_API_VERSION_1_3) { + if (minorVersion >= 3) { VkPhysicalDeviceVulkan13Features *newFeatures = (VkPhysicalDeviceVulkan13Features *)src; VULKAN_INTERNAL_AddDeviceFeatures(&dst13->robustImageAccess, &dst13->maintenance4, @@ -11619,9 +11621,10 @@ static bool VULKAN_INTERNAL_AddOptInVulkanOptions(SDL_PropertiesID props, Vulkan &features->robustBufferAccess); } - bool supportsHigherLevelFeatures = renderer->desiredApiVersion > VK_API_VERSION_1_0; + int minorVersion = VK_API_VERSION_MINOR(renderer->desiredApiVersion); + bool supportsHigherLevelFeatures = minorVersion > 0; if (supportsHigherLevelFeatures && options->feature_list) { - if (renderer->desiredApiVersion < VK_API_VERSION_1_2) { + if (minorVersion < 2) { // Iterate through the entire list and combine all requested features VkBaseOutStructure *nextStructure = (VkBaseOutStructure *)options->feature_list; while (nextStructure) { From cff7d01a104311357a4718242b30475f9c56afee Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Sat, 18 Oct 2025 15:03:28 +0200 Subject: [PATCH 17/24] Implement opt-in instance extensions --- include/SDL3/SDL_gpu.h | 2 ++ src/gpu/vulkan/SDL_gpu_vulkan.c | 59 ++++++++++++++++++++++++--------- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index 9f8254b3beec0..d630898a02d3f 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -2365,6 +2365,8 @@ typedef struct SDL_GPUVulkanOptions Uint32 vulkan_api_version; /**< The Vulkan API version to request for the instance. Use Vulkan's VK_MAKE_VERSION or VK_MAKE_API_VERSION. */ void *feature_list; /**< Pointer to the first element of a list of structs to be passed to device creation. */ void *vulkan_10_physical_device_features; /**< Pointer to a VkPhysicalDeviceFeatures struct to enable additional Vulkan 1.0 features. */ + Uint32 instance_extension_count; /**< Number of additional instance extensions to require. */ + char **instance_extension_names; /**< Pointer to a list of additional instance extensions to require. */ } SDL_GPUVulkanOptions; /** diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index 9307f6c0e9a06..41e81e4f11829 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -1113,6 +1113,8 @@ struct VulkanRenderer VkPhysicalDeviceVulkan11Features desiredVulkan11DeviceFeatures; VkPhysicalDeviceVulkan12Features desiredVulkan12DeviceFeatures; VkPhysicalDeviceVulkan13Features desiredVulkan13DeviceFeatures; + Uint32 additionalInstanceExtensionCount; + char **additionalInstanceExtensionNames; bool debugMode; bool preferLowPower; @@ -11080,7 +11082,8 @@ static Uint8 VULKAN_INTERNAL_CheckInstanceExtensions( Uint32 requiredExtensionsLength, bool *supportsDebugUtils, bool *supportsColorspace, - bool *supportsPhysicalDeviceProperties2) + bool *supportsPhysicalDeviceProperties2, + int *firstUnsupportedExtensionIndex) { Uint32 extensionCount, i; VkExtensionProperties *availableExtensions; @@ -11103,6 +11106,7 @@ static Uint8 VULKAN_INTERNAL_CheckInstanceExtensions( availableExtensions, extensionCount)) { allExtensionsSupported = 0; + *firstUnsupportedExtensionIndex = i; break; } } @@ -11645,6 +11649,9 @@ static bool VULKAN_INTERNAL_AddOptInVulkanOptions(SDL_PropertiesID props, Vulkan } } } + + renderer->additionalInstanceExtensionCount = options->instance_extension_count; + renderer->additionalInstanceExtensionNames = options->instance_extension_names; } } @@ -11684,37 +11691,59 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) return 0; } + Uint32 extraInstanceExtensionCount = renderer->additionalInstanceExtensionCount; + char** extraInstanceExtensionNames = renderer->additionalInstanceExtensionNames; + /* Extra space for the following extensions: * VK_KHR_get_physical_device_properties2 * VK_EXT_swapchain_colorspace * VK_EXT_debug_utils * VK_KHR_portability_enumeration + * + * Plus additional opt-in extensions. */ instanceExtensionNames = SDL_stack_alloc( const char *, - instanceExtensionCount + 4); - SDL_memcpy((void *)instanceExtensionNames, originalInstanceExtensionNames, instanceExtensionCount * sizeof(const char *)); + instanceExtensionCount + 4 + extraInstanceExtensionCount); + const char** nextInstanceExtensionNamePtr = instanceExtensionNames; + SDL_memcpy((void *)nextInstanceExtensionNamePtr, originalInstanceExtensionNames, instanceExtensionCount * sizeof(const char *)); + nextInstanceExtensionNamePtr += instanceExtensionCount; + + if (extraInstanceExtensionCount > 0) { + SDL_memcpy((void *)nextInstanceExtensionNamePtr, extraInstanceExtensionNames, extraInstanceExtensionCount * sizeof(const char *)); + nextInstanceExtensionNamePtr += extraInstanceExtensionCount; + } + #ifdef SDL_PLATFORM_APPLE - instanceExtensionNames[instanceExtensionCount++] = - VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; + *nextInstanceExtensionNamePtr++ = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME; + instanceExtensionCount++; createFlags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; #endif + int firstUnsupportedExtensionIndex = 0; if (!VULKAN_INTERNAL_CheckInstanceExtensions( instanceExtensionNames, - instanceExtensionCount, + instanceExtensionCount + extraInstanceExtensionCount, &renderer->supportsDebugUtils, &renderer->supportsColorspace, - &renderer->supportsPhysicalDeviceProperties2)) { + &renderer->supportsPhysicalDeviceProperties2, + &firstUnsupportedExtensionIndex)) { + if (renderer->debugMode) { + SDL_LogError(SDL_LOG_CATEGORY_GPU, + "Required Vulkan instance extension '%s' not supported", + instanceExtensionNames[firstUnsupportedExtensionIndex]); + } + SDL_SetError("Required Vulkan instance extension '%s' not supported", + instanceExtensionNames[firstUnsupportedExtensionIndex]); SDL_stack_free((char *)instanceExtensionNames); - SET_STRING_ERROR_AND_RETURN("Required Vulkan instance extensions not supported", false); + return false; } if (renderer->supportsDebugUtils) { // Append the debug extension - instanceExtensionNames[instanceExtensionCount++] = - VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + *nextInstanceExtensionNamePtr++ = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + instanceExtensionCount++; } else { SDL_LogWarn( SDL_LOG_CATEGORY_GPU, @@ -11724,14 +11753,14 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) if (renderer->supportsColorspace) { // Append colorspace extension - instanceExtensionNames[instanceExtensionCount++] = - VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME; + *nextInstanceExtensionNamePtr++ = VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME; + instanceExtensionCount++; } if (renderer->supportsPhysicalDeviceProperties2) { // Append KHR_physical_device_properties2 extension - instanceExtensionNames[instanceExtensionCount++] = - VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; + *nextInstanceExtensionNamePtr++ = VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME; + instanceExtensionCount++; } createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; @@ -11739,7 +11768,7 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) createInfo.flags = createFlags; createInfo.pApplicationInfo = &appInfo; createInfo.ppEnabledLayerNames = layerNames; - createInfo.enabledExtensionCount = instanceExtensionCount; + createInfo.enabledExtensionCount = instanceExtensionCount + extraInstanceExtensionCount; createInfo.ppEnabledExtensionNames = instanceExtensionNames; if (renderer->debugMode) { createInfo.enabledLayerCount = SDL_arraysize(layerNames); From b00b4df55a5fe6185fcc8f64479574d0b3aee56f Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Sat, 18 Oct 2025 16:26:28 +0200 Subject: [PATCH 18/24] Implement opt-in device extensions --- include/SDL3/SDL_gpu.h | 2 ++ src/gpu/vulkan/SDL_gpu_vulkan.c | 41 +++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index d630898a02d3f..04b24c28a0cb1 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -2365,6 +2365,8 @@ typedef struct SDL_GPUVulkanOptions Uint32 vulkan_api_version; /**< The Vulkan API version to request for the instance. Use Vulkan's VK_MAKE_VERSION or VK_MAKE_API_VERSION. */ void *feature_list; /**< Pointer to the first element of a list of structs to be passed to device creation. */ void *vulkan_10_physical_device_features; /**< Pointer to a VkPhysicalDeviceFeatures struct to enable additional Vulkan 1.0 features. */ + Uint32 device_extension_count; /**< Number of additional device extensions to require. */ + char **device_extension_names; /**< Pointer to a list of additional device extensions to require. */ Uint32 instance_extension_count; /**< Number of additional instance extensions to require. */ char **instance_extension_names; /**< Pointer to a list of additional instance extensions to require. */ } SDL_GPUVulkanOptions; diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index 41e81e4f11829..d73fc82fdb799 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -1113,6 +1113,8 @@ struct VulkanRenderer VkPhysicalDeviceVulkan11Features desiredVulkan11DeviceFeatures; VkPhysicalDeviceVulkan12Features desiredVulkan12DeviceFeatures; VkPhysicalDeviceVulkan13Features desiredVulkan13DeviceFeatures; + Uint32 additionalDeviceExtensionCount; + char **additionalDeviceExtensionNames; Uint32 additionalInstanceExtensionCount; char **additionalInstanceExtensionNames; @@ -11133,6 +11135,30 @@ static Uint8 VULKAN_INTERNAL_CheckInstanceExtensions( return allExtensionsSupported; } +static Uint8 CheckOptInDeviceExtensions(VulkanRenderer *renderer, + VkPhysicalDevice physicalDevice, + Uint32 numExtensions, + VkExtensionProperties *availableExtensions, + const char **missingExtensionName) { + Uint8 supportsAll = 1; + for (Uint32 extensionIdx = 0; extensionIdx < renderer->additionalDeviceExtensionCount; extensionIdx++) { + bool found = false; + for (Uint32 searchIdx = 0; searchIdx < numExtensions; searchIdx++) { + if (SDL_strcmp(renderer->additionalDeviceExtensionNames[extensionIdx], availableExtensions[searchIdx].extensionName) == 0) { + found = true; + break; + } + } + if (!found) { + supportsAll = 0; + *missingExtensionName = renderer->additionalDeviceExtensionNames[extensionIdx]; + break; + } + } + + return supportsAll; +} + static Uint8 VULKAN_INTERNAL_CheckDeviceExtensions( VulkanRenderer *renderer, VkPhysicalDevice physicalDevice, @@ -11160,6 +11186,19 @@ static Uint8 VULKAN_INTERNAL_CheckDeviceExtensions( extensionCount, physicalDeviceExtensions); + if (renderer->usesCustomVulkanOptions) { + const char *missingExtensionName; + if (!CheckOptInDeviceExtensions(renderer, physicalDevice, extensionCount, availableExtensions, &missingExtensionName)) { + SDL_assert(missingExtensionName); + if (renderer->debugMode) { + SDL_LogError(SDL_LOG_CATEGORY_GPU, + "Required Vulkan device extension '%s' not supported", + missingExtensionName); + } + allExtensionsSupported = 0; + } + } + SDL_free(availableExtensions); return allExtensionsSupported; } @@ -11650,6 +11689,8 @@ static bool VULKAN_INTERNAL_AddOptInVulkanOptions(SDL_PropertiesID props, Vulkan } } + renderer->additionalDeviceExtensionCount = options->device_extension_count; + renderer->additionalDeviceExtensionNames = options->device_extension_names; renderer->additionalInstanceExtensionCount = options->instance_extension_count; renderer->additionalInstanceExtensionNames = options->instance_extension_names; } From 4cbb2a077e1528f9e8df6540e4218df62fc2fc29 Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Sat, 18 Oct 2025 16:30:00 +0200 Subject: [PATCH 19/24] Add missing const --- include/SDL3/SDL_gpu.h | 4 ++-- src/gpu/vulkan/SDL_gpu_vulkan.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index 04b24c28a0cb1..f72c54bb2bd92 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -2366,9 +2366,9 @@ typedef struct SDL_GPUVulkanOptions void *feature_list; /**< Pointer to the first element of a list of structs to be passed to device creation. */ void *vulkan_10_physical_device_features; /**< Pointer to a VkPhysicalDeviceFeatures struct to enable additional Vulkan 1.0 features. */ Uint32 device_extension_count; /**< Number of additional device extensions to require. */ - char **device_extension_names; /**< Pointer to a list of additional device extensions to require. */ + const char **device_extension_names; /**< Pointer to a list of additional device extensions to require. */ Uint32 instance_extension_count; /**< Number of additional instance extensions to require. */ - char **instance_extension_names; /**< Pointer to a list of additional instance extensions to require. */ + const char **instance_extension_names; /**< Pointer to a list of additional instance extensions to require. */ } SDL_GPUVulkanOptions; /** diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index d73fc82fdb799..66c893dd52400 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -1114,9 +1114,9 @@ struct VulkanRenderer VkPhysicalDeviceVulkan12Features desiredVulkan12DeviceFeatures; VkPhysicalDeviceVulkan13Features desiredVulkan13DeviceFeatures; Uint32 additionalDeviceExtensionCount; - char **additionalDeviceExtensionNames; + const char **additionalDeviceExtensionNames; Uint32 additionalInstanceExtensionCount; - char **additionalInstanceExtensionNames; + const char **additionalInstanceExtensionNames; bool debugMode; bool preferLowPower; From 9e26c5ad328304e23a9bd65c06658e345de45a29 Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Sat, 18 Oct 2025 16:33:31 +0200 Subject: [PATCH 20/24] Update comment --- include/SDL3/SDL_gpu.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index f72c54bb2bd92..92a1d779f739a 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -2313,7 +2313,8 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDevice( * to a Vulkan structure to be appended to SDL's VkDeviceCreateInfo during * device creation. * This allows passing a list of VkPhysicalDeviceFeature structures to - * opt-into features aside from the minimal set SDL requires. + * opt-into features aside from the minimal set SDL requires. It also allows + * requesting a higher API version and opting into extensions. * * \param props the properties to use. * \returns a GPU context on success or NULL on failure; call SDL_GetError() From e3579360d4a3ee4dfa8470e1dfe6dfc0579b782e Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Sat, 18 Oct 2025 17:14:13 +0200 Subject: [PATCH 21/24] Fix compile error --- src/gpu/vulkan/SDL_gpu_vulkan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index 66c893dd52400..946d8f4f0fa8c 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -11733,7 +11733,7 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) } Uint32 extraInstanceExtensionCount = renderer->additionalInstanceExtensionCount; - char** extraInstanceExtensionNames = renderer->additionalInstanceExtensionNames; + const char** extraInstanceExtensionNames = renderer->additionalInstanceExtensionNames; /* Extra space for the following extensions: * VK_KHR_get_physical_device_properties2 From d95f1d88e4ad9a87ee3cd358fd31888dd34e55e2 Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Sat, 18 Oct 2025 18:49:54 +0200 Subject: [PATCH 22/24] Remove obsolete error check Error checking no longer happens in VULKAN_INTERNAL_AddOptInVulkanOptions() --- src/gpu/vulkan/SDL_gpu_vulkan.c | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index 946d8f4f0fa8c..5879e6f76804e 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -11636,7 +11636,7 @@ static bool VULKAN_INTERNAL_TryAddDeviceFeatures_Vulkan_12_Or_Later(VkPhysicalDe return hasAdded; } -static bool VULKAN_INTERNAL_AddOptInVulkanOptions(SDL_PropertiesID props, VulkanRenderer *renderer) +static void VULKAN_INTERNAL_AddOptInVulkanOptions(SDL_PropertiesID props, VulkanRenderer *renderer) { if (SDL_HasProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER)) { SDL_GPUVulkanOptions *options = (SDL_GPUVulkanOptions *)SDL_GetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER, NULL); @@ -11693,10 +11693,11 @@ static bool VULKAN_INTERNAL_AddOptInVulkanOptions(SDL_PropertiesID props, Vulkan renderer->additionalDeviceExtensionNames = options->device_extension_names; renderer->additionalInstanceExtensionCount = options->instance_extension_count; renderer->additionalInstanceExtensionNames = options->instance_extension_names; + } else if (renderer->debugMode) { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, + "VULKAN_INTERNAL_AddOptInVulkanOptions: Additional options property was set, but value was null. This may be a bug."); } } - - return true; } static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer) @@ -12338,13 +12339,11 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props) renderer->desiredVulkan10DeviceFeatures.imageCubeArray = VK_TRUE; // Handle opt-in device features - bool featuresInitialized = VULKAN_INTERNAL_AddOptInVulkanOptions(props, renderer); + VULKAN_INTERNAL_AddOptInVulkanOptions(props, renderer); - if (featuresInitialized) { - result = VULKAN_INTERNAL_PrepareVulkan(renderer); - if (result) { - renderer->vkDestroyInstance(renderer->instance, NULL); - } + result = VULKAN_INTERNAL_PrepareVulkan(renderer); + if (result) { + renderer->vkDestroyInstance(renderer->instance, NULL); } SDL_free(renderer); @@ -12393,12 +12392,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S renderer->desiredVulkan10DeviceFeatures.imageCubeArray = VK_TRUE; // Handle opt-in device features - if (!VULKAN_INTERNAL_AddOptInVulkanOptions(props, renderer)) { - SET_STRING_ERROR("Failed to initialize additional Vulkan options!"); - SDL_free(renderer); - SDL_Vulkan_UnloadLibrary(); - return NULL; - } + VULKAN_INTERNAL_AddOptInVulkanOptions(props, renderer); if (!VULKAN_INTERNAL_PrepareVulkan(renderer)) { SET_STRING_ERROR("Failed to initialize Vulkan!"); From a198c8cbcdbdc53aac97a28e0bb4165d4c104965 Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Sat, 18 Oct 2025 19:07:20 +0200 Subject: [PATCH 23/24] Clear extension name pointers after initialization Since the user might pass a pointer to the stack, we better clear this after we're done with it. --- src/gpu/vulkan/SDL_gpu_vulkan.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c index 5879e6f76804e..ba68092af949d 100644 --- a/src/gpu/vulkan/SDL_gpu_vulkan.c +++ b/src/gpu/vulkan/SDL_gpu_vulkan.c @@ -12401,6 +12401,12 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S return NULL; } + // Make sure we don't hold onto potentially unsafe pointers after initialization + renderer->additionalDeviceExtensionCount = 0; + renderer->additionalInstanceExtensionCount = 0; + renderer->additionalDeviceExtensionNames = NULL; + renderer->additionalInstanceExtensionNames = NULL; + renderer->props = SDL_CreateProperties(); if (verboseLogs) { SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "SDL_GPU Driver: Vulkan"); From 24749c43bc34fed6304c43b96201cc18d7bc0794 Mon Sep 17 00:00:00 2001 From: TheSniperFan <9803861+TheSniperFan@users.noreply.github.com> Date: Sat, 18 Oct 2025 19:09:11 +0200 Subject: [PATCH 24/24] Update include/SDL3/SDL_gpu.h Co-authored-by: Sam Lantinga --- include/SDL3/SDL_gpu.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index 92a1d779f739a..9af3b5b7094d3 100644 --- a/include/SDL3/SDL_gpu.h +++ b/include/SDL3/SDL_gpu.h @@ -2349,8 +2349,6 @@ extern SDL_DECLSPEC SDL_GPUDevice * SDLCALL SDL_CreateGPUDeviceWithProperties( #define SDL_PROP_GPU_DEVICE_CREATE_VULKAN_OPTIONS_POINTER "SDL.gpu.device.create.vulkan.options" - - /** * A structure specifying additional options when using Vulkan. *