///////////////////////////////////////////////////////////////////////////////
// Name:        src/univ/glx11.cpp
// Purpose:     code common to all X11-based wxGLCanvas implementations
// Author:      Vadim Zeitlin
// Created:     2007-04-15
// RCS-ID:      $Id: glx11.cpp 54022 2008-06-08 00:12:12Z VZ $
// Copyright:   (c) 2007 Vadim Zeitlin <vadim@wxwindows.org>
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

// ============================================================================
// declarations
// ============================================================================

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

// for compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#if wxUSE_GLCANVAS

#ifndef WX_PRECOMP
    #include "wx/log.h"
#endif //WX_PRECOMP

#include "wx/glcanvas.h"

// ============================================================================
// wxGLContext implementation
// ============================================================================

IMPLEMENT_CLASS(wxGLContext, wxObject)

wxGLContext::wxGLContext(wxGLCanvas *gc, const wxGLContext *other)
{
    if ( wxGLCanvas::GetGLXVersion() >= 13 )
    {
        GLXFBConfig *fbc = gc->GetGLXFBConfig();
        wxCHECK_RET( fbc, _T("invalid GLXFBConfig for OpenGL") );

        m_glContext = glXCreateNewContext( wxGetX11Display(), fbc[0], GLX_RGBA_TYPE,
                                           other ? other->m_glContext : None,
                                           GL_TRUE );
    }
    else // GLX <= 1.2
    {
        XVisualInfo *vi = gc->GetXVisualInfo();
        wxCHECK_RET( vi, _T("invalid visual for OpenGL") );

        m_glContext = glXCreateContext( wxGetX11Display(), vi,
                                        other ? other->m_glContext : None,
                                        GL_TRUE );
    }

    wxASSERT_MSG( m_glContext, _T("Couldn't create OpenGL context") );
}

wxGLContext::~wxGLContext()
{
    if ( !m_glContext )
        return;

    if ( m_glContext == glXGetCurrentContext() )
        MakeCurrent(None, NULL);

    glXDestroyContext( wxGetX11Display(), m_glContext );
}

bool wxGLContext::SetCurrent(const wxGLCanvas& win) const
{
    if ( !m_glContext )
        return false;

    const Window xid = win.GetXWindow();
    wxCHECK2_MSG( xid, return false, _T("window must be shown") );

    return MakeCurrent(xid, m_glContext);
}

// wrapper around glXMakeContextCurrent/glXMakeCurrent depending on GLX
// version
/* static */
bool wxGLContext::MakeCurrent(GLXDrawable drawable, GLXContext context)
{
    if (wxGLCanvas::GetGLXVersion() >= 13)
        return glXMakeContextCurrent( wxGetX11Display(), drawable, drawable, context);
    else // GLX <= 1.2 doesn't have glXMakeContextCurrent()
        return glXMakeCurrent( wxGetX11Display(), drawable, context);
}

// ============================================================================
// wxGLCanvasX11 implementation
// ============================================================================

// ----------------------------------------------------------------------------
// initialization methods and dtor
// ----------------------------------------------------------------------------

wxGLCanvasX11::wxGLCanvasX11()
{
    m_fbc = NULL;
    m_vi = NULL;
}

bool wxGLCanvasX11::InitVisual(const int *attribList)
{
    return InitXVisualInfo(attribList, &m_fbc, &m_vi);
}

wxGLCanvasX11::~wxGLCanvasX11()
{
    if ( m_fbc && m_fbc != ms_glFBCInfo )
        XFree(m_fbc);

    if ( m_vi && m_vi != ms_glVisualInfo )
        XFree(m_vi);
}

// ----------------------------------------------------------------------------
// working with GL attributes
// ----------------------------------------------------------------------------

/* static */
bool wxGLCanvasBase::IsExtensionSupported(const char *extension)
{
    Display * const dpy = wxGetX11Display();

    return IsExtensionInList(glXQueryExtensionsString(dpy, DefaultScreen(dpy)),
                             extension);
}


/* static */
bool wxGLCanvasX11::IsGLXMultiSampleAvailable()
{
    static int s_isMultiSampleAvailable = -1;
    if ( s_isMultiSampleAvailable == -1 )
        s_isMultiSampleAvailable = IsExtensionSupported("GLX_ARB_multisample");

    return s_isMultiSampleAvailable != 0;
}

