Skip to content

gpu_helper: dual-path Vulkan init with runtime API-level branch#1

Draft
Copilot wants to merge 5 commits intomasterfrom
copilot/fix-dynamic-loading-for-android-29
Draft

gpu_helper: dual-path Vulkan init with runtime API-level branch#1
Copilot wants to merge 5 commits intomasterfrom
copilot/fix-dynamic-loading-for-android-29

Conversation

Copy link
Copy Markdown

Copilot AI commented Mar 5, 2026

On Android 10 (API 29), static Vulkan linking crashes at vkCreateInstance. The fix is dynamic loading via dlopen/dlsym, but applying it unconditionally diverges unnecessarily from upstream. This PR restores the upstream static path for API > 29 and gates the dynamic-load fix behind a runtime check.

Changes

  • Runtime branchJava_com_winlator_core_GPUHelper_vkGetDeviceExtensions now calls android_get_device_api_level() and dispatches to one of two static helpers:

    if (android_get_device_api_level() <= 29)
        return vkGetDeviceExtensions_dynamic(env);
    return vkGetDeviceExtensions_static(env);
  • vkGetDeviceExtensions_dynamic (API ≤ 29) — resolves vkCreateInstance, vkEnumeratePhysicalDevices, vkEnumerateDeviceExtensionProperties, vkDestroyInstance via dlsym; populates a full VkApplicationInfo; cleans up (vkDestroyInstance + dlclose) on every exit path; releases JNI local refs in the loop; null-checks all allocations and JNI calls.

  • vkGetDeviceExtensions_static (API > 29) — upstream code verbatim; only change is the return type jlongjobjectArray.

  • Return type fix — the JNI entry point and both helpers now return jobjectArray instead of jlong, matching the Java declaration public static native String[] vkGetDeviceExtensions().

  • make_empty_array helper — shared by both paths; includes a null guard on FindClass.

  • New includes<dlfcn.h>, <string.h>, <android/api-level.h>.

Original prompt

Context

The file app/src/main/cpp/winlator/gpu_helper.c was modified in commit 5476493f5d87b251772a95213396de9d2dde1872 to fix a crash on Android 10 (API 29) by switching from static Vulkan linking to dynamic loading via dlopen/dlsym. However, the goal is to minimize changes to the upstream code (from utkarshdalal/GameNative) and only apply the dynamic-load fix where it's actually needed.

Required Changes

Restructure app/src/main/cpp/winlator/gpu_helper.c so that:

  1. A runtime API-level check determines which code path runs, using android_get_device_api_level() from <android/api-level.h>.

  2. For API ≤ 29: Use the dynamic-loading implementation (the fix from commit 5476493), which:

    • Loads libvulkan.so at runtime via dlopen/dlsym
    • Provides a fully populated VkApplicationInfo with VK_API_VERSION_1_0
    • Properly cleans up with vkDestroyInstance and dlclose on all code paths
    • Releases JNI local refs in the extension name loop
    • Has comprehensive null checks
  3. For API > 29: Use the original upstream code from utkarshdalal/GameNative, preserved as-is. The original upstream code for reference is:

#include <jni.h>
#include <vulkan/vulkan.h>
#include <stdlib.h>

