//
// Copyright 2016 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

// FunctionsEGL.cpp: Implements the FunctionsEGL class.

#include "libANGLE/renderer/gl/egl/FunctionsEGL.h"

#include <algorithm>

#include "common/platform.h"
#include "common/string_utils.h"
#include "libANGLE/renderer/driver_utils.h"
#include "libANGLE/renderer/gl/FunctionsGL.h"
#include "libANGLE/renderer/gl/egl/functionsegl_typedefs.h"

#if defined(ANGLE_HAS_LIBDRM)
// clang-format off
#include <fcntl.h>
#include <unistd.h>
#include <xf86drm.h>
// clang-format on
#endif  // defined(ANGLE_HAS_LIBDRM)

namespace
{

template <typename T>
bool SetPtr(T *dst, void *src)
{
    if (src)
    {
        *dst = reinterpret_cast<T>(src);
        return true;
    }
    return false;
}

bool IsValidPlatformTypeForPlatformDisplayConnection(EGLAttrib platformType)
{
    switch (platformType)
    {
        case EGL_PLATFORM_SURFACELESS_MESA:
        case EGL_PLATFORM_GBM_KHR:
        case EGL_PLATFORM_WAYLAND_EXT:
            return true;
        default:
            break;
    }
    return false;
}

}  // namespace

namespace rx
{

struct FunctionsEGL::EGLDispatchTable
{
    EGLDispatchTable()
        : bindAPIPtr(nullptr),
          chooseConfigPtr(nullptr),
          createContextPtr(nullptr),
          createPbufferSurfacePtr(nullptr),
          createWindowSurfacePtr(nullptr),
          destroyContextPtr(nullptr),
          destroySurfacePtr(nullptr),
          getConfigAttribPtr(nullptr),
          getConfigsPtr(nullptr),
          getCurrentSurfacePtr(nullptr),
          getDisplayPtr(nullptr),
          getErrorPtr(nullptr),
          initializePtr(nullptr),
          makeCurrentPtr(nullptr),
          queryStringPtr(nullptr),
          querySurfacePtr(nullptr),
          swapBuffersPtr(nullptr),
          terminatePtr(nullptr),

          bindTexImagePtr(nullptr),
          releaseTexImagePtr(nullptr),
          surfaceAttribPtr(nullptr),
          swapIntervalPtr(nullptr),

          getCurrentContextPtr(nullptr),

          createImageKHRPtr(nullptr),
          destroyImageKHRPtr(nullptr),

          createSyncKHRPtr(nullptr),
          destroySyncKHRPtr(nullptr),
          clientWaitSyncKHRPtr(nullptr),
          getSyncAttribKHRPtr(nullptr),

          waitSyncKHRPtr(nullptr),

          swapBuffersWithDamageKHRPtr(nullptr),

          presentationTimeANDROIDPtr(nullptr),

          setBlobCacheFuncsANDROIDPtr(nullptr),

          getCompositorTimingSupportedANDROIDPtr(nullptr),
          getCompositorTimingANDROIDPtr(nullptr),
          getNextFrameIdANDROIDPtr(nullptr),
          getFrameTimestampSupportedANDROIDPtr(nullptr),
          getFrameTimestampsANDROIDPtr(nullptr),

          dupNativeFenceFDANDROIDPtr(nullptr),

          queryDmaBufFormatsEXTPtr(nullptr),
          queryDmaBufModifiersEXTPtr(nullptr),

          queryDeviceAttribEXTPtr(nullptr),
          queryDeviceStringEXTPtr(nullptr),
          queryDisplayAttribEXTPtr(nullptr),

          getDisplayDriverNamePtr(nullptr)
    {}

    // 1.0
    PFNEGLBINDAPIPROC bindAPIPtr;
    PFNEGLCHOOSECONFIGPROC chooseConfigPtr;
    PFNEGLCREATECONTEXTPROC createContextPtr;
    PFNEGLCREATEPBUFFERSURFACEPROC createPbufferSurfacePtr;
    PFNEGLCREATEWINDOWSURFACEPROC createWindowSurfacePtr;
    PFNEGLDESTROYCONTEXTPROC destroyContextPtr;
    PFNEGLDESTROYSURFACEPROC destroySurfacePtr;
    PFNEGLGETCONFIGATTRIBPROC getConfigAttribPtr;
    PFNEGLGETCONFIGSPROC getConfigsPtr;
    PFNEGLGETCURRENTSURFACEPROC getCurrentSurfacePtr;
    PFNEGLGETDISPLAYPROC getDisplayPtr;
    PFNEGLGETERRORPROC getErrorPtr;
    PFNEGLINITIALIZEPROC initializePtr;
    PFNEGLMAKECURRENTPROC makeCurrentPtr;
    PFNEGLQUERYSTRINGPROC queryStringPtr;
    PFNEGLQUERYSURFACEPROC querySurfacePtr;
    PFNEGLSWAPBUFFERSPROC swapBuffersPtr;
    PFNEGLTERMINATEPROC terminatePtr;

