/********************************************************************
 KWin - the KDE window manager
 This file is part of the KDE project.

Copyright (C) 2006 Lubos Lunak <l.lunak@kde.org>
Copyright (C) 2012 Martin Gräßlin <mgraesslin@kde.org>

Based on glcompmgr code by Felix Bellaby.
Using code from Compiz and Beryl.

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, see <http://www.gnu.org/licenses/>.
*********************************************************************/

// own
#include "glxbackend.h"
// kwin
#include "options.h"
#include "utils.h"
#include "overlaywindow.h"
#include "composite.h"
#include "screens.h"
#include "xcbutils.h"
// kwin libs
#include <kwinglplatform.h>
#include <kwinxrenderutils.h>
// Qt
#include <QDebug>
#include <QOpenGLContext>
// system
#include <unistd.h>

#include <deque>
#include <algorithm>

#ifndef XCB_GLX_BUFFER_SWAP_COMPLETE
#define XCB_GLX_BUFFER_SWAP_COMPLETE 1
typedef struct xcb_glx_buffer_swap_complete_event_t {
    uint8_t            response_type; /**<  */
    uint8_t            pad0; /**<  */
    uint16_t           sequence; /**<  */
    uint16_t           event_type; /**<  */
    uint8_t            pad1[2]; /**<  */
    xcb_glx_drawable_t drawable; /**<  */
    uint32_t           ust_hi; /**<  */
    uint32_t           ust_lo; /**<  */
    uint32_t           msc_hi; /**<  */
    uint32_t           msc_lo; /**<  */
    uint32_t           sbc; /**<  */
} xcb_glx_buffer_swap_complete_event_t;
#endif

#include <tuple>

#if __cplusplus <= 201103L
namespace std {
    // C++-14
    template<class T, class... Args>
    unique_ptr<T> make_unique(Args&&... args) {
        return unique_ptr<T>(new T(std::forward<Args>(args)...));
    }
}
#endif


namespace KWin
{

SwapEventFilter::SwapEventFilter(xcb_drawable_t drawable, xcb_glx_drawable_t glxDrawable)
    : X11EventFilter(Xcb::Extensions::self()->glxEventBase() + XCB_GLX_BUFFER_SWAP_COMPLETE),
      m_drawable(drawable),
      m_glxDrawable(glxDrawable)
{
}

bool SwapEventFilter::event(xcb_generic_event_t *event)
{
    xcb_glx_buffer_swap_complete_event_t *ev =
            reinterpret_cast<xcb_glx_buffer_swap_complete_event_t *>(event);

    // The drawable field is the X drawable when the event was synthesized
    // by a WireToEvent handler, and the GLX drawable when the event was
    // received over the wire
    if (ev->drawable == m_drawable || ev->drawable == m_glxDrawable) {
        Compositor::self()->bufferSwapComplete();
        return true;
    }

    return false;
}


// -----------------------------------------------------------------------



GlxBackend::GlxBackend()
    : OpenGLBackend()
    , m_overlayWindow(new OverlayWindow())
    , window(None)
    , fbconfig(NULL)
    , glxWindow(None)
    , ctx(nullptr)
    , m_bufferAge(0)
    , haveSwapInterval(false)
{
    init();
}

static bool gs_tripleBufferUndetected = true;
static bool gs_tripleBufferNeedsDetection = false;

GlxBackend::~GlxBackend()
{
    if (isFailed()) {
        m_overlayWindow->destroy();
    }
    // TODO: cleanup in error case
    // do cleanup after initBuffer()
    cleanupGL();
    doneCurrent();

    gs_tripleBufferUndetected = true;
    gs_tripleBufferNeedsDetection = false;

    if (ctx)
        glXDestroyContext(display(), ctx);

    if (glxWindow)
        glXDestroyWindow(display(), glxWindow);

    if (window)
        XDestroyWindow(display(), window);

    overlayWindow()->destroy();
    delete m_overlayWindow;
}

void GlxBackend::init()
{
    initGLX();

    // Require at least GLX 1.3
    if (!hasGLXVersion(1, 3)) {
        setFailed(QStringLiteral("Requires at least GLX 1.3"));
        return;
    }

    initVisualDepthHashTable();

    if (!initBuffer()) {
        setFailed(QStringLiteral("Could not initialize the buffer"));
        return;
    }

    if (!initRenderingContext()) {
        setFailed(QStringLiteral("Could not initialize rendering context"));
        return;
    }

    // Initialize OpenGL
    GLPlatform *glPlatform = GLPlatform::instance();
    glPlatform->detect(GlxPlatformInterface);
    if (GLPlatform::instance()->driver() == Driver_Intel)
        options->setUnredirectFullscreen(false); // bug #252817
    options->setGlPreferBufferSwap(options->glPreferBufferSwap()); // resolve autosetting
    if (options->glPreferBufferSwap() == Options::AutoSwapStrategy)
        options->setGlPreferBufferSwap('e'); // for unknown drivers - should not happen
    glPlatform->printResults();
    initGL(GlxPlatformInterface);

    // Check whether certain features are supported
    m_haveMESACopySubBuffer = hasGLExtension(QByteArrayLiteral("GLX_MESA_copy_sub_buffer"));
    m_haveMESASwapControl   = hasGLExtension(QByteArrayLiteral("GLX_MESA_swap_control"));
    m_haveEXTSwapControl    = hasGLExtension(QByteArrayLiteral("GLX_EXT_swap_control"));
    m_haveSGISwapControl    = hasGLExtension(QByteArrayLiteral("GLX_SGI_swap_control"));
    // only enable Intel swap event if env variable is set, see BUG 342582
    m_haveINTELSwapEvent    = hasGLExtension(QByteArrayLiteral("GLX_INTEL_swap_event"))
                                && qgetenv("KWIN_USE_INTEL_SWAP_EVENT") == QByteArrayLiteral("1");

    if (m_haveINTELSwapEvent) {
        m_swapEventFilter = std::make_unique<SwapEventFilter>(window, glxWindow);
        glXSelectEvent(display(), glxWindow, GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK);
    }

    haveSwapInterval = m_haveMESASwapControl || m_haveEXTSwapControl || m_haveSGISwapControl;

    setSupportsBufferAge(false);

    if (hasGLExtension(QByteArrayLiteral("GLX_EXT_buffer_age"))) {
        const QByteArray useBufferAge = qgetenv("KWIN_USE_BUFFER_AGE");

        if (useBufferAge != "0")
            setSupportsBufferAge(true);
    }

    setSyncsToVBlank(false);
    setBlocksForRetrace(false);
    haveWaitSync = false;
    gs_tripleBufferNeedsDetection = false;
    m_swapProfiler.init();
    const bool wantSync = options->glPreferBufferSwap() != Options::NoSwapEncourage;
    if (wantSync && glXIsDirect(display(), ctx)) {
        if (haveSwapInterval) { // glXSwapInterval is preferred being more reliable
            setSwapInterval(1);
            setSyncsToVBlank(true);
            const QByteArray tripleBuffer = qgetenv("KWIN_TRIPLE_BUFFER");
            if (!tripleBuffer.isEmpty()) {
                setBlocksForRetrace(qstrcmp(tripleBuffer, "0") == 0);
                gs_tripleBufferUndetected = false;
            }
            gs_tripleBufferNeedsDetection = gs_tripleBufferUndetected;
        } else if (hasGLExtension(QByteArrayLiteral("GLX_SGI_video_sync"))) {
            unsigned int sync;
            if (glXGetVideoSyncSGI(&sync) == 0 && glXWaitVideoSyncSGI(1, 0, &sync) == 0) {
                setSyncsToVBlank(true);
                setBlocksForRetrace(true);
                haveWaitSync = true;
            } else
                qCWarning(KWIN_CORE) << "NO VSYNC! glXSwapInterval is not supported, glXWaitVideoSync is supported but broken";
        } else
            qCWarning(KWIN_CORE) << "NO VSYNC! neither glSwapInterval nor glXWaitVideoSync are supported";
    } else {
        // disable v-sync (if possible)
        setSwapInterval(0);
    }
    if (glPlatform->isVirtualBox()) {
        // VirtualBox does not support glxQueryDrawable
        // this should actually be in kwinglutils_funcs, but QueryDrawable seems not to be provided by an extension
        // and the GLPlatform has not been initialized at the moment when initGLX() is called.
        glXQueryDrawable = NULL;
    }

    setIsDirectRendering(bool(glXIsDirect(display(), ctx)));

    qCDebug(KWIN_CORE) << "Direct rendering:" << isDirectRendering();
}

bool GlxBackend::initRenderingContext()
{
    const bool direct = true;

    // Use glXCreateContextAttribsARB() when it's available
    if (hasGLExtension(QByteArrayLiteral("GLX_ARB_create_context"))) {
        const int attribs_31_core_robustness[] = {
            GLX_CONTEXT_MAJOR_VERSION_ARB,               3,
            GLX_CONTEXT_MINOR_VERSION_ARB,               1,
            GLX_CONTEXT_FLAGS_ARB,                       GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB,
            GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, GLX_LOSE_CONTEXT_ON_RESET_ARB,
            0
        };

        const int attribs_31_core[] = {
            GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
            GLX_CONTEXT_MINOR_VERSION_ARB, 1,
            0
        };

        const int attribs_legacy_robustness[] = {
            GLX_CONTEXT_FLAGS_ARB,                       GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB,
            GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, GLX_LOSE_CONTEXT_ON_RESET_ARB,
            0
        };

        const int attribs_legacy[] = {
            GLX_CONTEXT_MAJOR_VERSION_ARB,               1,
            GLX_CONTEXT_MINOR_VERSION_ARB,               2,
            0
        };

        const bool have_robustness = hasGLExtension(QByteArrayLiteral("GLX_ARB_create_context_robustness"));

        // Try to create a 3.1 context first
        if (options->glCoreProfile()) {
            if (have_robustness)
                ctx = glXCreateContextAttribsARB(display(), fbconfig, 0, direct, attribs_31_core_robustness);

            if (!ctx)
                ctx = glXCreateContextAttribsARB(display(), fbconfig, 0, direct, attribs_31_core);
        }

        if (!ctx && have_robustness)
            ctx = glXCreateContextAttribsARB(display(), fbconfig, 0, direct, attribs_legacy_robustness);

        if (!ctx)
            ctx = glXCreateContextAttribsARB(display(), fbconfig, 0, direct, attribs_legacy);
    }

    if (!ctx)
        ctx = glXCreateNewContext(display(), fbconfig, GLX_RGBA_TYPE, NULL, direct);

    if (!ctx) {
        qCDebug(KWIN_CORE) << "Failed to create an OpenGL context.";
        return false;
    }

    if (!glXMakeCurrent(display(), glxWindow, ctx)) {
        qCDebug(KWIN_CORE) << "Failed to make the OpenGL context current.";
        glXDestroyContext(display(), ctx);
        ctx = 0;
        return false;
    }

    return true;
}

bool GlxBackend::initBuffer()
{
    if (!initFbConfig())
        return false;

    if (overlayWindow()->create()) {
        xcb_connection_t * const c = connection();

        // Try to create double-buffered window in the overlay
        xcb_visualid_t visual;
        glXGetFBConfigAttrib(display(), fbconfig, GLX_VISUAL_ID, (int *) &visual);

        if (!visual) {
           qCCritical(KWIN_CORE) << "The GLXFBConfig does not have an associated X visual";
           return false;
        }

        xcb_colormap_t colormap = xcb_generate_id(c);
        xcb_create_colormap(c, false, colormap, rootWindow(), visual);

        const QSize size = screens()->size();

        window = xcb_generate_id(c);
        xcb_create_window(c, visualDepth(visual), window, overlayWindow()->window(),
                          0, 0, size.width(), size.height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
                          visual, XCB_CW_COLORMAP, &colormap);

        glxWindow = glXCreateWindow(display(), fbconfig, window, NULL);
        overlayWindow()->setup(window);
    } else {
        qCCritical(KWIN_CORE) << "Failed to create overlay window";
        return false;
    }

    return true;
}

bool GlxBackend::initFbConfig()
{
    const int attribs[] = {
        GLX_RENDER_TYPE,    GLX_RGBA_BIT,
        GLX_DRAWABLE_TYPE,  GLX_WINDOW_BIT,
        GLX_RED_SIZE,       1,
        GLX_GREEN_SIZE,     1,
        GLX_BLUE_SIZE,      1,
        GLX_ALPHA_SIZE,     0,
        GLX_DEPTH_SIZE,     0,
        GLX_STENCIL_SIZE,   0,
        GLX_CONFIG_CAVEAT,  GLX_NONE,
        GLX_DOUBLEBUFFER,   true,
        0
    };

    // Try to find a double buffered configuration
    int count = 0;
    GLXFBConfig *configs = glXChooseFBConfig(display(), DefaultScreen(display()), attribs, &count);

    struct FBConfig {
        GLXFBConfig config;
        int depth;
        int stencil;
    };

    std::deque<FBConfig> candidates;

    for (int i = 0; i < count; i++) {
        int depth, stencil;
        glXGetFBConfigAttrib(display(), configs[i], GLX_DEPTH_SIZE,   &depth);
        glXGetFBConfigAttrib(display(), configs[i], GLX_STENCIL_SIZE, &stencil);

        candidates.emplace_back(FBConfig{configs[i], depth, stencil});
    }

    if (count > 0)
        XFree(configs);

    std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) {
        if (left.depth < right.depth)
            return true;

        if (left.stencil < right.stencil)
            return true;

        return false;
    });

    if (candidates.size() > 0) {
        fbconfig = candidates.front().config;

        int fbconfig_id, visual_id, red, green, blue, alpha, depth, stencil;
        glXGetFBConfigAttrib(display(), fbconfig, GLX_FBCONFIG_ID,  &fbconfig_id);
        glXGetFBConfigAttrib(display(), fbconfig, GLX_VISUAL_ID,    &visual_id);
        glXGetFBConfigAttrib(display(), fbconfig, GLX_RED_SIZE,     &red);
        glXGetFBConfigAttrib(display(), fbconfig, GLX_GREEN_SIZE,   &green);
        glXGetFBConfigAttrib(display(), fbconfig, GLX_BLUE_SIZE,    &blue);
        glXGetFBConfigAttrib(display(), fbconfig, GLX_ALPHA_SIZE,   &alpha);
        glXGetFBConfigAttrib(display(), fbconfig, GLX_DEPTH_SIZE,   &depth);
        glXGetFBConfigAttrib(display(), fbconfig, GLX_STENCIL_SIZE, &stencil);

        qCDebug(KWIN_CORE, "Choosing GLXFBConfig %#x X visual %#x depth %d RGBA %d:%d:%d:%d ZS %d:%d",
                fbconfig_id, visual_id, visualDepth(visual_id), red, green, blue, alpha, depth, stencil);
    }

    if (fbconfig == nullptr) {
        qCCritical(KWIN_CORE) << "Failed to find a usable framebuffer configuration";
        return false;
    }

    return true;
}

void GlxBackend::initVisualDepthHashTable()
{
    const xcb_setup_t *setup = xcb_get_setup(connection());

    for (auto screen = xcb_setup_roots_iterator(setup); screen.rem; xcb_screen_next(&screen)) {
        for (auto depth = xcb_screen_allowed_depths_iterator(screen.data); depth.rem; xcb_depth_next(&depth)) {
            const int len = xcb_depth_visuals_length(depth.data);
            const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data);

            for (int i = 0; i < len; i++)
                m_visualDepthHash.insert(visuals[i].visual_id, depth.data->depth);
        }
    }
}

int GlxBackend::visualDepth(xcb_visualid_t visual) const
{
    return m_visualDepthHash.value(visual);
}

FBConfigInfo *GlxBackend::infoForVisual(xcb_visualid_t visual)
{
    FBConfigInfo *&info = m_fbconfigHash[visual];

    if (info)
        return info;

    info = new FBConfigInfo;
    info->fbconfig            = nullptr;
    info->bind_texture_format = 0;
    info->texture_targets     = 0;
    info->y_inverted          = 0;
    info->mipmap              = 0;

    const xcb_render_pictformat_t format = XRenderUtils::findPictFormat(visual);
    const xcb_render_directformat_t *direct = XRenderUtils::findPictFormatInfo(format);

    if (!direct) {
        qCCritical(KWIN_CORE).nospace() << "Could not find a picture format for visual 0x" << hex << visual;
        return info;
    }

    const int red_bits   = bitCount(direct->red_mask);
    const int green_bits = bitCount(direct->green_mask);
    const int blue_bits  = bitCount(direct->blue_mask);
    const int alpha_bits = bitCount(direct->alpha_mask);

    const int depth = visualDepth(visual);

    const auto rgb_sizes = std::tie(red_bits, green_bits, blue_bits);

    const int attribs[] = {
        GLX_RENDER_TYPE,    GLX_RGBA_BIT,
        GLX_DRAWABLE_TYPE,  GLX_WINDOW_BIT | GLX_PIXMAP_BIT,
        GLX_X_VISUAL_TYPE,  GLX_TRUE_COLOR,
        GLX_X_RENDERABLE,   True,
        GLX_CONFIG_CAVEAT,  int(GLX_DONT_CARE), // The ARGB32 visual is marked non-conformant in Catalyst
        GLX_BUFFER_SIZE,    red_bits + green_bits + blue_bits + alpha_bits,
        GLX_RED_SIZE,       red_bits,
        GLX_GREEN_SIZE,     green_bits,
        GLX_BLUE_SIZE,      blue_bits,
        GLX_ALPHA_SIZE,     alpha_bits,
        GLX_STENCIL_SIZE,   0,
        GLX_DEPTH_SIZE,     0,
        0
    };

    int count = 0;
    GLXFBConfig *configs = glXChooseFBConfig(display(), DefaultScreen(display()), attribs, &count);

    if (count < 1) {
        qCCritical(KWIN_CORE).nospace() << "Could not find a framebuffer configuration for visual 0x" << hex << visual;
        return info;
    }

    struct FBConfig {
        GLXFBConfig config;
        int depth;
        int stencil;
        int format;
    };

    std::deque<FBConfig> candidates;

    for (int i = 0; i < count; i++) {
        int red, green, blue;
        glXGetFBConfigAttrib(display(), configs[i], GLX_RED_SIZE,   &red);
        glXGetFBConfigAttrib(display(), configs[i], GLX_GREEN_SIZE, &green);
        glXGetFBConfigAttrib(display(), configs[i], GLX_BLUE_SIZE,  &blue);

        if (std::tie(red, green, blue) != rgb_sizes)
            continue;

        xcb_visualid_t visual;
        glXGetFBConfigAttrib(display(), configs[i], GLX_VISUAL_ID, (int *) &visual);

        if (visualDepth(visual) != depth)
            continue;

        int bind_rgb, bind_rgba;
        glXGetFBConfigAttrib(display(), configs[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, &bind_rgba);
        glXGetFBConfigAttrib(display(), configs[i], GLX_BIND_TO_TEXTURE_RGB_EXT,  &bind_rgb);

        if (!bind_rgb && !bind_rgba)
            continue;

        int depth, stencil;
        glXGetFBConfigAttrib(display(), configs[i], GLX_DEPTH_SIZE,   &depth);
        glXGetFBConfigAttrib(display(), configs[i], GLX_STENCIL_SIZE, &stencil);

        int texture_format;
        if (alpha_bits)
            texture_format = bind_rgba ? GLX_TEXTURE_FORMAT_RGBA_EXT : GLX_TEXTURE_FORMAT_RGB_EXT;
        else
            texture_format = bind_rgb ? GLX_TEXTURE_FORMAT_RGB_EXT : GLX_TEXTURE_FORMAT_RGBA_EXT;

        candidates.emplace_back(FBConfig{configs[i], depth, stencil, texture_format});
    }

    if (count > 0)
        XFree(configs);

    std::stable_sort(candidates.begin(), candidates.end(), [](const FBConfig &left, const FBConfig &right) {
        if (left.depth < right.depth)
            return true;

        if (left.stencil < right.stencil)
            return true;

        return false;
    });

    if (candidates.size() > 0) {
        const FBConfig &candidate = candidates.front();

        int y_inverted, texture_targets;
        glXGetFBConfigAttrib(display(), candidate.config, GLX_BIND_TO_TEXTURE_TARGETS_EXT, &texture_targets);
        glXGetFBConfigAttrib(display(), candidate.config, GLX_Y_INVERTED_EXT, &y_inverted);

        info->fbconfig            = candidate.config;
        info->bind_texture_format = candidate.format;
        info->texture_targets     = texture_targets;
        info->y_inverted          = y_inverted;
        info->mipmap              = 0;
    }

    if (info->fbconfig) {
        int fbc_id = 0;
        int visual_id = 0;

        glXGetFBConfigAttrib(display(), info->fbconfig, GLX_FBCONFIG_ID, &fbc_id);
        glXGetFBConfigAttrib(display(), info->fbconfig, GLX_VISUAL_ID,   &visual_id);

        qCDebug(KWIN_CORE).nospace() << "Using FBConfig 0x" << hex << fbc_id << " for visual 0x" << hex << visual_id;
    }

    return info;
}

void GlxBackend::setSwapInterval(int interval)
{
    if (m_haveEXTSwapControl)
        glXSwapIntervalEXT(display(), glxWindow, interval);
    else if (m_haveMESASwapControl)
        glXSwapIntervalMESA(interval);
    else if (m_haveSGISwapControl)
        glXSwapIntervalSGI(interval);
}

void GlxBackend::waitSync()
{
    // NOTE that vsync has no effect with indirect rendering
    if (haveWaitSync) {
        uint sync;
#if 0
        // TODO: why precisely is this important?
        // the sync counter /can/ perform multiple steps during glXGetVideoSync & glXWaitVideoSync
        // but this only leads to waiting for two frames??!?
        glXGetVideoSync(&sync);
        glXWaitVideoSync(2, (sync + 1) % 2, &sync);
#else
        glXWaitVideoSyncSGI(1, 0, &sync);
#endif
    }
}

void GlxBackend::present()
{
    if (lastDamage().isEmpty())
        return;

    const QSize &screenSize = screens()->size();
    const QRegion displayRegion(0, 0, screenSize.width(), screenSize.height());
    const bool fullRepaint = supportsBufferAge() || (lastDamage() == displayRegion);

    if (fullRepaint) {
        if (m_haveINTELSwapEvent)
            Compositor::self()->aboutToSwapBuffers();

        if (haveSwapInterval) {
            if (gs_tripleBufferNeedsDetection) {
                glXWaitGL();
                m_swapProfiler.begin();
            }
            glXSwapBuffers(display(), glxWindow);
            if (gs_tripleBufferNeedsDetection) {
                glXWaitGL();
                if (char result = m_swapProfiler.end()) {
                    gs_tripleBufferUndetected = gs_tripleBufferNeedsDetection = false;
                    if (result == 'd' && GLPlatform::instance()->driver() == Driver_NVidia) {
                        // TODO this is a workaround, we should get __GL_YIELD set before libGL checks it
                        if (qstrcmp(qgetenv("__GL_YIELD"), "USLEEP")) {
                            options->setGlPreferBufferSwap(0);
                            setSwapInterval(0);
                            result = 0; // hint proper behavior
                            qCWarning(KWIN_CORE) << "\nIt seems you are using the nvidia driver without triple buffering\n"
                                              "You must export __GL_YIELD=\"USLEEP\" to prevent large CPU overhead on synced swaps\n"
                                              "Preferably, enable the TripleBuffer Option in the xorg.conf Device\n"
                                              "For this reason, the tearing prevention has been disabled.\n"
                                              "See https://bugs.kde.org/show_bug.cgi?id=322060\n";
                        }
                    }
                    setBlocksForRetrace(result == 'd');
                }
            } else if (blocksForRetrace()) {
                // at least the nvidia blob manages to swap async, ie. return immediately on double
                // buffering - what messes our timing calculation and leads to laggy behavior #346275
                glXWaitGL();
            }
        } else {
            waitSync();
            glXSwapBuffers(display(), glxWindow);
        }
        if (supportsBufferAge()) {
            glXQueryDrawable(display(), glxWindow, GLX_BACK_BUFFER_AGE_EXT, (GLuint *) &m_bufferAge);
        }
    } else if (m_haveMESACopySubBuffer) {
        foreach (const QRect & r, lastDamage().rects()) {
            // convert to OpenGL coordinates
            int y = screenSize.height() - r.y() - r.height();
            glXCopySubBufferMESA(display(), glxWindow, r.x(), y, r.width(), r.height());
        }
    } else { // Copy Pixels (horribly slow on Mesa)
        glDrawBuffer(GL_FRONT);
        SceneOpenGL::copyPixels(lastDamage());
        glDrawBuffer(GL_BACK);
    }

    setLastDamage(QRegion());
    if (!supportsBufferAge()) {
        glXWaitGL();
        XFlush(display());
    }
}

void GlxBackend::screenGeometryChanged(const QSize &size)
{
    doneCurrent();

    XMoveResizeWindow(display(), window, 0, 0, size.width(), size.height());
    overlayWindow()->setup(window);
    Xcb::sync();

    makeCurrent();
    glViewport(0, 0, size.width(), size.height());

    // The back buffer contents are now undefined
    m_bufferAge = 0;
}

SceneOpenGL::TexturePrivate *GlxBackend::createBackendTexture(SceneOpenGL::Texture *texture)
{
    return new GlxTexture(texture, this);
}

QRegion GlxBackend::prepareRenderingFrame()
{
    QRegion repaint;

    if (gs_tripleBufferNeedsDetection) {
        // the composite timer floors the repaint frequency. This can pollute our triple buffering
        // detection because the glXSwapBuffers call for the new frame has to wait until the pending
        // one scanned out.
        // So we compensate for that by waiting an extra milisecond to give the driver the chance to
        // fllush the buffer queue
        usleep(1000);
    }

    present();

    if (supportsBufferAge())
        repaint = accumulatedDamageHistory(m_bufferAge);

    startRenderTimer();
    glXWaitX();

    return repaint;
}

void GlxBackend::endRenderingFrame(const QRegion &renderedRegion, const QRegion &damagedRegion)
{
    if (damagedRegion.isEmpty()) {
        setLastDamage(QRegion());

        // If the damaged region of a window is fully occluded, the only
        // rendering done, if any, will have been to repair a reused back
        // buffer, making it identical to the front buffer.
        //
        // In this case we won't post the back buffer. Instead we'll just
        // set the buffer age to 1, so the repaired regions won't be
        // rendered again in the next frame.
        if (!renderedRegion.isEmpty())
            glFlush();

        m_bufferAge = 1;
        return;
    }

    setLastDamage(renderedRegion);

    if (!blocksForRetrace()) {
        // This also sets lastDamage to empty which prevents the frame from
        // being posted again when prepareRenderingFrame() is called.
        present();
    } else {
        // Make sure that the GPU begins processing the command stream
        // now and not the next time prepareRenderingFrame() is called.
        glFlush();
    }

    if (overlayWindow()->window())  // show the window only after the first pass,
        overlayWindow()->show();   // since that pass may take long

    // Save the damaged region to history
    if (supportsBufferAge())
        addToDamageHistory(damagedRegion);
}

bool GlxBackend::makeCurrent()
{
    if (QOpenGLContext *context = QOpenGLContext::currentContext()) {
        // Workaround to tell Qt that no QOpenGLContext is current
        context->doneCurrent();
    }
    const bool current = glXMakeCurrent(display(), glxWindow, ctx);
    return current;
}

void GlxBackend::doneCurrent()
{
    glXMakeCurrent(display(), None, nullptr);
}

OverlayWindow* GlxBackend::overlayWindow()
{
    return m_overlayWindow;
}

bool GlxBackend::usesOverlayWindow() const
{
    return true;
}

/********************************************************
 * GlxTexture
 *******************************************************/
GlxTexture::GlxTexture(SceneOpenGL::Texture *texture, GlxBackend *backend)
    : SceneOpenGL::TexturePrivate()
    , q(texture)
    , m_backend(backend)
    , m_glxpixmap(None)
{
}

GlxTexture::~GlxTexture()
{
    if (m_glxpixmap != None) {
        if (!options->isGlStrictBinding()) {
            glXReleaseTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT);
        }
        glXDestroyPixmap(display(), m_glxpixmap);
        m_glxpixmap = None;
    }
}

void GlxTexture::onDamage()
{
    if (options->isGlStrictBinding() && m_glxpixmap) {
        glXReleaseTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT);
        glXBindTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT, NULL);
    }
    GLTexturePrivate::onDamage();
}

bool GlxTexture::loadTexture(xcb_pixmap_t pixmap, const QSize &size, xcb_visualid_t visual)
{
    if (pixmap == XCB_NONE || size.isEmpty() || visual == XCB_NONE)
        return false;

    const FBConfigInfo *info = m_backend->infoForVisual(visual);
    if (!info || info->fbconfig == nullptr)
        return false;

    if (info->texture_targets & GLX_TEXTURE_2D_BIT_EXT) {
        m_target = GL_TEXTURE_2D;
        m_scale.setWidth(1.0f / m_size.width());
        m_scale.setHeight(1.0f / m_size.height());
    } else {
        assert(info->texture_targets & GLX_TEXTURE_RECTANGLE_BIT_EXT);

        m_target = GL_TEXTURE_RECTANGLE;
        m_scale.setWidth(1.0f);
        m_scale.setHeight(1.0f);
    }

    const int attrs[] = {
        GLX_TEXTURE_FORMAT_EXT, info->bind_texture_format,
        GLX_MIPMAP_TEXTURE_EXT, false,
        GLX_TEXTURE_TARGET_EXT, m_target == GL_TEXTURE_2D ? GLX_TEXTURE_2D_EXT : GLX_TEXTURE_RECTANGLE_EXT,
        0
    };

    m_glxpixmap     = glXCreatePixmap(display(), info->fbconfig, pixmap, attrs);
    m_size          = size;
    m_yInverted     = info->y_inverted ? true : false;
    m_canUseMipmaps = false;

    glGenTextures(1, &m_texture);

    q->setDirty();
    q->setFilter(GL_NEAREST);

    glBindTexture(m_target, m_texture);
    glXBindTexImageEXT(display(), m_glxpixmap, GLX_FRONT_LEFT_EXT, nullptr);

    updateMatrix();
    return true;
}

bool GlxTexture::loadTexture(WindowPixmap *pixmap)
{
    Toplevel *t = pixmap->toplevel();
    return loadTexture(pixmap->pixmap(), t->size(), t->visual());
}

OpenGLBackend *GlxTexture::backend()
{
    return m_backend;
}

} // namespace