/*
 * Copyright (C) 2011, 2012 Igalia, S.L.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "config.h"
#include "GLContextGLX.h"

#if USE(GLX)
#include "GraphicsContext3D.h"
#include "OpenGLShims.h"
#include "PlatformDisplayX11.h"
#include "XErrorTrapper.h"
#include <GL/glx.h>
#include <cairo.h>

#if ENABLE(ACCELERATED_2D_CANVAS)
#include <cairo-gl.h>
#endif

namespace WebCore {

#if !defined(PFNGLXSWAPINTERVALSGIPROC)
typedef int (*PFNGLXSWAPINTERVALSGIPROC) (int);
#endif
#if !defined(PFNGLXCREATECONTEXTATTRIBSARBPROC)
typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC) (Display *dpy, GLXFBConfig config, GLXContext share_context, Bool direct, const int *attrib_list);
#endif

static PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI;
static PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB;

static bool hasSGISwapControlExtension(Display* display)
{
    static bool initialized = false;
    if (initialized)
        return !!glXSwapIntervalSGI;

    initialized = true;
    if (!GLContext::isExtensionSupported(glXQueryExtensionsString(display, 0), "GLX_SGI_swap_control"))
        return false;

    glXSwapIntervalSGI = reinterpret_cast<PFNGLXSWAPINTERVALSGIPROC>(glXGetProcAddress(reinterpret_cast<const unsigned char*>("glXSwapIntervalSGI")));
    return !!glXSwapIntervalSGI;
}

static bool hasGLXARBCreateContextExtension(Display* display)
{
    static bool initialized = false;
    if (initialized)
        return !!glXCreateContextAttribsARB;

    initialized = true;
    if (!GLContext::isExtensionSupported(glXQueryExtensionsString(display, 0), "GLX_ARB_create_context"))
        return false;

    glXCreateContextAttribsARB = reinterpret_cast<PFNGLXCREATECONTEXTATTRIBSARBPROC>(glXGetProcAddress(reinterpret_cast<const unsigned char*>("glXCreateContextAttribsARB")));
    return !!glXCreateContextAttribsARB;
}

static GLXContext createGLXARBContext(Display* display, GLXFBConfig config, GLXContext sharingContext)
{
    // We want to create a context with version >= 3.2 core profile, cause that ensures that the i965 driver won't
    // use the software renderer. If that doesn't work, we will use whatever version available. Unfortunately,
    // there's no way to know whether glXCreateContextAttribsARB can provide an OpenGL version >= 3.2 until
    // we actually call it and check the return value. To make things more fun, if a version >= 3.2 cannot be
    // provided, glXCreateContextAttribsARB will throw a GLXBadFBConfig X error, causing the app to crash.
    // So, the first time a context is requested, we set a X error trap to disable crashes with GLXBadFBConfig
    // and then check whether the return value is a context or not.

    static bool canCreate320Context = false;
    static bool canCreate320ContextInitialized = false;

    static const int contextAttributes[] = {
        GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
        GLX_CONTEXT_MINOR_VERSION_ARB, 2,
        0
    };

    if (!canCreate320ContextInitialized) {
        canCreate320ContextInitialized = true;

        {
            // Set an X error trapper that ignores errors to avoid crashing on GLXBadFBConfig. Use a scope
            // here to limit the error trap to just this context creation call.
            XErrorTrapper trapper(display, XErrorTrapper::Policy::Ignore);
            GLXContext context = glXCreateContextAttribsARB(display, config, sharingContext, GL_TRUE, contextAttributes);
            if (context) {
                canCreate320Context = true;
                return context;
            }
        }

        // Creating the 3.2 context failed, so use whatever is available.
        return glXCreateContextAttribsARB(display, config, sharingContext, GL_TRUE, nullptr);
    }

    if (canCreate320Context)
        return glXCreateContextAttribsARB(display, config, sharingContext, GL_TRUE, contextAttributes);

    return glXCreateContextAttribsARB(display, config, sharingContext, GL_TRUE, nullptr);
}

static bool compatibleVisuals(XVisualInfo* a, XVisualInfo* b)
{
    return a->c_class == b->c_class
        && a->depth == b->depth
        && a->red_mask == b->red_mask
        && a->green_mask == b->green_mask
        && a->blue_mask == b->blue_mask
        && a->colormap_size == b->colormap_size
        && a->bits_per_rgb == b->bits_per_rgb;
}

std::unique_ptr<GLContextGLX> GLContextGLX::createWindowContext(GLNativeWindowType window, PlatformDisplay& platformDisplay, GLXContext sharingContext)
{
    // In order to create the GLContext, we need to select a GLXFBConfig that has depth and stencil
    // buffers that is compatible with the Visual used to create the window. To do this, we request
    // all the GLXFBConfigs that have the features we need and compare their XVisualInfo to check whether
    // they are compatible with the window one. Then we try to create the GLContext with each of those
    // configs until we succeed, and finally fallback to the window config if nothing else works.
    Display* display = downcast<PlatformDisplayX11>(platformDisplay).native();
    XWindowAttributes attributes;
    if (!XGetWindowAttributes(display, static_cast<Window>(window), &attributes))
        return nullptr;

    XVisualInfo visualInfo;
    visualInfo.visualid = XVisualIDFromVisual(attributes.visual);

    int numConfigs = 0;
    GLXFBConfig windowConfig = nullptr;
    XUniquePtr<GLXFBConfig> configs(glXGetFBConfigs(display, DefaultScreen(display), &numConfigs));
    for (int i = 0; i < numConfigs; i++) {
        XUniquePtr<XVisualInfo> glxVisualInfo(glXGetVisualFromFBConfig(display, configs.get()[i]));
        if (!glxVisualInfo)
            continue;
        if (glxVisualInfo.get()->visualid == visualInfo.visualid) {
            windowConfig = configs.get()[i];
            break;
        }
    }
    ASSERT(windowConfig);
    XUniquePtr<XVisualInfo> windowVisualInfo(glXGetVisualFromFBConfig(display, windowConfig));

    static const int fbConfigAttributes[] = {
        GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
        GLX_RENDER_TYPE, GLX_RGBA_BIT,
        GLX_X_RENDERABLE, GL_TRUE,
        GLX_RED_SIZE, 1,
        GLX_GREEN_SIZE, 1,
        GLX_BLUE_SIZE, 1,
        GLX_ALPHA_SIZE, 1,
        GLX_DEPTH_SIZE, 1,
        GLX_STENCIL_SIZE, 1,
        GLX_DOUBLEBUFFER, GL_TRUE,
        GLX_CONFIG_CAVEAT, GLX_NONE,
#ifdef GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT
        // Discard sRGB configs if any sRGB extension is installed.
        GLX_FRAMEBUFFER_SRGB_CAPABLE_EXT, GL_FALSE,
#endif
        0
    };
    configs.reset(glXChooseFBConfig(display, DefaultScreen(display), fbConfigAttributes, &numConfigs));
    XUniqueGLXContext context;
    for (int i = 0; i < numConfigs; i++) {
        XUniquePtr<XVisualInfo> configVisualInfo(glXGetVisualFromFBConfig(display, configs.get()[i]));
        if (!configVisualInfo)
            continue;
        if (compatibleVisuals(windowVisualInfo.get(), configVisualInfo.get())) {
            // Try to create a context with this config. Use the trapper in case we get an XError.
            XErrorTrapper trapper(display, XErrorTrapper::Policy::Ignore);
            if (hasGLXARBCreateContextExtension(display))
                context.reset(createGLXARBContext(display, configs.get()[i], sharingContext));
            else {
                // Legacy OpenGL version.
                context.reset(glXCreateContext(display, configVisualInfo.get(), sharingContext, True));
            }

            if (context)
                return std::unique_ptr<GLContextGLX>(new GLContextGLX(platformDisplay, WTFMove(context), window));
        }
    }

    // Fallback to the config used by the window. We don't probably have the buffers we need in
    // this config and that will cause artifacts, but it's better than not rendering anything.
    if (hasGLXARBCreateContextExtension(display))
        context.reset(createGLXARBContext(display, windowConfig, sharingContext));
    else {
        // Legacy OpenGL version.
        context.reset(glXCreateContext(display, windowVisualInfo.get(), sharingContext, True));
    }

    if (!context)
        return nullptr;

    return std::unique_ptr<GLContextGLX>(new GLContextGLX(platformDisplay, WTFMove(context), window));
}

std::unique_ptr<GLContextGLX> GLContextGLX::createPbufferContext(PlatformDisplay& platformDisplay, GLXContext sharingContext)
{
    static const int fbConfigAttributes[] = {
        GLX_DRAWABLE_TYPE, GLX_PBUFFER_BIT,
        GLX_RENDER_TYPE, GLX_RGBA_BIT,
        GLX_RED_SIZE, 1,
        GLX_GREEN_SIZE, 1,
        GLX_BLUE_SIZE, 1,
        GLX_ALPHA_SIZE, 1,
        GLX_DOUBLEBUFFER, GL_FALSE,
        0
    };

    int returnedElements;
    Display* display = downcast<PlatformDisplayX11>(platformDisplay).native();
    XUniquePtr<GLXFBConfig> configs(glXChooseFBConfig(display, 0, fbConfigAttributes, &returnedElements));
    if (!returnedElements)
        return nullptr;

    // We will be rendering to a texture, so our pbuffer does not need to be large.
    static const int pbufferAttributes[] = { GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, 0 };
    XUniqueGLXPbuffer pbuffer(glXCreatePbuffer(display, configs.get()[0], pbufferAttributes));
    if (!pbuffer)
        return nullptr;

    XUniqueGLXContext context;
    if (hasGLXARBCreateContextExtension(display))
        context.reset(createGLXARBContext(display, configs.get()[0], sharingContext));
    else {
        // Legacy OpenGL version.
        context.reset(glXCreateNewContext(display, configs.get()[0], GLX_RGBA_TYPE, sharingContext, GL_TRUE));
    }

    if (!context)
        return nullptr;

    return std::unique_ptr<GLContextGLX>(new GLContextGLX(platformDisplay, WTFMove(context), WTFMove(pbuffer)));
}

std::unique_ptr<GLContextGLX> GLContextGLX::createPixmapContext(PlatformDisplay& platformDisplay, GLXContext sharingContext)
{
    static int visualAttributes[] = {
        GLX_RGBA,
        GLX_RED_SIZE, 1,
        GLX_GREEN_SIZE, 1,
        GLX_BLUE_SIZE, 1,
        GLX_ALPHA_SIZE, 1,
        0
    };

    Display* display = downcast<PlatformDisplayX11>(platformDisplay).native();
    XUniquePtr<XVisualInfo> visualInfo(glXChooseVisual(display, DefaultScreen(display), visualAttributes));
    if (!visualInfo)
        return nullptr;

    XUniqueGLXContext context(glXCreateContext(display, visualInfo.get(), sharingContext, GL_TRUE));
    if (!context)
        return nullptr;

    XUniquePixmap pixmap(XCreatePixmap(display, DefaultRootWindow(display), 1, 1, visualInfo->depth));
    if (!pixmap)
        return nullptr;

    XUniqueGLXPixmap glxPixmap(glXCreateGLXPixmap(display, visualInfo.get(), pixmap.get()));
    if (!glxPixmap)
        return nullptr;

    return std::unique_ptr<GLContextGLX>(new GLContextGLX(platformDisplay, WTFMove(context), WTFMove(pixmap), WTFMove(glxPixmap)));
}

std::unique_ptr<GLContextGLX> GLContextGLX::createContext(GLNativeWindowType window, PlatformDisplay& platformDisplay)
{
    GLXContext glxSharingContext = platformDisplay.sharingGLContext() ? static_cast<GLContextGLX*>(platformDisplay.sharingGLContext())->m_context.get() : nullptr;
    auto context = window ? createWindowContext(window, platformDisplay, glxSharingContext) : nullptr;
    if (!context)
        context = createPbufferContext(platformDisplay, glxSharingContext);
    if (!context)
        context = createPixmapContext(platformDisplay, glxSharingContext);

    return context;
}

std::unique_ptr<GLContextGLX> GLContextGLX::createSharingContext(PlatformDisplay& platformDisplay)
{
    auto context = createPbufferContext(platformDisplay);
    if (!context)
        context = createPixmapContext(platformDisplay);
    return context;
}

GLContextGLX::GLContextGLX(PlatformDisplay& display, XUniqueGLXContext&& context, GLNativeWindowType window)
    : GLContext(display)
    , m_x11Display(downcast<PlatformDisplayX11>(m_display).native())
    , m_context(WTFMove(context))
    , m_window(static_cast<Window>(window))
{
}

GLContextGLX::GLContextGLX(PlatformDisplay& display, XUniqueGLXContext&& context, XUniqueGLXPbuffer&& pbuffer)
    : GLContext(display)
    , m_x11Display(downcast<PlatformDisplayX11>(m_display).native())
    , m_context(WTFMove(context))
    , m_pbuffer(WTFMove(pbuffer))
{
}

GLContextGLX::GLContextGLX(PlatformDisplay& display, XUniqueGLXContext&& context, XUniquePixmap&& pixmap, XUniqueGLXPixmap&& glxPixmap)
    : GLContext(display)
    , m_x11Display(downcast<PlatformDisplayX11>(m_display).native())
    , m_context(WTFMove(context))
    , m_pixmap(WTFMove(pixmap))
    , m_glxPixmap(WTFMove(glxPixmap))
{
}

GLContextGLX::~GLContextGLX()
{
    if (m_cairoDevice)
        cairo_device_destroy(m_cairoDevice);

    if (m_context) {
        // Due to a bug in some nvidia drivers, we need bind the default framebuffer in a context before
        // destroying it to avoid a crash. In order to do that, we need to make the context current and,
        // after the bind change, we need to set the previous context again.
        GLContext* previousActiveContext = GLContext::current();
        makeContextCurrent();
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
        if (previousActiveContext && previousActiveContext != this) {
            // If there was a previous context different from this one, just make it current again.
            previousActiveContext->makeContextCurrent();
        } else {
            // If there was no previous context or this was the previous, set a void context as current.
            // We use the GLX function here, and the destructor of GLContext will clean the pointer
            // returned by GLContext::current().
            glXMakeCurrent(m_x11Display, None, None);
        }
    }
}

bool GLContextGLX::canRenderToDefaultFramebuffer()
{
    return m_window;
}

IntSize GLContextGLX::defaultFrameBufferSize()
{
    if (!canRenderToDefaultFramebuffer() || !m_window)
        return IntSize();

    int x, y;
    Window rootWindow;
    unsigned int width, height, borderWidth, depth;
    if (!XGetGeometry(m_x11Display, m_window, &rootWindow, &x, &y, &width, &height, &borderWidth, &depth))
        return IntSize();

    return IntSize(width, height);
}

bool GLContextGLX::makeContextCurrent()
{
    ASSERT(m_context && (m_window || m_pbuffer || m_glxPixmap));

    GLContext::makeContextCurrent();
    if (glXGetCurrentContext() == m_context.get())
        return true;

    if (m_window)
        return glXMakeCurrent(m_x11Display, m_window, m_context.get());

    if (m_pbuffer)
        return glXMakeCurrent(m_x11Display, m_pbuffer.get(), m_context.get());

    return ::glXMakeCurrent(m_x11Display, m_glxPixmap.get(), m_context.get());
}

void GLContextGLX::swapBuffers()
{
    if (m_window)
        glXSwapBuffers(m_x11Display, m_window);
}

void GLContextGLX::waitNative()
{
    glXWaitX();
}

void GLContextGLX::swapInterval(int interval)
{
    if (!hasSGISwapControlExtension(m_x11Display))
        return;
    glXSwapIntervalSGI(interval);
}

cairo_device_t* GLContextGLX::cairoDevice()
{
    if (m_cairoDevice)
        return m_cairoDevice;

#if ENABLE(ACCELERATED_2D_CANVAS) && CAIRO_HAS_GLX_FUNCTIONS
    m_cairoDevice = cairo_glx_device_create(m_x11Display, m_context.get());
#endif

    return m_cairoDevice;
}

#if ENABLE(GRAPHICS_CONTEXT_3D)
PlatformGraphicsContext3D GLContextGLX::platformContext()
{
    return m_context.get();
}
#endif

} // namespace WebCore

#endif // USE(GLX)