    // 1.1
    PFNEGLBINDTEXIMAGEPROC bindTexImagePtr;
    PFNEGLRELEASETEXIMAGEPROC releaseTexImagePtr;
    PFNEGLSURFACEATTRIBPROC surfaceAttribPtr;
    PFNEGLSWAPINTERVALPROC swapIntervalPtr;

    // 1.4
    PFNEGLGETCURRENTCONTEXTPROC getCurrentContextPtr;

    // EGL_KHR_image
    PFNEGLCREATEIMAGEKHRPROC createImageKHRPtr;
    PFNEGLDESTROYIMAGEKHRPROC destroyImageKHRPtr;

    // EGL_KHR_fence_sync
    PFNEGLCREATESYNCKHRPROC createSyncKHRPtr;
    PFNEGLDESTROYSYNCKHRPROC destroySyncKHRPtr;
    PFNEGLCLIENTWAITSYNCKHRPROC clientWaitSyncKHRPtr;
    PFNEGLGETSYNCATTRIBKHRPROC getSyncAttribKHRPtr;

    // EGL_KHR_wait_sync
    PFNEGLWAITSYNCKHRPROC waitSyncKHRPtr;

    // EGL_KHR_swap_buffers_with_damage
    PFNEGLSWAPBUFFERSWITHDAMAGEKHRPROC swapBuffersWithDamageKHRPtr;

    // EGL_ANDROID_presentation_time
    PFNEGLPRESENTATIONTIMEANDROIDPROC presentationTimeANDROIDPtr;

    // EGL_ANDROID_blob_cache
    PFNEGLSETBLOBCACHEFUNCSANDROIDPROC setBlobCacheFuncsANDROIDPtr;

    // EGL_ANDROID_get_frame_timestamps
    PFNEGLGETCOMPOSITORTIMINGSUPPORTEDANDROIDPROC getCompositorTimingSupportedANDROIDPtr;
    PFNEGLGETCOMPOSITORTIMINGANDROIDPROC getCompositorTimingANDROIDPtr;
    PFNEGLGETNEXTFRAMEIDANDROIDPROC getNextFrameIdANDROIDPtr;
    PFNEGLGETFRAMETIMESTAMPSUPPORTEDANDROIDPROC getFrameTimestampSupportedANDROIDPtr;
    PFNEGLGETFRAMETIMESTAMPSANDROIDPROC getFrameTimestampsANDROIDPtr;

    // EGL_ANDROID_native_fence_sync
    PFNEGLDUPNATIVEFENCEFDANDROIDPROC dupNativeFenceFDANDROIDPtr;

    // EGL_EXT_image_dma_buf_import_modifiers
    PFNEGLQUERYDMABUFFORMATSEXTPROC queryDmaBufFormatsEXTPtr;
    PFNEGLQUERYDMABUFMODIFIERSEXTPROC queryDmaBufModifiersEXTPtr;

    // EGL_EXT_device_query
    PFNEGLQUERYDEVICEATTRIBEXTPROC queryDeviceAttribEXTPtr;
    PFNEGLQUERYDEVICESTRINGEXTPROC queryDeviceStringEXTPtr;
    PFNEGLQUERYDISPLAYATTRIBEXTPROC queryDisplayAttribEXTPtr;

    // EGL_MESA_query_driver
    PFNEGLGETDISPLAYDRIVERNAMEPROC getDisplayDriverNamePtr;
};

FunctionsEGL::FunctionsEGL()
    : majorVersion(0), minorVersion(0), mFnPtrs(new EGLDispatchTable()), mEGLDisplay(EGL_NO_DISPLAY)
{}

FunctionsEGL::~FunctionsEGL()
{
    SafeDelete(mFnPtrs);
}

egl::Error FunctionsEGL::initialize(EGLAttrib platformType, EGLNativeDisplayType nativeDisplay)
{
#define ANGLE_GET_PROC_OR_WARNING(MEMBER, NAME)                \
    do                                                         \
    {                                                          \
        if (!SetPtr(MEMBER, getProcAddress(#NAME)))            \
        {                                                      \
            WARN() << "Could not load EGL entry point " #NAME; \
        }                                                      \
    } while (0)
#define ANGLE_GET_PROC_OR_ERROR(MEMBER, NAME)                                                \
    do                                                                                       \
    {                                                                                        \
        if (!SetPtr(MEMBER, getProcAddress(#NAME)))                                          \
        {                                                                                    \
            return egl::Error(EGL_NOT_INITIALIZED, "Could not load EGL entry point " #NAME); \
        }                                                                                    \
    } while (0)

    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->bindAPIPtr, eglBindAPI);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->chooseConfigPtr, eglChooseConfig);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->createContextPtr, eglCreateContext);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->createPbufferSurfacePtr, eglCreatePbufferSurface);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->createWindowSurfacePtr, eglCreateWindowSurface);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->destroyContextPtr, eglDestroyContext);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->destroySurfacePtr, eglDestroySurface);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->getConfigAttribPtr, eglGetConfigAttrib);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->getConfigsPtr, eglGetConfigs);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->getCurrentSurfacePtr, eglGetCurrentSurface);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->getDisplayPtr, eglGetDisplay);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->getErrorPtr, eglGetError);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->initializePtr, eglInitialize);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->makeCurrentPtr, eglMakeCurrent);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->queryStringPtr, eglQueryString);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->querySurfacePtr, eglQuerySurface);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->swapBuffersPtr, eglSwapBuffers);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->terminatePtr, eglTerminate);

    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->bindTexImagePtr, eglBindTexImage);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->releaseTexImagePtr, eglReleaseTexImage);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->surfaceAttribPtr, eglSurfaceAttrib);
    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->swapIntervalPtr, eglSwapInterval);

    // Querying EGL_EXTENSIONS string and loading it into the mExtensions
    // vector will at this point retrieve the client extensions since mEGLDisplay is still
    // EGL_NO_DISPLAY. This is desired, and mExtensions will later be reinitialized with the display
    // extensions once the display is created and initialized.
    queryExtensions();

#if defined(ANGLE_USE_WAYLAND)
    if (platformType == EGL_PLATFORM_WAYLAND_EXT)
    {
        mEGLDisplay = getPlatformDisplay(platformType, nativeDisplay);
    }
#endif
#if defined(ANGLE_HAS_LIBDRM)
    if (mEGLDisplay == EGL_NO_DISPLAY && (platformType != EGL_PLATFORM_GBM_KHR || !nativeDisplay))
    {
        mEGLDisplay = getPreferredDisplay(&majorVersion, &minorVersion);
    }