bool
wxGLCanvasX11::ConvertWXAttrsToGL(const int *wxattrs, int *glattrs, size_t n)
{
    wxCHECK_MSG( n >= 16, false, _T("GL attributes buffer too small") );

    /*
       Different versions of GLX API use rather different attributes lists, see
       the following URLs:

        - <= 1.2: http://www.opengl.org/sdk/docs/man/xhtml/glXChooseVisual.xml
        - >= 1.3: http://www.opengl.org/sdk/docs/man/xhtml/glXChooseFBConfig.xml

       Notice in particular that
        - GLX_RGBA is boolean attribute in the old version of the API but a
          value of GLX_RENDER_TYPE in the new one
        - Boolean attributes such as GLX_DOUBLEBUFFER don't take values in the
          old version but must be followed by True or False in the new one.
     */

    if ( !wxattrs )
    {
        size_t i = 0;

        // use double-buffered true colour by default
        glattrs[i++] = GLX_DOUBLEBUFFER;

        if ( GetGLXVersion() < 13 )
        {
            // default settings if attriblist = 0
            glattrs[i++] = GLX_RGBA;
            glattrs[i++] = GLX_DEPTH_SIZE;   glattrs[i++] = 1;
            glattrs[i++] = GLX_RED_SIZE;     glattrs[i++] = 1;
            glattrs[i++] = GLX_GREEN_SIZE;   glattrs[i++] = 1;
            glattrs[i++] = GLX_BLUE_SIZE;    glattrs[i++] = 1;
            glattrs[i++] = GLX_ALPHA_SIZE;   glattrs[i++] = 0;
        }
        else // recent GLX can choose the defaults on its own just fine
        {
            // we just need to have a value after GLX_DOUBLEBUFFER
            glattrs[i++] = True;
        }

        glattrs[i] = None;

        wxASSERT_MSG( i < n, _T("GL attributes buffer too small") );
    }
    else // have non-default attributes
    {
        size_t p = 0;
        for ( int arg = 0; wxattrs[arg] != 0; )
        {
            // check if we have any space left, knowing that we may insert 2
            // more elements during this loop iteration and we always need to
            // terminate the list with None (hence -3)
            if ( p > n - 3 )
                return false;

            // indicates whether we have a boolean attribute
            bool isBoolAttr = false;

            switch ( wxattrs[arg++] )
            {
                case WX_GL_BUFFER_SIZE:
                    glattrs[p++] = GLX_BUFFER_SIZE;
                    break;

                case WX_GL_LEVEL:
                    glattrs[p++] = GLX_LEVEL;
                    break;

                case WX_GL_RGBA:
                    if ( GetGLXVersion() >= 13 )
                    {
                        // this is the default GLX_RENDER_TYPE anyhow
                        continue;
                    }

                    glattrs[p++] = GLX_RGBA;
                    isBoolAttr = true;
                    break;

                case WX_GL_DOUBLEBUFFER:
                    glattrs[p++] = GLX_DOUBLEBUFFER;
                    isBoolAttr = true;
                    break;

                case WX_GL_STEREO:
                    glattrs[p++] = GLX_STEREO;
                    isBoolAttr = true;
                    break;

                case WX_GL_AUX_BUFFERS:
                    glattrs[p++] = GLX_AUX_BUFFERS;
                    break;

                case WX_GL_MIN_RED:
                    glattrs[p++] = GLX_RED_SIZE;
                    break;

                case WX_GL_MIN_GREEN:
                    glattrs[p++] = GLX_GREEN_SIZE;
                    break;

                case WX_GL_MIN_BLUE:
                    glattrs[p++] = GLX_BLUE_SIZE;
                    break;

                case WX_GL_MIN_ALPHA:
                    glattrs[p++] = GLX_ALPHA_SIZE;
                    break;

                case WX_GL_DEPTH_SIZE:
                    glattrs[p++] = GLX_DEPTH_SIZE;
                    break;

                case WX_GL_STENCIL_SIZE:
                    glattrs[p++] = GLX_STENCIL_SIZE;
                    break;

                case WX_GL_MIN_ACCUM_RED:
                    glattrs[p++] = GLX_ACCUM_RED_SIZE;
                    break;

                case WX_GL_MIN_ACCUM_GREEN:
                    glattrs[p++] = GLX_ACCUM_GREEN_SIZE;
                    break;

                case WX_GL_MIN_ACCUM_BLUE:
                    glattrs[p++] = GLX_ACCUM_BLUE_SIZE;
                    break;

                case WX_GL_MIN_ACCUM_ALPHA:
                    glattrs[p++] = GLX_ACCUM_ALPHA_SIZE;
                    break;

                case WX_GL_SAMPLE_BUFFERS:
                    if ( !IsGLXMultiSampleAvailable() )
                    {
                        // if it was specified just to disable it, no problem
                        if ( !wxattrs[arg++] )
                            continue;

                        // otherwise indicate that it's not supported
                        return false;
                    }

                    glattrs[p++] = GLX_SAMPLE_BUFFERS_ARB;
                    break;

                case WX_GL_SAMPLES:
                    if ( !IsGLXMultiSampleAvailable() )
                    {
                        if ( !wxattrs[arg++] )
                            continue;

                        return false;
                    }

                    glattrs[p++] = GLX_SAMPLES_ARB;
                    break;

                default:
                    wxLogDebug(_T("Unsupported OpenGL attribute %d"),
                               wxattrs[arg - 1]);
                    continue;
            }

            if ( isBoolAttr )
            {
                // as explained above, for pre 1.3 API the attribute just needs
                // to be present so we only add its value when using the new API
                if ( GetGLXVersion() >= 13 )
                    glattrs[p++] = True;
            }
            else // attribute with real (non-boolean) value
            {
                // copy attribute value as is
                glattrs[p++] = wxattrs[arg++];
            }
        }

        glattrs[p] = None;
    }

    return true;
}

