//
// Copyright 2015 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.
//

// FunctionsGLX.cpp: Implements the FunctionsGLX class.

#define ANGLE_SKIP_GLX_DEFINES 1
#include "libANGLE/renderer/gl/glx/FunctionsGLX.h"
#undef ANGLE_SKIP_GLX_DEFINES

// We can only include glx.h in files which do not include ANGLE's GLES
// headers, to avoid doubly-defined GLenum macros, typedefs, etc.
#include <GL/glx.h>

#include <dlfcn.h>
#include <algorithm>

#include "common/string_utils.h"
#include "libANGLE/renderer/gl/glx/functionsglx_typedefs.h"

namespace rx
{

void *FunctionsGLX::sLibHandle = nullptr;

template <typename T>
static bool GetProc(PFNGETPROCPROC getProc, T *member, const char *name)
{
    *member = reinterpret_cast<T>(getProc(name));
    return *member != nullptr;
}

struct FunctionsGLX::GLXFunctionTable
{
    GLXFunctionTable()
        : createContextPtr(nullptr),
          destroyContextPtr(nullptr),
          makeCurrentPtr(nullptr),
          swapBuffersPtr(nullptr),
          queryExtensionPtr(nullptr),
          queryVersionPtr(nullptr),
          getCurrentContextPtr(nullptr),
          getCurrentDrawablePtr(nullptr),
          waitXPtr(nullptr),
          waitGLPtr(nullptr),
          getClientStringPtr(nullptr),
          queryExtensionsStringPtr(nullptr),
          getFBConfigsPtr(nullptr),
          chooseFBConfigPtr(nullptr),
          getFBConfigAttribPtr(nullptr),
          getVisualFromFBConfigPtr(nullptr),
          createWindowPtr(nullptr),
          destroyWindowPtr(nullptr),
          createPbufferPtr(nullptr),
          destroyPbufferPtr(nullptr),
          queryDrawablePtr(nullptr),
          createContextAttribsARBPtr(nullptr),
          swapIntervalEXTPtr(nullptr),
          swapIntervalMESAPtr(nullptr),
          swapIntervalSGIPtr(nullptr)
    {}

    // GLX 1.0
    PFNGLXCREATECONTEXTPROC createContextPtr;
    PFNGLXDESTROYCONTEXTPROC destroyContextPtr;
    PFNGLXMAKECURRENTPROC makeCurrentPtr;
    PFNGLXSWAPBUFFERSPROC swapBuffersPtr;
    PFNGLXQUERYEXTENSIONPROC queryExtensionPtr;
    PFNGLXQUERYVERSIONPROC queryVersionPtr;
    PFNGLXGETCURRENTCONTEXTPROC getCurrentContextPtr;
    PFNGLXGETCURRENTDRAWABLEPROC getCurrentDrawablePtr;
    PFNGLXWAITXPROC waitXPtr;
    PFNGLXWAITGLPROC waitGLPtr;

    // GLX 1.1
    PFNGLXGETCLIENTSTRINGPROC getClientStringPtr;
    PFNGLXQUERYEXTENSIONSSTRINGPROC queryExtensionsStringPtr;

    // GLX 1.3
    PFNGLXGETFBCONFIGSPROC getFBConfigsPtr;
    PFNGLXCHOOSEFBCONFIGPROC chooseFBConfigPtr;
    PFNGLXGETFBCONFIGATTRIBPROC getFBConfigAttribPtr;
    PFNGLXGETVISUALFROMFBCONFIGPROC getVisualFromFBConfigPtr;
    PFNGLXCREATEWINDOWPROC createWindowPtr;
    PFNGLXDESTROYWINDOWPROC destroyWindowPtr;
    PFNGLXCREATEPBUFFERPROC createPbufferPtr;
    PFNGLXDESTROYPBUFFERPROC destroyPbufferPtr;
    PFNGLXQUERYDRAWABLEPROC queryDrawablePtr;

    // GLX_ARB_create_context
    PFNGLXCREATECONTEXTATTRIBSARBPROC createContextAttribsARBPtr;

    // GLX_EXT_swap_control
    PFNGLXSWAPINTERVALEXTPROC swapIntervalEXTPtr;

    // GLX_MESA_swap_control
    PFNGLXSWAPINTERVALMESAPROC swapIntervalMESAPtr;