#endif  // defined(ANGLE_HAS_LIBDRM)

    if (mEGLDisplay == EGL_NO_DISPLAY)
    {
        if (IsValidPlatformTypeForPlatformDisplayConnection(platformType))
        {
            mEGLDisplay = getPlatformDisplay(platformType, nativeDisplay);
        }
        else
        {
            mEGLDisplay = mFnPtrs->getDisplayPtr(nativeDisplay);
        }
    }

    if (mEGLDisplay != EGL_NO_DISPLAY &&
        mFnPtrs->initializePtr(mEGLDisplay, &majorVersion, &minorVersion) != EGL_TRUE)
    {
        mEGLDisplay = EGL_NO_DISPLAY;
    }
    if (mEGLDisplay == EGL_NO_DISPLAY)
    {
        // If no display was available, try to fallback to the first available
        // native device object's display.
        mEGLDisplay = getNativeDisplay(&majorVersion, &minorVersion);
    }
    if (mEGLDisplay == EGL_NO_DISPLAY)
    {
        return egl::Error(EGL_NOT_INITIALIZED, "Failed to get system egl display");
    }
    if (majorVersion < 1 || (majorVersion == 1 && minorVersion < 4))
    {
        return egl::Error(EGL_NOT_INITIALIZED, "Unsupported EGL version (require at least 1.4).");
    }
    if (mFnPtrs->bindAPIPtr(EGL_OPENGL_ES_API) != EGL_TRUE)
    {
        return egl::Error(mFnPtrs->getErrorPtr(), "Failed to bind API in system egl");
    }

    vendorString  = queryString(EGL_VENDOR);
    versionString = queryString(EGL_VERSION);

    ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->getCurrentContextPtr, eglGetCurrentContext);

    if (!queryExtensions())
    {
        return egl::Error(mFnPtrs->getErrorPtr(), "Failed to query extensions in system egl");
    }

    if (hasExtension("EGL_KHR_image_base"))
    {
        ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->createImageKHRPtr, eglCreateImageKHR);
        ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->destroyImageKHRPtr, eglDestroyImageKHR);
    }
    if (hasExtension("EGL_KHR_fence_sync"))
    {
        ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->createSyncKHRPtr, eglCreateSyncKHR);
        ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->destroySyncKHRPtr, eglDestroySyncKHR);
        ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->clientWaitSyncKHRPtr, eglClientWaitSyncKHR);
        ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->getSyncAttribKHRPtr, eglGetSyncAttribKHR);
    }
    if (hasExtension("EGL_KHR_wait_sync"))
    {
        ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->waitSyncKHRPtr, eglWaitSyncKHR);
    }

    if (hasExtension("EGL_KHR_swap_buffers_with_damage"))
    {
        ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->swapBuffersWithDamageKHRPtr, eglSwapBuffersWithDamageKHR);
    }

    if (hasExtension("EGL_ANDROID_presentation_time"))
    {
        ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->presentationTimeANDROIDPtr, eglPresentationTimeANDROID);
    }

    if (hasExtension("EGL_ANDROID_blob_cache"))
    {
        ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->setBlobCacheFuncsANDROIDPtr, eglSetBlobCacheFuncsANDROID);
    }

    if (hasExtension("EGL_ANDROID_get_frame_timestamps"))
    {
        ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->getCompositorTimingSupportedANDROIDPtr,
                                eglGetCompositorTimingSupportedANDROID);
        ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->getCompositorTimingANDROIDPtr,
                                eglGetCompositorTimingANDROID);
        ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->getNextFrameIdANDROIDPtr, eglGetNextFrameIdANDROID);
        ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->getFrameTimestampSupportedANDROIDPtr,
                                eglGetFrameTimestampSupportedANDROID);
        ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->getFrameTimestampsANDROIDPtr,
                                eglGetFrameTimestampsANDROID);
    }

    // The native fence sync extension is a bit complicated. It's reported as present for ChromeOS,
    // but Android currently doesn't report this extension even when it's present, and older devices
    // may export a useless wrapper function. See crbug.com/775707 for details. In short, if the
    // symbol is present and we're on Android N or newer, assume that it's usable even if the
    // extension wasn't reported.
    if (hasExtension("EGL_ANDROID_native_fence_sync") || GetAndroidSDKVersion() >= 24)
    {
        // Don't error trying to load this entry point.
        if (SetPtr(&mFnPtrs->dupNativeFenceFDANDROIDPtr,
                   getProcAddress("eglDupNativeFenceFDANDROID")) &&
            !hasExtension("EGL_ANDROID_native_fence_sync"))
        {
            mExtensions.push_back("EGL_ANDROID_native_fence_sync");
        }
    }

    if (hasExtension("EGL_EXT_image_dma_buf_import_modifiers"))
    {
        // https://anglebug.com/42266130
        // Some drivers, notably older versions of ANGLE, announce this extension without
        // implementing the following functions. DisplayEGL checks for this case and disables the
        // extension.
        ANGLE_GET_PROC_OR_WARNING(&mFnPtrs->queryDmaBufFormatsEXTPtr, eglQueryDmaBufFormatsEXT);
        ANGLE_GET_PROC_OR_WARNING(&mFnPtrs->queryDmaBufModifiersEXTPtr, eglQueryDmaBufModifiersEXT);
    }

    // EGL_EXT_device_query is only advertised in extension string in the
    // no-display case, see getNativeDisplay().
    if (SetPtr(&mFnPtrs->queryDeviceAttribEXTPtr, getProcAddress("eglQueryDeviceAttribEXT")) &&
        SetPtr(&mFnPtrs->queryDeviceStringEXTPtr, getProcAddress("eglQueryDeviceStringEXT")) &&
        SetPtr(&mFnPtrs->queryDisplayAttribEXTPtr, getProcAddress("eglQueryDisplayAttribEXT")))
    {
        mExtensions.push_back("EGL_EXT_device_query");
    }

    if (hasExtension("EGL_MESA_query_driver"))
    {
        ANGLE_GET_PROC_OR_ERROR(&mFnPtrs->getDisplayDriverNamePtr, eglGetDisplayDriverName);
    }

#undef ANGLE_GET_PROC_OR_ERROR

    return egl::NoError();
}

egl::Error FunctionsEGL::terminate()
{
    if (mFnPtrs->terminatePtr == nullptr || mFnPtrs->terminatePtr(mEGLDisplay) == EGL_TRUE)
    {
        mEGLDisplay = nullptr;
        return egl::NoError();
    }
    return egl::Error(mFnPtrs->getErrorPtr());
}

bool FunctionsEGL::queryExtensions()
{
    const char *extensions = queryString(EGL_EXTENSIONS);
    if (!extensions)
    {
        return false;
    }

    angle::SplitStringAlongWhitespace(extensions, &mExtensions);

    return true;
}

