builtin-programs/gpu/enumerate.folk

set cc [C]
$cc cflags -I./vendor
$cc code {
    #define VOLK_IMPLEMENTATION
    #include "volk/volk.h"
}

$cc proc enumerateDisplays {} Jim_Obj* {
    FOLK_ENSURE(volkInitialize() == VK_SUCCESS);

    // Create a minimal Vulkan instance for display enumeration.
    VkInstance instance;
    {
        VkInstanceCreateInfo createInfo = {0};
        createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;

        const char* enabledExtensions[] = {
            VK_KHR_DISPLAY_EXTENSION_NAME,
            VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
#ifdef __APPLE__
            VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
#endif
        };
        createInfo.enabledExtensionCount = sizeof(enabledExtensions)/sizeof(enabledExtensions[0]);
        createInfo.ppEnabledExtensionNames = enabledExtensions;

#ifdef __APPLE__
        createInfo.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
#endif

        VkResult res = vkCreateInstance(&createInfo, NULL, &instance);
        if (res != VK_SUCCESS) {
            if (res == VK_ERROR_EXTENSION_NOT_PRESENT) {
                FOLK_ERROR("Unable to enumerate displays (for example, this doesn't work on macOS)");
            }
            FOLK_ERROR("Failed to create Vulkan instance: %d", res);
        }
    }

    volkLoadInstance(instance);

    uint32_t physicalDeviceCount = 0;
    vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, NULL);
    if (physicalDeviceCount == 0) {
        vkDestroyInstance(instance, NULL);
        return Jim_NewListObj(interp, NULL, 0);
    }

    VkPhysicalDevice physicalDevices[physicalDeviceCount];
    vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, physicalDevices);

    Jim_Obj *allDisplaysList = Jim_NewListObj(interp, NULL, 0);

    // Enumerate displays for each physical device
    for (uint32_t devIdx = 0; devIdx < physicalDeviceCount; devIdx++) {
        VkPhysicalDevice physicalDevice = physicalDevices[devIdx];

        uint32_t displayCount;
        vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayCount, NULL);

        VkDisplayPropertiesKHR displayProps[displayCount];
        vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayCount, displayProps);

        for (uint32_t i = 0; i < displayCount; i++) {
            uint32_t modeCount;
            vkGetDisplayModePropertiesKHR(physicalDevice, displayProps[i].display,
                                          &modeCount, NULL);

            VkDisplayModePropertiesKHR modeProps[modeCount];
            vkGetDisplayModePropertiesKHR(physicalDevice, displayProps[i].display,
                                          &modeCount, modeProps);

            Jim_Obj *modesList = Jim_NewListObj(interp, NULL, 0);
            for (uint32_t j = 0; j < modeCount; j++) {
                Jim_Obj *modeDict = Jim_ObjPrintf("visibleRegion {%u %u} refreshRate %u",
                    modeProps[j].parameters.visibleRegion.width,
                    modeProps[j].parameters.visibleRegion.height,
                    modeProps[j].parameters.refreshRate);
                Jim_ListAppendElement(interp, modesList, modeDict);
            }

            int modesLen;
            const char *modesStr = Jim_GetString(modesList, &modesLen);
            Jim_Obj *displayDict = Jim_ObjPrintf("name {%s} physicalDimensions {%u %u} "
                                                 "physicalResolution {%u %u} modes {%s}",
                displayProps[i].displayName ? displayProps[i].displayName : "",
                displayProps[i].physicalDimensions.width,
                displayProps[i].physicalDimensions.height,
                displayProps[i].physicalResolution.width,
                displayProps[i].physicalResolution.height,
                modesStr);

            Jim_ListAppendElement(interp, allDisplaysList, displayDict);
        }
    }

    vkDestroyInstance(instance, NULL);

    return allDisplaysList;
}

set displayLib [$cc compile]

if {![catch {exec pkg-config --exists glfw3}]} {
    Claim $::thisNode has display glfw with info {name "GLFW (windowed)"}
}

try {
    foreach display [$displayLib enumerateDisplays] {
        Claim $::thisNode has display $display(name) with info $display
    }
} finally {
    Claim $::thisNode has had displays enumerated
}