JNIEXPORT jlong JNICALL
Java_com_winlator_core_GPUHelper_vkGetDeviceExtensions(JNIEnv *env, jclass clazz)
{
    VkInstance instance;
    VkResult   res;

    VkInstanceCreateInfo ci = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO };
    res = vkCreateInstance(&ci, NULL, &instance);
    if (res != VK_SUCCESS) goto make_empty_array;

    uint32_t pdCount = 0;
    res = vkEnumeratePhysicalDevices(instance, &pdCount, NULL);
    if (res != VK_SUCCESS || pdCount == 0) goto make_empty_array;

    pdCount = 1;                        /* original code asks for only one */
    VkPhysicalDevice pd;
    res = vkEnumeratePhysicalDevices(instance, &pdCount, &pd);
    if (!(res == VK_SUCCESS || res == VK_INCOMPLETE)) goto make_empty_array;

    uint32_t extCount = 0;
    res = vkEnumerateDeviceExtensionProperties(pd, NULL, &extCount, NULL);
    if (res != VK_SUCCESS || extCount == 0) goto make_empty_array;

    VkExtensionProperties *ext =
            calloc(extCount, sizeof(VkExtensionProperties));
    res = vkEnumerateDeviceExtensionProperties(pd, NULL, &extCount, ext);
    if (res != VK_SUCCESS) { free(ext); goto make_empty_array; }

    jclass stringCls = (*env)->FindClass(env, "java/lang/String");
    jobjectArray arr =
            (*env)->NewObjectArray(env, (jsize)extCount, stringCls, NULL);

    for (jsize i = 0; i < (jsize)extCount; ++i)
    {
        jstring js = (*env)->NewStringUTF(env, ext[i].extensionName);
        (*env)->SetObjectArrayElement(env, arr, i, js);
    }
    free(ext);
    return (jlong)arr;

    make_empty_array:
    {
        jclass stringCls = (*env)->FindClass(env, "java/lang/String");
        jobjectArray empty = (*env)->NewObjectArray(env, 0, stringCls, NULL);
        return (jlong)empty;
    }
}
  1. The JNI entry point Java_com_winlator_core_GPUHelper_vkGetDeviceExtensions should have its return type changed from jlong to jobjectArray (this is mandatory on ALL paths because the Java declaration is public static native String[] vkGetDeviceExtensions() — returning jlong is undefined behavior). The entry point should simply branch:
JNIEXPORT jobjectArray JNICALL
Java_com_winlator_core_GPUHelper_vkGetDeviceExtensions(JNIEnv *env, jclass clazz)
{
    (void)clazz;
    if (android_get_device_api_level() <= 29) {
        return vkGetDeviceExtensions_dynamic(env);
    }
    return vkGetDeviceExtensions_static(env);
}
  1. The structure should be:

    • A make_empty_array() helper at the top
    • static jobjectArray vkGetDeviceExtensions_dynamic(JNIEnv *env) — the dynamic-load fix for API ≤ 29
    • static jobjectArray vkGetDeviceExtensions_static(JNIEnv *env) — the original upstream code (with only the jlongjobjectArray return type fix applied)
    • The JNI entry point that branches based on android_get_device_api_level()
  2. Required includes: <jni.h>, <vulkan/vulkan.h>, <stdlib.h>, <dlfcn.h>, <string.h>, <android/api-level.h>

Important Notes

  • The base for this PR should be the current state of the file (which is the dynamic-load version from commit 5476493). The PR needs to restructure it into the two-path approach described above.
  • Keep the upstream static path as close to the original as possible — the only change should be the return type from jlong to jobjectArray and corresponding return statements.

This pull request was created from Copilot chat.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Catpotatos and others added 4 commits February 21, 2026 16:52
-- still to be tested.
currently being stuck at log in due to SQLite error from ( @query("SELECT * FROM epic_games WHERE is_dlc .... )
will come back to this once i can test
…r SQLite compatibility - changed DAO for EpicGame and GOGGame. This fixed crash in API 29 (Android 10) otherwise Skip steam login or steam log in triggers native crash.
 Fix - ContainerConfigDialog.kt, gpuExtensions is computed inside remember { ... } and mutates gpuExtensionsErrorDialogState inside the catch. That is a side-effect during composition (inside remember init), which can lead to invalid bytecode / verifier failure on some devices (especially older runtimes like API 29) - created GpuCompatHelper to avoid the issue.

This commit refactors the handling of Vulkan extension retrieval for API 29 devices with Mali GPUs.

The logic, which previously existed directly within `ContainerConfigDialog.kt`, has been extracted into a new `GpuCompatHelper` class. This helper now safely attempts to get the device's Vulkan extensions and provides a default list if the native call fails, preventing a crash.

The `ContainerConfigDialog` now uses this helper to get the extensions and displays an informational dialog to the user if the fallback list was used. This improves code organization and isolates the compatibility workaround.
…or Android 10 fix

Co-authored-by: Catpotatos <221862400+Catpotatos@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix dynamic loading for Vulkan on Android 10 gpu_helper: dual-path Vulkan init with runtime API-level branch Mar 5, 2026
Catpotatos pushed a commit that referenced this pull request Mar 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants