fix(vulkan): Correctly support Vulkan 1.0 on Android (#10679) 8e0fadf978
VkBootstrap will incorrecty error with `vulkan_version_1_1_unavailable` on Vulkan 1.0 systems that do not have the vkEnumerateInstanceVersion function, even when we specifically said that it was fine to fall back to 1.0. To work around this, we will detect that error and then try again to create the Vulkan instance, specifically requesting version 1.0.

Additionally fix a "function must return" error when building in MSVC on Windows, and update LOG_ERROR to be LOG_ERROR_LINE to make it clear that it writes lines (and update its use of fprintf to match what the android logging does)

Co-authored-by: Josh Jersild <joshua@rive.app>
diff --git a/.rive_head b/.rive_head
index 7952868..e1a40ae 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-7e676124de940ed882b8d2a733217a50e434320f
+8e0fadf9789534151a187ba7da25f0461f64abbb
diff --git a/renderer/path_fiddle/fiddle_context_vulkan.cpp b/renderer/path_fiddle/fiddle_context_vulkan.cpp
index 9eb6ecc..40bf222 100644
--- a/renderer/path_fiddle/fiddle_context_vulkan.cpp
+++ b/renderer/path_fiddle/fiddle_context_vulkan.cpp
@@ -39,22 +39,82 @@
         const char** glfwExtensions;
         glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
 
-        vkb::InstanceBuilder instanceBuilder;
-        instanceBuilder.set_app_name("path_fiddle")
-            .set_engine_name("Rive Renderer")
-            .enable_extensions(glfwExtensionCount, glfwExtensions)
-            .require_api_version(1, options.coreFeaturesOnly ? 0 : 3, 0)
-            .set_minimum_instance_version(1, 0, 0);
-#ifdef DEBUG
-        instanceBuilder.enable_validation_layers(
-            m_options.enableVulkanValidationLayers);
-        if (!m_options.disableDebugCallbacks)
+        int minorVersionRequested = options.coreFeaturesOnly ? 0 : 3;
+        bool enableVulkanValidationLayers =
+            m_options.enableVulkanValidationLayers;
+        bool disableDebugCallbacks = m_options.disableDebugCallbacks;
+
+        while (true)
         {
-            instanceBuilder.set_debug_callback(
-                rive_vkb::default_debug_callback);
-        }
+            vkb::InstanceBuilder instanceBuilder;
+            instanceBuilder.set_app_name("path_fiddle")
+                .set_engine_name("Rive Renderer")
+                .enable_extensions(glfwExtensionCount, glfwExtensions)
+                .require_api_version(1, minorVersionRequested, 0)
+                .set_minimum_instance_version(1, 0, 0);
+#ifdef DEBUG
+            instanceBuilder.enable_validation_layers(
+                enableVulkanValidationLayers);
+            if (!disableDebugCallbacks)
+            {
+                instanceBuilder.set_debug_callback(
+                    rive_vkb::default_debug_callback);
+            }
 #endif
-        m_instance = VKB_CHECK(instanceBuilder.build());
+
+            auto instanceResult = instanceBuilder.build();
+            if (!instanceResult)
+            {
+                auto error = static_cast<vkb::InstanceError>(
+                    instanceResult.error().value());
+
+                if (error ==
+                        vkb::InstanceError::vulkan_version_1_1_unavailable &&
+                    minorVersionRequested != 0)
+                {
+                    // There's a bug in VkBootstrap (due to not properly
+                    // handling Vulkan 1.0 not having the
+                    // vkEnumerateInstanceVersion function) where it can give a
+                    // vulkan_version_1_1_unavailable error even though we've
+                    // specified a minimum of 1.0. If we get that error,
+                    // request 1.0 directly and try again.
+                    fprintf(stderr, "Falling back on Vulkan 1.0.\n");
+                    minorVersionRequested = 0;
+                    continue;
+                }
+
+#ifdef DEBUG
+                if (enableVulkanValidationLayers &&
+                    error == vkb::InstanceError::requested_layers_not_present)
+                {
+                    fprintf(stderr,
+                            "WARNING: Validation layers not found. Attempting "
+                            "to create a Vulkan context again without "
+                            "validation layers.\n");
+                    enableVulkanValidationLayers = false;
+                    continue;
+                }
+
+                if (!disableDebugCallbacks &&
+                    error == vkb::InstanceError::failed_create_debug_messenger)
+                {
+                    fprintf(stderr,
+                            "WARNING: Debug callbacks not supported. "
+                            "Attempting to create a Vulkan context again "
+                            "without debug callbacks.");
+                    disableDebugCallbacks = true;
+                    continue;
+                }
+#endif
+                fprintf(stderr,
+                        "ERROR: %s: Failed to build Vulkan instance.",
+                        instanceResult.error().message().c_str());
+                abort();
+            }
+
+            m_instance = *instanceResult;
+            break;
+        }
         m_instanceDispatchTable = m_instance.make_table();
 
         VulkanFeatures vulkanFeatures;