    // GLX_SGI_swap_control
    PFNGLXSWAPINTERVALSGIPROC swapIntervalSGIPtr;
};

FunctionsGLX::FunctionsGLX()
    : majorVersion(0),
      minorVersion(0),
      mXDisplay(nullptr),
      mXScreen(-1),
      mFnPtrs(new GLXFunctionTable())
{}

FunctionsGLX::~FunctionsGLX()
{
    delete mFnPtrs;
    terminate();
}

bool FunctionsGLX::initialize(Display *xDisplay, int screen, std::string *errorString)
{
    terminate();
    mXDisplay = xDisplay;
    mXScreen  = screen;

#if !defined(ANGLE_LINK_GLX)
    // Some OpenGL implementations can't handle having this library
    // handle closed while there's any X window still open against
    // which a GLXWindow was ever created.
    if (!sLibHandle)
    {
        sLibHandle = dlopen("libGL.so.1", RTLD_NOW);
        if (!sLibHandle)
        {
            *errorString = std::string("Could not dlopen libGL.so.1: ") + dlerror();
            return false;
        }
    }

    getProc = reinterpret_cast<PFNGETPROCPROC>(dlsym(sLibHandle, "glXGetProcAddress"));
    if (!getProc)
    {
        getProc = reinterpret_cast<PFNGETPROCPROC>(dlsym(sLibHandle, "glXGetProcAddressARB"));
    }
    if (!getProc)
    {
        *errorString = "Could not retrieve glXGetProcAddress";
        return false;
    }
#else
    getProc = reinterpret_cast<PFNGETPROCPROC>(glXGetProcAddress);
#endif

#define GET_PROC_OR_ERROR(MEMBER, NAME)                             \
    do                                                              \
    {                                                               \
        if (!GetProc(getProc, MEMBER, #NAME))                       \
        {                                                           \
            *errorString = "Could not load GLX entry point " #NAME; \
            return false;                                           \
        }                                                           \
    } while (0)
#if !defined(ANGLE_LINK_GLX)
#    define GET_FNPTR_OR_ERROR(MEMBER, NAME) GET_PROC_OR_ERROR(MEMBER, NAME)
#else
#    define GET_FNPTR_OR_ERROR(MEMBER, NAME) *MEMBER = NAME
#endif

    // GLX 1.0
    GET_FNPTR_OR_ERROR(&mFnPtrs->createContextPtr, glXCreateContext);
    GET_FNPTR_OR_ERROR(&mFnPtrs->destroyContextPtr, glXDestroyContext);
    GET_FNPTR_OR_ERROR(&mFnPtrs->makeCurrentPtr, glXMakeCurrent);
    GET_FNPTR_OR_ERROR(&mFnPtrs->swapBuffersPtr, glXSwapBuffers);
    GET_FNPTR_OR_ERROR(&mFnPtrs->queryExtensionPtr, glXQueryExtension);
    GET_FNPTR_OR_ERROR(&mFnPtrs->queryVersionPtr, glXQueryVersion);
    GET_FNPTR_OR_ERROR(&mFnPtrs->getCurrentContextPtr, glXGetCurrentContext);
    GET_FNPTR_OR_ERROR(&mFnPtrs->getCurrentDrawablePtr, glXGetCurrentDrawable);
    GET_FNPTR_OR_ERROR(&mFnPtrs->waitXPtr, glXWaitX);
    GET_FNPTR_OR_ERROR(&mFnPtrs->waitGLPtr, glXWaitGL);

    // GLX 1.1
    GET_FNPTR_OR_ERROR(&mFnPtrs->getClientStringPtr, glXGetClientString);
    GET_FNPTR_OR_ERROR(&mFnPtrs->queryExtensionsStringPtr, glXQueryExtensionsString);

    // Check we have a working GLX
    {
        int errorBase;
        int eventBase;
        if (!queryExtension(&errorBase, &eventBase))
        {
            *errorString = "GLX is not present.";
            return false;
        }
    }

    // Check we have a supported version of GLX
    if (!queryVersion(&majorVersion, &minorVersion))
    {
        *errorString = "Could not query the GLX version.";
        return false;
    }
    if (majorVersion != 1 || minorVersion < 3)
    {
        *errorString = "Unsupported GLX version (requires at least 1.3).";
        return false;
    }

    const char *extensions = queryExtensionsString();
    if (!extensions)
    {
        *errorString = "glXQueryExtensionsString returned NULL";
        return false;
    }
    angle::SplitStringAlongWhitespace(extensions, &mExtensions);

    // GLX 1.3
    GET_FNPTR_OR_ERROR(&mFnPtrs->getFBConfigsPtr, glXGetFBConfigs);
    GET_FNPTR_OR_ERROR(&mFnPtrs->chooseFBConfigPtr, glXChooseFBConfig);
    GET_FNPTR_OR_ERROR(&mFnPtrs->getFBConfigAttribPtr, glXGetFBConfigAttrib);
    GET_FNPTR_OR_ERROR(&mFnPtrs->getVisualFromFBConfigPtr, glXGetVisualFromFBConfig);
    GET_FNPTR_OR_ERROR(&mFnPtrs->createWindowPtr, glXCreateWindow);
    GET_FNPTR_OR_ERROR(&mFnPtrs->destroyWindowPtr, glXDestroyWindow);
    GET_FNPTR_OR_ERROR(&mFnPtrs->createPbufferPtr, glXCreatePbuffer);
    GET_FNPTR_OR_ERROR(&mFnPtrs->destroyPbufferPtr, glXDestroyPbuffer);
    GET_FNPTR_OR_ERROR(&mFnPtrs->queryDrawablePtr, glXQueryDrawable);

    // Extensions
    if (hasExtension("GLX_ARB_create_context"))
    {
        GET_PROC_OR_ERROR(&mFnPtrs->createContextAttribsARBPtr, glXCreateContextAttribsARB);
    }
    if (hasExtension("GLX_EXT_swap_control"))
    {
        GET_PROC_OR_ERROR(&mFnPtrs->swapIntervalEXTPtr, glXSwapIntervalEXT);
    }
    if (hasExtension("GLX_MESA_swap_control"))
    {
        GET_PROC_OR_ERROR(&mFnPtrs->swapIntervalMESAPtr, glXSwapIntervalMESA);
    }
    if (hasExtension("GLX_SGI_swap_control"))
    {
        GET_PROC_OR_ERROR(&mFnPtrs->swapIntervalSGIPtr, glXSwapIntervalSGI);
    }

#undef GET_FNPTR_OR_ERROR
#undef GET_PROC_OR_ERROR

    *errorString = "";
    return true;
}

void FunctionsGLX::terminate() {}

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

Display *FunctionsGLX::getDisplay() const
{
    return mXDisplay;
}

int FunctionsGLX::getScreen() const
{
    return mXScreen;
}

// GLX functions

// GLX 1.0
glx::Context FunctionsGLX::createContext(XVisualInfo *visual, glx::Context share, bool direct) const
{
    GLXContext shareCtx = reinterpret_cast<GLXContext>(share);
    GLXContext context  = mFnPtrs->createContextPtr(mXDisplay, visual, shareCtx, direct);
    return reinterpret_cast<glx::Context>(context);
}
void FunctionsGLX::destroyContext(glx::Context context) const
{
    GLXContext ctx = reinterpret_cast<GLXContext>(context);
    mFnPtrs->destroyContextPtr(mXDisplay, ctx);
}
Bool FunctionsGLX::makeCurrent(glx::Drawable drawable, glx::Context context) const
{
    GLXContext ctx = reinterpret_cast<GLXContext>(context);
    return mFnPtrs->makeCurrentPtr(mXDisplay, drawable, ctx);
}
void FunctionsGLX::swapBuffers(glx::Drawable drawable) const
{
    mFnPtrs->swapBuffersPtr(mXDisplay, drawable);
}
Bool FunctionsGLX::queryExtension(int *errorBase, int *event) const
{
    return mFnPtrs->queryExtensionPtr(mXDisplay, errorBase, event);
}
Bool FunctionsGLX::queryVersion(int *major, int *minor) const
{
    return mFnPtrs->queryVersionPtr(mXDisplay, major, minor);
}
glx::Context FunctionsGLX::getCurrentContext() const
{
    GLXContext context = mFnPtrs->getCurrentContextPtr();
    return reinterpret_cast<glx::Context>(context);
}
glx::Drawable FunctionsGLX::getCurrentDrawable() const
{
    GLXDrawable drawable = mFnPtrs->getCurrentDrawablePtr();
    return reinterpret_cast<glx::Drawable>(drawable);
}
void FunctionsGLX::waitX() const
{
    mFnPtrs->waitXPtr();
}
void FunctionsGLX::waitGL() const
{
    mFnPtrs->waitGLPtr();
}

// GLX 1.1
const char *FunctionsGLX::getClientString(int name) const
{
    return mFnPtrs->getClientStringPtr(mXDisplay, name);
}

const char *FunctionsGLX::queryExtensionsString() const
{
    return mFnPtrs->queryExtensionsStringPtr(mXDisplay, mXScreen);
}

// GLX 1.4
glx::FBConfig *FunctionsGLX::getFBConfigs(int *nElements) const
{
    GLXFBConfig *configs = mFnPtrs->getFBConfigsPtr(mXDisplay, mXScreen, nElements);
    return reinterpret_cast<glx::FBConfig *>(configs);
}
glx::FBConfig *FunctionsGLX::chooseFBConfig(const int *attribList, int *nElements) const
{
    GLXFBConfig *configs = mFnPtrs->chooseFBConfigPtr(mXDisplay, mXScreen, attribList, nElements);
    return reinterpret_cast<glx::FBConfig *>(configs);
}
int FunctionsGLX::getFBConfigAttrib(glx::FBConfig config, int attribute, int *value) const
{
    GLXFBConfig cfg = reinterpret_cast<GLXFBConfig>(config);
    return mFnPtrs->getFBConfigAttribPtr(mXDisplay, cfg, attribute, value);
}
XVisualInfo *FunctionsGLX::getVisualFromFBConfig(glx::FBConfig config) const
{
    GLXFBConfig cfg = reinterpret_cast<GLXFBConfig>(config);
    return mFnPtrs->getVisualFromFBConfigPtr(mXDisplay, cfg);
}
GLXWindow FunctionsGLX::createWindow(glx::FBConfig config,
                                     Window window,
                                     const int *attribList) const
{
    GLXFBConfig cfg = reinterpret_cast<GLXFBConfig>(config);
    return mFnPtrs->createWindowPtr(mXDisplay, cfg, window, attribList);
}
void FunctionsGLX::destroyWindow(glx::Window window) const
{
    mFnPtrs->destroyWindowPtr(mXDisplay, window);
}
glx::Pbuffer FunctionsGLX::createPbuffer(glx::FBConfig config, const int *attribList) const
{
    GLXFBConfig cfg = reinterpret_cast<GLXFBConfig>(config);
    return mFnPtrs->createPbufferPtr(mXDisplay, cfg, attribList);
}
void FunctionsGLX::destroyPbuffer(glx::Pbuffer pbuffer) const
{
    mFnPtrs->destroyPbufferPtr(mXDisplay, pbuffer);
}
void FunctionsGLX::queryDrawable(glx::Drawable drawable, int attribute, unsigned int *value) const
{
    mFnPtrs->queryDrawablePtr(mXDisplay, drawable, attribute, value);
}

// GLX_ARB_create_context
glx::Context FunctionsGLX::createContextAttribsARB(glx::FBConfig config,
                                                   glx::Context shareContext,
                                                   Bool direct,
                                                   const int *attribList) const
{
    GLXContext shareCtx = reinterpret_cast<GLXContext>(shareContext);
    GLXFBConfig cfg     = reinterpret_cast<GLXFBConfig>(config);
    GLXContext ctx =
        mFnPtrs->createContextAttribsARBPtr(mXDisplay, cfg, shareCtx, direct, attribList);
    return reinterpret_cast<glx::Context>(ctx);
}

void FunctionsGLX::swapIntervalEXT(glx::Drawable drawable, int intervals) const
{
    mFnPtrs->swapIntervalEXTPtr(mXDisplay, drawable, intervals);
}

int FunctionsGLX::swapIntervalMESA(int intervals) const
{
    return mFnPtrs->swapIntervalMESAPtr(intervals);
}

int FunctionsGLX::swapIntervalSGI(int intervals) const
{
    return mFnPtrs->swapIntervalSGIPtr(intervals);
}

}  // namespace rx