EGLDisplay FunctionsEGL::getPlatformDisplay(EGLAttrib platformType,
                                            EGLNativeDisplayType nativeDisplay)
{
    PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplayEXTPtr;
    if (!hasExtension("EGL_EXT_platform_base") ||
        !SetPtr(&getPlatformDisplayEXTPtr, getProcAddress("eglGetPlatformDisplayEXT")))
    {
        return EGL_NO_DISPLAY;
    }

    ASSERT(IsValidPlatformTypeForPlatformDisplayConnection(platformType));
    switch (platformType)
    {
        case EGL_PLATFORM_SURFACELESS_MESA:
            if (!hasExtension("EGL_MESA_platform_surfaceless"))
                return EGL_NO_DISPLAY;
            break;
        case EGL_PLATFORM_GBM_KHR:
            if (!hasExtension("EGL_KHR_platform_gbm") && !hasExtension("EGL_MESA_platform_gbm"))
            {
                return EGL_NO_DISPLAY;
            }
            break;
        case EGL_PLATFORM_WAYLAND_EXT:
            if (!hasExtension("EGL_EXT_platform_wayland"))
            {
                return EGL_NO_DISPLAY;
            }
            break;
        default:
            UNREACHABLE();
            return EGL_NO_DISPLAY;
    }

    return getPlatformDisplayEXTPtr(static_cast<EGLenum>(platformType),
                                    reinterpret_cast<void *>(nativeDisplay), nullptr);
}

std::vector<EGLDeviceEXT> FunctionsEGL::queryDevices(int *major, int *minor)
{
    // Only called after confirming we have the necessary extension.
    PFNEGLQUERYDEVICESEXTPROC queryDevicesEXTPtr;
    if (!SetPtr(&queryDevicesEXTPtr, getProcAddress("eglQueryDevicesEXT")))
    {
        return {};
    }

    // Get a list of native device objects.
    const EGLint kMaxDevices = 32;
    EGLint numDevices        = 0;
    std::vector<EGLDeviceEXT> devices(kMaxDevices, EGL_NO_DEVICE_EXT);
    if (!queryDevicesEXTPtr(kMaxDevices, devices.data(), &numDevices))
    {
        return {};
    }

    devices.resize(numDevices);
    return devices;
}

EGLDisplay FunctionsEGL::getNativeDisplay(int *major, int *minor)
{
    // This fallback mechanism makes use of:
    // - EGL_EXT_device_enumeration or EGL_EXT_device_base for eglQueryDevicesEXT
    // - EGL_EXT_platform_base for eglGetPlatformDisplayEXT
    // - EGL_EXT_platform_device for EGL_PLATFORM_DEVICE_EXT
    PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplayEXTPtr;
    bool hasQueryDevicesEXT =
        hasExtension("EGL_EXT_device_enumeration") || hasExtension("EGL_EXT_device_base");
    bool hasPlatformBaseEXT   = hasExtension("EGL_EXT_platform_base");
    bool hasPlatformDeviceEXT = hasExtension("EGL_EXT_platform_device");
    if (!hasQueryDevicesEXT || !hasPlatformBaseEXT || !hasPlatformDeviceEXT)
    {
        return EGL_NO_DISPLAY;
    }

    if (!SetPtr(&getPlatformDisplayEXTPtr, getProcAddress("eglGetPlatformDisplayEXT")))
    {
        return EGL_NO_DISPLAY;
    }

    std::vector<EGLDeviceEXT> devices = queryDevices(major, minor);
    if (devices.size() == 0)
    {
        return EGL_NO_DISPLAY;
    }

    // Look for the first native device that gives us a valid display.
    for (const EGLDeviceEXT device : devices)
    {
        EGLDisplay display = getPlatformDisplayEXTPtr(EGL_PLATFORM_DEVICE_EXT, device, nullptr);
        if (mFnPtrs->getErrorPtr() == EGL_SUCCESS &&
            mFnPtrs->initializePtr(display, major, minor) == EGL_TRUE)
        {
            return display;
        }
    }

    return EGL_NO_DISPLAY;
}

#if defined(ANGLE_HAS_LIBDRM)
EGLDeviceEXT FunctionsEGL::getPreferredEGLDevice(const std::vector<EGLDeviceEXT> &devices)
{
    // Only called after confirming we have the necessary extension.
    PFNEGLQUERYDEVICESTRINGEXTPROC queryDeviceStringEXTPtr;
    if (!SetPtr(&queryDeviceStringEXTPtr, getProcAddress("eglQueryDeviceStringEXT")))
    {
        return EGL_NO_DEVICE_EXT;
    }

    std::map<EGLDeviceEXT, std::string> device_drivers;
    for (const EGLDeviceEXT device : devices)
    {
        const char *filename = queryDeviceStringEXTPtr(device, EGL_DRM_DEVICE_FILE_EXT);
        if (!filename)
        {
            continue;
        }

        int fd = open(filename, O_RDWR);
        if (!fd)
        {
            continue;
        }

        drmVersionPtr version = drmGetVersion(fd);
        if (version)
        {
            const std::string driver_name(version->name, version->name_len);
            device_drivers.insert({device, driver_name});
        }

        drmFreeVersion(version);
        close(fd);
    }

    const char *preferred_drivers[3] = {"i915", "amdgpu", "virtio_gpu"};
    for (const char *preferred_driver : preferred_drivers)
    {
        for (const EGLDeviceEXT device : devices)
        {
            const auto driver = device_drivers.find(device);
            if (driver != device_drivers.end() && driver->second == preferred_driver)
            {
                return device;
            }
        }
    }

    return EGL_NO_DEVICE_EXT;
}

EGLDisplay FunctionsEGL::getPreferredDisplay(int *major, int *minor)
{
    // This  mechanism makes use of:
    // - EGL_EXT_device_enumeration or EGL_EXT_device_base for eglQueryDevicesEXT
    // - EGL_EXT_platform_base for eglGetPlatformDisplayEXT
    // - EGL_EXT_platform_device for EGL_PLATFORM_DEVICE_EXT
    // - EGL_EXT_device_query for eglQueryDeviceStringEXT
    PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplayEXTPtr;
    bool hasQueryDevicesEXT =
        hasExtension("EGL_EXT_device_enumeration") || hasExtension("EGL_EXT_device_base");
    bool hasPlatformBaseEXT   = hasExtension("EGL_EXT_platform_base");
    bool hasPlatformDeviceEXT = hasExtension("EGL_EXT_platform_device");
    bool hasDeviceQueryEXT    = hasExtension("EGL_EXT_device_query");
    if (!hasQueryDevicesEXT || !hasPlatformBaseEXT || !hasPlatformDeviceEXT || !hasDeviceQueryEXT)
    {
        return EGL_NO_DISPLAY;
    }

    if (!SetPtr(&getPlatformDisplayEXTPtr, getProcAddress("eglGetPlatformDisplayEXT")))
    {
        return EGL_NO_DISPLAY;
    }

    std::vector<EGLDeviceEXT> devices = queryDevices(major, minor);
    if (devices.size() == 0)
    {
        return EGL_NO_DISPLAY;
    }

    EGLDeviceEXT device = getPreferredEGLDevice(devices);
    if (device == EGL_NO_DEVICE_EXT)
    {
        return EGL_NO_DISPLAY;
    }

    EGLDisplay display = getPlatformDisplayEXTPtr(EGL_PLATFORM_DEVICE_EXT, device, nullptr);
    if (mFnPtrs->getErrorPtr() == EGL_SUCCESS)
    {
        return display;
    }

    return EGL_NO_DISPLAY;
}
#endif  // defined(ANGLE_HAS_LIBDRM)

class FunctionsGLEGL : public FunctionsGL
{
  public:
    FunctionsGLEGL(const FunctionsEGL &egl, const char *displayDriverName)
        : mEGL(egl), mDisplayDriverName(displayDriverName != nullptr ? displayDriverName : "")
    {}

    ~FunctionsGLEGL() override {}

    const char *getDriverName() const override { return mDisplayDriverName.c_str(); }

  private:
    void *loadProcAddress(const std::string &function) const override
    {
        return mEGL.getProcAddress(function.c_str());
    }

    const FunctionsEGL &mEGL;
    std::string mDisplayDriverName;
};

FunctionsGL *FunctionsEGL::makeFunctionsGL(void) const
{
    return new FunctionsGLEGL(*this, getDisplayDriverName());
}

bool FunctionsEGL::hasExtension(const char *extension) const
{
    return std::find(mExtensions.begin(), mExtensions.end(), extension) != mExtensions.end();
}

bool FunctionsEGL::hasDmaBufImportModifierFunctions() const
{
    return mFnPtrs->queryDmaBufFormatsEXTPtr != nullptr &&
           mFnPtrs->queryDmaBufModifiersEXTPtr != nullptr;
}

EGLDisplay FunctionsEGL::getDisplay() const
{
    return mEGLDisplay;
}

EGLint FunctionsEGL::getError() const
{
    return mFnPtrs->getErrorPtr();
}

EGLBoolean FunctionsEGL::chooseConfig(EGLint const *attribList,
                                      EGLConfig *configs,
                                      EGLint configSize,
                                      EGLint *numConfig) const
{
    return mFnPtrs->chooseConfigPtr(mEGLDisplay, attribList, configs, configSize, numConfig);
}

EGLBoolean FunctionsEGL::getConfigs(EGLConfig *configs,
                                    EGLint config_size,
                                    EGLint *num_config) const
{
    return mFnPtrs->getConfigsPtr(mEGLDisplay, configs, config_size, num_config);
}

EGLBoolean FunctionsEGL::getConfigAttrib(EGLConfig config, EGLint attribute, EGLint *value) const
{
    return mFnPtrs->getConfigAttribPtr(mEGLDisplay, config, attribute, value);
}

EGLSurface FunctionsEGL::getCurrentSurface(EGLint readdraw) const
{
    return mFnPtrs->getCurrentSurfacePtr(readdraw);
}

EGLContext FunctionsEGL::createContext(EGLConfig config,
                                       EGLContext share_context,
                                       EGLint const *attrib_list) const
{
    return mFnPtrs->createContextPtr(mEGLDisplay, config, share_context, attrib_list);
}

EGLSurface FunctionsEGL::createPbufferSurface(EGLConfig config, const EGLint *attrib_list) const
{
    return mFnPtrs->createPbufferSurfacePtr(mEGLDisplay, config, attrib_list);
}

EGLSurface FunctionsEGL::createWindowSurface(EGLConfig config,
                                             EGLNativeWindowType win,
                                             const EGLint *attrib_list) const
{
    return mFnPtrs->createWindowSurfacePtr(mEGLDisplay, config, win, attrib_list);
}

EGLBoolean FunctionsEGL::destroyContext(EGLContext context) const
{
    return mFnPtrs->destroyContextPtr(mEGLDisplay, context);
}

EGLBoolean FunctionsEGL::destroySurface(EGLSurface surface) const
{
    return mFnPtrs->destroySurfacePtr(mEGLDisplay, surface);
}

EGLBoolean FunctionsEGL::makeCurrent(EGLSurface surface, EGLContext context) const
{
    return mFnPtrs->makeCurrentPtr(mEGLDisplay, surface, surface, context);
}

char const *FunctionsEGL::queryString(EGLint name) const
{
    return mFnPtrs->queryStringPtr(mEGLDisplay, name);
}

EGLBoolean FunctionsEGL::querySurface(EGLSurface surface, EGLint attribute, EGLint *value) const
{
    return mFnPtrs->querySurfacePtr(mEGLDisplay, surface, attribute, value);
}

EGLBoolean FunctionsEGL::swapBuffers(EGLSurface surface) const
{
    return mFnPtrs->swapBuffersPtr(mEGLDisplay, surface);
}

EGLBoolean FunctionsEGL::bindTexImage(EGLSurface surface, EGLint buffer) const
{
    return mFnPtrs->bindTexImagePtr(mEGLDisplay, surface, buffer);
}

EGLBoolean FunctionsEGL::releaseTexImage(EGLSurface surface, EGLint buffer) const
{
    return mFnPtrs->releaseTexImagePtr(mEGLDisplay, surface, buffer);
}

EGLBoolean FunctionsEGL::surfaceAttrib(EGLSurface surface, EGLint attribute, EGLint value) const
{
    return mFnPtrs->surfaceAttribPtr(mEGLDisplay, surface, attribute, value);
}

EGLBoolean FunctionsEGL::swapInterval(EGLint interval) const
{
    return mFnPtrs->swapIntervalPtr(mEGLDisplay, interval);
}

EGLContext FunctionsEGL::getCurrentContext() const
{
    return mFnPtrs->getCurrentContextPtr();
}

EGLImageKHR FunctionsEGL::createImageKHR(EGLContext context,
                                         EGLenum target,
                                         EGLClientBuffer buffer,
                                         const EGLint *attrib_list) const
{
    return mFnPtrs->createImageKHRPtr(mEGLDisplay, context, target, buffer, attrib_list);
}

EGLBoolean FunctionsEGL::destroyImageKHR(EGLImageKHR image) const
{
    return mFnPtrs->destroyImageKHRPtr(mEGLDisplay, image);
}

EGLSyncKHR FunctionsEGL::createSyncKHR(EGLenum type, const EGLint *attrib_list) const
{
    return mFnPtrs->createSyncKHRPtr(mEGLDisplay, type, attrib_list);
}

EGLBoolean FunctionsEGL::destroySyncKHR(EGLSyncKHR sync) const
{
    return mFnPtrs->destroySyncKHRPtr(mEGLDisplay, sync);
}

EGLint FunctionsEGL::clientWaitSyncKHR(EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout) const
{
    return mFnPtrs->clientWaitSyncKHRPtr(mEGLDisplay, sync, flags, timeout);
}

EGLBoolean FunctionsEGL::getSyncAttribKHR(EGLSyncKHR sync, EGLint attribute, EGLint *value) const
{
    return mFnPtrs->getSyncAttribKHRPtr(mEGLDisplay, sync, attribute, value);
}