diff --git a/renderer/src/vulkan/draw_pipeline_vulkan.cpp b/renderer/src/vulkan/draw_pipeline_vulkan.cpp
index 967de02..bdd8b29 100644
--- a/renderer/src/vulkan/draw_pipeline_vulkan.cpp
+++ b/renderer/src/vulkan/draw_pipeline_vulkan.cpp
@@ -174,6 +174,8 @@
         case gpu::DrawType::renderPassResolve:
             return mainSubpassIdx + 1;
     }
+
+    RIVE_UNREACHABLE();
 }
 
 DrawPipelineVulkan::DrawPipelineVulkan(
diff --git a/tests/common/testing_window_android_vulkan.cpp b/tests/common/testing_window_android_vulkan.cpp
index 03ee044..2d89b17 100644
--- a/tests/common/testing_window_android_vulkan.cpp
+++ b/tests/common/testing_window_android_vulkan.cpp
@@ -29,9 +29,9 @@
 
 // Send errors to stderr and the Android log, just for redundancy in case one or
 // the other gets dropped.
-#define LOG_ERROR(FORMAT, ...)                                                 \
+#define LOG_ERROR_LINE(FORMAT, ...)                                            \
     [](auto&&... args) {                                                       \
-        fprintf(stderr, FORMAT, std::forward<decltype(args)>(args)...);        \
+        fprintf(stderr, FORMAT "\n", std::forward<decltype(args)>(args)...);   \
         __android_log_print(ANDROID_LOG_ERROR,                                 \
                             "rive_android_tests",                              \
                             FORMAT,                                            \
@@ -49,13 +49,16 @@
         m_androidWindowHeight = m_height = ANativeWindow_getHeight(window);
         rive_vkb::load_vulkan();
 
+        // Request Vulkan 1.3, except if we're in core mode where we want 1.0.
+        int minorVersionRequested = m_backendParams.core ? 0 : 3;
+
         for (;;)
         {
             vkb::InstanceBuilder instanceBuilder;
             instanceBuilder.set_app_name("path_fiddle")
                 .set_engine_name("Rive Renderer")
                 .enable_extension(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME)
-                .require_api_version(1, m_backendParams.core ? 0 : 3, 0)
+                .require_api_version(1, minorVersionRequested, 0)
                 .set_minimum_instance_version(1, 0, 0);
 #ifdef DEBUG
             if (!m_backendParams.disableValidationLayers)
@@ -71,13 +74,28 @@
             auto instanceResult = instanceBuilder.build();
             if (!instanceResult)
             {
+                auto error = static_cast<vkb::InstanceError>(
+                    instanceResult.error().value());
+                if (error ==
+                        vkb::InstanceError::vulkan_version_1_1_unavailable &&
+                    minorVersionRequested != 0)
+                {
+                    // There's a bug in VkBootstrap (due to not properly
+                    // handling Vulkan 1.0 not having the
+                    // vkEnumerateInstanceVersion function) where it can give a
+                    // vulkan_version_1_1_unavailable error even though we've
+                    // specified a minimum of 1.0. If we get that error,
+                    // request 1.0 directly and try again.
+                    LOG_ERROR_LINE("Falling back on Vulkan 1.0.");
+                    minorVersionRequested = 0;
+                    continue;
+                }
+
 #ifdef DEBUG
                 if (!m_backendParams.disableValidationLayers &&
-                    static_cast<vkb::InstanceError>(
-                        instanceResult.error().value()) ==
-                        vkb::InstanceError::requested_layers_not_present)
+                    error == vkb::InstanceError::requested_layers_not_present)
                 {
-                    LOG_ERROR(
+                    LOG_ERROR_LINE(
                         "WARNING: Validation layers not found. Attempting to "
                         "create a Vulkan context again without validation "
                         "layers.");
@@ -85,11 +103,9 @@
                     continue;
                 }
                 if (!m_backendParams.disableDebugCallbacks &&
-                    static_cast<vkb::InstanceError>(
-                        instanceResult.error().value()) ==
-                        vkb::InstanceError::failed_create_debug_messenger)
+                    error == vkb::InstanceError::failed_create_debug_messenger)
                 {
-                    LOG_ERROR(
+                    LOG_ERROR_LINE(
                         "WARNING: Debug callbacks not supported. Attempting to "
                         "create a Vulkan context again without debug "
                         "callbacks.");
@@ -97,8 +113,8 @@
                     continue;
                 }
 #endif
-                LOG_ERROR("ERROR: %s: Failed to build Vulkan instance.",
-                          instanceResult.error().message().c_str());
+                LOG_ERROR_LINE("ERROR: %s: Failed to build Vulkan instance.",
+                               instanceResult.error().message().c_str());
                 abort();
             }
             m_instance = *instanceResult;
diff --git a/tests/common/testing_window_vulkan_texture.cpp b/tests/common/testing_window_vulkan_texture.cpp
index bbcdede..a2f8bb9 100644
--- a/tests/common/testing_window_vulkan_texture.cpp
+++ b/tests/common/testing_window_vulkan_texture.cpp
@@ -29,22 +29,79 @@
     {
         rive_vkb::load_vulkan();
 
-        vkb::InstanceBuilder instanceBuilder;
-        instanceBuilder.set_app_name("rive_tools")
-            .set_engine_name("Rive Renderer")
-            .set_headless(true)
-            .require_api_version(1, m_backendParams.core ? 0 : 3, 0)
-            .set_minimum_instance_version(1, 0, 0);
-#ifdef DEBUG
-        instanceBuilder.enable_validation_layers(
-            !backendParams.disableValidationLayers);
-        if (!backendParams.disableDebugCallbacks)
+        int minorVersionRequested = m_backendParams.core ? 0 : 3;
+        bool disableValidationLayers = m_backendParams.disableValidationLayers;
+        bool disableDebugCallbacks = m_backendParams.disableDebugCallbacks;
+
+        while (true)
         {
-            instanceBuilder.set_debug_callback(
-                rive_vkb::default_debug_callback);
-        }
+            vkb::InstanceBuilder instanceBuilder;
+            instanceBuilder.set_app_name("rive_tools")
+                .set_engine_name("Rive Renderer")
+                .set_headless(true)
+                .require_api_version(1, m_backendParams.core ? 0 : 3, 0)
+                .set_minimum_instance_version(1, 0, 0);
+#ifdef DEBUG
+            instanceBuilder.enable_validation_layers(!disableValidationLayers);
+            if (!disableDebugCallbacks)
+            {
+                instanceBuilder.set_debug_callback(
+                    rive_vkb::default_debug_callback);
+            }
 #endif
-        m_instance = VKB_CHECK(instanceBuilder.build());
+            auto instanceResult = instanceBuilder.build();
+            if (!instanceResult)
+            {
+                auto error = static_cast<vkb::InstanceError>(
+                    instanceResult.error().value());
+
+                if (error ==
+                        vkb::InstanceError::vulkan_version_1_1_unavailable &&
+                    minorVersionRequested != 0)
+                {
+                    // There's a bug in VkBootstrap (due to not properly
+                    // handling Vulkan 1.0 not having the
+                    // vkEnumerateInstanceVersion function) where it can give a
+                    // vulkan_version_1_1_unavailable error even though we've
+                    // specified a minimum of 1.0. If we get that error,
+                    // request 1.0 directly and try again.
+                    fprintf(stderr, "Falling back on Vulkan 1.0.\n");
+                    minorVersionRequested = 0;
+                    continue;
+                }
+
+#ifdef DEBUG
+                if (!disableValidationLayers &&
+                    error == vkb::InstanceError::requested_layers_not_present)
+                {
+                    fprintf(stderr,
+                            "WARNING: Validation layers not found. Attempting "
+                            "to create a Vulkan context again without "
+                            "validation layers.\n");
+                    disableValidationLayers = true;
+                    continue;
+                }
+
+                if (!disableDebugCallbacks &&
+                    error == vkb::InstanceError::failed_create_debug_messenger)
+                {
+                    fprintf(stderr,
+                            "WARNING: Debug callbacks not supported. "
+                            "Attempting to create a Vulkan context again "
+                            "without debug callbacks.");
+                    disableDebugCallbacks = true;
+                    continue;
+                }
+#endif
+                fprintf(stderr,
+                        "ERROR: %s: Failed to build Vulkan instance.",
+                        instanceResult.error().message().c_str());
+                abort();
+            }
+
+            m_instance = *instanceResult;
+            break;
+        }
 
         VulkanFeatures vulkanFeatures;
         std::tie(m_device, vulkanFeatures) = rive_vkb::select_device(