/* gltest - small OpenGL tearing test program
* Copyright (C) 2012-2013 Ralf Jung <post@ralfj.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

#include "glxbackend.h"
#include "glutil.h"

#include <stdio.h>
#include <assert.h>
#include <GL/glxext.h>
#include <string>

#if !defined(CON_GL1) && !defined(CON_GL3)
#error "Valid GL contexts for GLX are: GL1, GL3"
#endif

// attributes for a double buffered framebuffer in RGBA format with at least 4 bits per color
static int configAttribs[] =                                             
{
    GLX_RENDER_TYPE, GLX_RGBA_BIT,
    GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
    GLX_DOUBLEBUFFER, True,
    GLX_RED_SIZE, 4,
    GLX_GREEN_SIZE, 4,
    GLX_BLUE_SIZE, 4,
    None
};

#ifdef CON_GL3
// attributes for a GL3 context
static int contextAttribs[] =
{
    GLX_CONTEXT_MAJOR_VERSION_ARB,               3,
    GLX_CONTEXT_MINOR_VERSION_ARB,               0,
    GLX_CONTEXT_FLAGS_ARB,                       GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
    None
};
#endif

VisualID GLXBackend::initialize(Display *display)
{
    if (this->display == NULL) { // this implies that the context is also unitialized
        this->display = display;
        // debugging: show version information
        int glxMajor, glxMinor;
        glXQueryVersion(display, &glxMajor, &glxMinor);
        printf("Using GLX version: %d.%d\n", glxMajor, glxMinor);
        if (glxMajor == 1 && glxMinor < 4) {
            // glXChooseFBConfig and glXCreateNewContext require GLX 1.3; GLX_ARB_create_context requires GLX 1.4
            die("Need at least GLX 1.4 to function properly\n");
        }
        // check for extension-based functions
        funSwapIntervalMesa = (PFNGLXSWAPINTERVALMESAPROC)resolveGLXFunction("GLX_MESA_swap_control", "glXSwapIntervalMESA");
        funSwapIntervalExt = (PFNGLXSWAPINTERVALEXTPROC)resolveGLXFunction("GLX_EXT_swap_control", "glXSwapIntervalEXT");
        funCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)resolveGLXFunction("GLX_ARB_create_context", "glXCreateContextAttribsARB");
        // get the first usable framebuffer configuration
        int count = 0;
        GLXFBConfig *configs = glXChooseFBConfig(display, DefaultScreen(display), configAttribs, &count);
        if (count < 1) {
            die("Failed to choose framebuffer configuration\n");
        }
        config = configs[0];
        XFree(configs);
    }
    // return visual ID
    XVisualInfo *vi = glXGetVisualFromFBConfig(display, config);
    if (vi== NULL) {
        die("The GLXFBConfig I got is invalid\n");
    }
    VisualID visualid = vi->visualid;
    XFree(vi);
    return visualid;
}

bool GLXBackend::haveGLXExtension(const std::string &name)
{
    assert(display != NULL);
    std::string extensions = glXQueryExtensionsString(display, DefaultScreen(display));
    return (std::string(" "+extensions+" ").find(" "+name+" ") != std::string::npos);
}

T_proc GLXBackend::resolveGLXFunction(const char *extension, const char *function)
{
    if (!haveGLXExtension(extension)) return NULL;
    T_proc f = glXGetProcAddress((const GLubyte*)function);
    if (f)
        printf("%s is supported, using it for %s\n", extension, function);
    else
        printf("%s is supported, but I could not find %s\n", extension, function);
    return f;
}

void GLXBackend::createContext(Window window)
{
    assert(display != NULL && context == None);
    this->window = window;
    // create context with that window
#ifdef CON_GL1
    context = glXCreateNewContext(display, config, GLX_RGBA_TYPE, NULL, GL_TRUE);
#else
    if (!funCreateContextAttribsARB) {
        die("Cannot create GL3 context: GLX_ARB_create_context not supported\n");
    }
    context = funCreateContextAttribsARB(display, config, NULL, GL_TRUE, contextAttribs);
#endif
    if (!context) {
        die("Error creating context\n");
    }
    glXMakeCurrent(display, window, context);                                      
    assert(glXIsDirect(display, context));
    printf("Using GL version: %s\n", glGetString(GL_VERSION));
    // initialise GL utilities
    resolveFunctionPointers((T_glGetProcAddress)glXGetProcAddress);
}

GLXBackend::~GLXBackend()
{
    if (display != NULL) {
        if (context != None) {
            glXMakeCurrent(display, None, NULL);
            glXDestroyContext(display, context);
        }
    }
}

void GLXBackend::swapBuffers() const
{
    assert(context != None); // this implies the display is also initialized
    glXSwapBuffers(display, window);
}

void GLXBackend::setSwapInterval(int i) const
{
    assert(context != None);
    // check if swap interval value is supported
    if (i < 0) {
        die("Cannot set swap interval to %d, must not be negative\n", i);
    }
    // set it
    if (funSwapIntervalExt)
        funSwapIntervalExt(display, window, i);
    else if (funSwapIntervalMesa)
        funSwapIntervalMesa(i);
    else {
        die("At least one of GLX_EXT_swap_control, GLX_MESA_swap_control must be supported by the system\n");
    }
}