EGLint FunctionsEGL::waitSyncKHR(EGLSyncKHR sync, EGLint flags) const
{
    return mFnPtrs->waitSyncKHRPtr(mEGLDisplay, sync, flags);
}

EGLBoolean FunctionsEGL::swapBuffersWithDamageKHR(EGLSurface surface,
                                                  const EGLint *rects,
                                                  EGLint n_rects) const
{
    return mFnPtrs->swapBuffersWithDamageKHRPtr(mEGLDisplay, surface, rects, n_rects);
}

EGLBoolean FunctionsEGL::presentationTimeANDROID(EGLSurface surface, EGLnsecsANDROID time) const
{
    return mFnPtrs->presentationTimeANDROIDPtr(mEGLDisplay, surface, time);
}

void FunctionsEGL::setBlobCacheFuncsANDROID(EGLSetBlobFuncANDROID set,
                                            EGLGetBlobFuncANDROID get) const
{
    return mFnPtrs->setBlobCacheFuncsANDROIDPtr(mEGLDisplay, set, get);
}

EGLBoolean FunctionsEGL::getCompositorTimingSupportedANDROID(EGLSurface surface, EGLint name) const
{
    return mFnPtrs->getCompositorTimingSupportedANDROIDPtr(mEGLDisplay, surface, name);
}

EGLBoolean FunctionsEGL::getCompositorTimingANDROID(EGLSurface surface,
                                                    EGLint numTimestamps,
                                                    const EGLint *names,
                                                    EGLnsecsANDROID *values) const
{
    return mFnPtrs->getCompositorTimingANDROIDPtr(mEGLDisplay, surface, numTimestamps, names,
                                                  values);
}

EGLBoolean FunctionsEGL::getNextFrameIdANDROID(EGLSurface surface, EGLuint64KHR *frameId) const
{
    return mFnPtrs->getNextFrameIdANDROIDPtr(mEGLDisplay, surface, frameId);
}

EGLBoolean FunctionsEGL::getFrameTimestampSupportedANDROID(EGLSurface surface,
                                                           EGLint timestamp) const
{
    return mFnPtrs->getFrameTimestampSupportedANDROIDPtr(mEGLDisplay, surface, timestamp);
}

EGLBoolean FunctionsEGL::getFrameTimestampsANDROID(EGLSurface surface,
                                                   EGLuint64KHR frameId,
                                                   EGLint numTimestamps,
                                                   const EGLint *timestamps,
                                                   EGLnsecsANDROID *values) const
{
    return mFnPtrs->getFrameTimestampsANDROIDPtr(mEGLDisplay, surface, frameId, numTimestamps,
                                                 timestamps, values);
}

EGLint FunctionsEGL::dupNativeFenceFDANDROID(EGLSync sync) const
{
    return mFnPtrs->dupNativeFenceFDANDROIDPtr(mEGLDisplay, sync);
}

EGLint FunctionsEGL::queryDmaBufFormatsEXT(EGLint maxFormats,
                                           EGLint *formats,
                                           EGLint *numFormats) const
{
    return mFnPtrs->queryDmaBufFormatsEXTPtr(mEGLDisplay, maxFormats, formats, numFormats);
}

EGLint FunctionsEGL::queryDmaBufModifiersEXT(EGLint format,
                                             EGLint maxModifiers,
                                             EGLuint64KHR *modifiers,
                                             EGLBoolean *externalOnly,
                                             EGLint *numModifiers) const
{
    return mFnPtrs->queryDmaBufModifiersEXTPtr(mEGLDisplay, format, maxModifiers, modifiers,
                                               externalOnly, numModifiers);
}

EGLBoolean FunctionsEGL::queryDeviceAttribEXT(EGLDeviceEXT device,
                                              EGLint attribute,
                                              EGLAttrib *value) const
{
    return mFnPtrs->queryDeviceAttribEXTPtr(device, attribute, value);
}

const char *FunctionsEGL::queryDeviceStringEXT(EGLDeviceEXT device, EGLint name) const
{
    return mFnPtrs->queryDeviceStringEXTPtr(device, name);
}

EGLBoolean FunctionsEGL::queryDisplayAttribEXT(EGLint attribute, EGLAttrib *value) const
{
    return mFnPtrs->queryDisplayAttribEXTPtr(mEGLDisplay, attribute, value);
}

const char *FunctionsEGL::getDisplayDriverName() const
{
    if (mFnPtrs->getDisplayDriverNamePtr != nullptr)
    {
        return mFnPtrs->getDisplayDriverNamePtr(mEGLDisplay);
    }
    return nullptr;
}

}  // namespace rx