/* static */
bool
wxGLCanvasX11::InitXVisualInfo(const int *attribList,
                               GLXFBConfig **pFBC,
                               XVisualInfo **pXVisual)
{
    int data[512];
    if ( !ConvertWXAttrsToGL(attribList, data, WXSIZEOF(data)) )
        return false;

    Display * const dpy = wxGetX11Display();

    if ( GetGLXVersion() >= 13 )
    {
        int returned;
        *pFBC = glXChooseFBConfig(dpy, DefaultScreen(dpy), data, &returned);

        if ( *pFBC )
        {
            *pXVisual = glXGetVisualFromFBConfig(wxGetX11Display(), **pFBC);
            if ( !*pXVisual )
            {
                XFree(*pFBC);
                *pFBC = NULL;
            }
        }
    }
    else // GLX <= 1.2
    {
        *pFBC = NULL;
        *pXVisual = glXChooseVisual(dpy, DefaultScreen(dpy), data);
    }

    return *pXVisual != NULL;
}

/* static */
bool
wxGLCanvasBase::IsDisplaySupported(const int *attribList)
{
    GLXFBConfig *fbc = NULL;
    XVisualInfo *vi = NULL;

    const bool
        isSupported = wxGLCanvasX11::InitXVisualInfo(attribList, &fbc, &vi);

    if ( fbc )
        XFree(fbc);
    if ( vi )
        XFree(vi);

    return isSupported;
}

// ----------------------------------------------------------------------------
// default visual management
// ----------------------------------------------------------------------------

XVisualInfo *wxGLCanvasX11::ms_glVisualInfo = NULL;
GLXFBConfig *wxGLCanvasX11::ms_glFBCInfo = NULL;

/* static */
bool wxGLCanvasX11::InitDefaultVisualInfo(const int *attribList)
{
    FreeDefaultVisualInfo();

    return InitXVisualInfo(attribList, &ms_glFBCInfo, &ms_glVisualInfo);
}

/* static */
void wxGLCanvasX11::FreeDefaultVisualInfo()
{
    if ( ms_glFBCInfo )
    {
        XFree(ms_glFBCInfo);
        ms_glFBCInfo = NULL;
    }

    if ( ms_glVisualInfo )
    {
        XFree(ms_glVisualInfo);
        ms_glVisualInfo = NULL;
    }
}

// ----------------------------------------------------------------------------
// other GL methods
// ----------------------------------------------------------------------------

/* static */
int wxGLCanvasX11::GetGLXVersion()
{
    static int s_glxVersion = 0;
    if ( s_glxVersion == 0 )
    {
        // check the GLX version
        int glxMajorVer, glxMinorVer;
        bool ok = glXQueryVersion(wxGetX11Display(), &glxMajorVer, &glxMinorVer);
        wxASSERT_MSG( ok, _T("GLX version not found") );
        if (!ok)
            s_glxVersion = 10; // 1.0 by default
        else
            s_glxVersion = glxMajorVer*10 + glxMinorVer;
    }

    return s_glxVersion;
}

bool wxGLCanvasX11::SwapBuffers()
{
    const Window xid = GetXWindow();
    wxCHECK2_MSG( xid, return false, _T("window must be shown") );

    glXSwapBuffers(wxGetX11Display(), xid);
    return true;
}

bool wxGLCanvasX11::IsShownOnScreen() const
{
    return GetXWindow() && wxGLCanvasBase::IsShownOnScreen();
}

#endif // wxUSE_GLCANVAS

