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

Copyright (C) 2010 Fredrik Höglund <fredrik@kde.org>

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/>.
*********************************************************************/

#include "kwinglplatform.h"
#include "kwinglutils.h"

#include <QRegExp>
#include <QStringList>
#include <QDebug>

#include <sys/utsname.h>
#include <X11/Xlib.h>

#include <iostream>
#include <iomanip>
#include <ios>

namespace KWin
{

GLPlatform *GLPlatform::s_platform = 0;

static qint64 parseVersionString(const QByteArray &version)
{
    // Skip any leading non digit
    int start = 0;
    while (start < version.length() && !QChar::fromLatin1(version[start]).isDigit())
        start++;

    // Strip any non digit, non '.' characters from the end
    int end = start;
    while (end < version.length() && (version[end] == '.' || QChar::fromLatin1(version[end]).isDigit()))
        end++;

    const QByteArray result = version.mid(start, end-start);
    const QList<QByteArray> tokens = result.split('.');
    const qint64 major = tokens.at(0).toInt();
    const qint64 minor = tokens.count() > 1 ? tokens.at(1).toInt() : 0;
    const qint64 patch = tokens.count() > 2 ? tokens.at(2).toInt() : 0;

    return kVersionNumber(major, minor, patch);
}

static qint64 getXServerVersion()
{
    qint64 major, minor, patch;

    Display *dpy = display();
    if (dpy && strstr(ServerVendor(dpy), "X.Org")) {
        const int release  = VendorRelease(dpy);
        major = (release / 10000000);
        minor = (release /   100000) % 100;
        patch = (release /     1000) % 100;
    } else {
        major = 0;
        minor = 0;
        patch = 0;
    }

    return kVersionNumber(major, minor, patch);
}

static qint64 getKernelVersion()
{
    struct utsname name;
    uname(&name);

    if (QByteArray(name.sysname) == "Linux")
        return parseVersionString(name.release);

    return 0;
}

// Extracts the portion of a string that matches a regular expression
static QString extract(const QString &string, const QString &match, int offset = 0)
{
    QString result;
    QRegExp rx(match);
    int pos = rx.indexIn(string, offset);
    if (pos != -1)
        result = string.mid(pos, rx.matchedLength());
    return result;
}

static ChipClass detectRadeonClass(const QString &chipset)
{
    if (chipset.isEmpty())
        return UnknownRadeon;

    if (chipset.contains(QStringLiteral("R100"))  ||
        chipset.contains(QStringLiteral("RV100")) ||
        chipset.contains(QStringLiteral("RS100")))
        return R100;

    if (chipset.contains(QStringLiteral("RV200")) ||
        chipset.contains(QStringLiteral("RS200")) ||
        chipset.contains(QStringLiteral("R200"))  ||
        chipset.contains(QStringLiteral("RV250")) ||
        chipset.contains(QStringLiteral("RS300")) ||
        chipset.contains(QStringLiteral("RV280")))
        return R200;

    if (chipset.contains(QStringLiteral("R300"))  ||
        chipset.contains(QStringLiteral("R350"))  ||
        chipset.contains(QStringLiteral("R360"))  ||
        chipset.contains(QStringLiteral("RV350")) ||
        chipset.contains(QStringLiteral("RV370")) ||
        chipset.contains(QStringLiteral("RV380")))
        return R300;

    if (chipset.contains(QStringLiteral("R420"))  ||
        chipset.contains(QStringLiteral("R423"))  ||
        chipset.contains(QStringLiteral("R430"))  ||
        chipset.contains(QStringLiteral("R480"))  ||
        chipset.contains(QStringLiteral("R481"))  ||
        chipset.contains(QStringLiteral("RV410")) ||
        chipset.contains(QStringLiteral("RS400")) ||
        chipset.contains(QStringLiteral("RC410")) ||
        chipset.contains(QStringLiteral("RS480")) ||
        chipset.contains(QStringLiteral("RS482")) ||
        chipset.contains(QStringLiteral("RS600")) ||
        chipset.contains(QStringLiteral("RS690")) ||
        chipset.contains(QStringLiteral("RS740")))
        return R400;

    if (chipset.contains(QStringLiteral("RV515")) ||
        chipset.contains(QStringLiteral("R520"))  ||
        chipset.contains(QStringLiteral("RV530")) ||
        chipset.contains(QStringLiteral("R580"))  ||
        chipset.contains(QStringLiteral("RV560")) ||
        chipset.contains(QStringLiteral("RV570")))
        return R500;

    if (chipset.contains(QStringLiteral("R600"))  ||
        chipset.contains(QStringLiteral("RV610")) ||
        chipset.contains(QStringLiteral("RV630")) ||
        chipset.contains(QStringLiteral("RV670")) ||
        chipset.contains(QStringLiteral("RV620")) ||
        chipset.contains(QStringLiteral("RV635")) ||
        chipset.contains(QStringLiteral("RS780")) ||
        chipset.contains(QStringLiteral("RS880")))
        return R600;

    if (chipset.contains(QStringLiteral("R700"))  ||
        chipset.contains(QStringLiteral("RV770")) ||
        chipset.contains(QStringLiteral("RV730")) ||
        chipset.contains(QStringLiteral("RV710")) ||
        chipset.contains(QStringLiteral("RV740")))
        return R700;

    if (chipset.contains(QStringLiteral("EVERGREEN")) ||  // Not an actual chipset, but returned by R600G in 7.9
        chipset.contains(QStringLiteral("CEDAR"))     ||
        chipset.contains(QStringLiteral("REDWOOD"))   ||
        chipset.contains(QStringLiteral("JUNIPER"))   ||
        chipset.contains(QStringLiteral("CYPRESS"))   ||
        chipset.contains(QStringLiteral("HEMLOCK"))   ||
        chipset.contains(QStringLiteral("PALM")))
        return Evergreen;

    if (chipset.contains(QStringLiteral("SUMO"))   ||
        chipset.contains(QStringLiteral("SUMO2"))  ||
        chipset.contains(QStringLiteral("BARTS"))  ||
        chipset.contains(QStringLiteral("TURKS"))  ||
        chipset.contains(QStringLiteral("CAICOS")) ||
        chipset.contains(QStringLiteral("CAYMAN")))
        return NorthernIslands;

    QString name = extract(chipset, QStringLiteral("HD [0-9]{4}")); // HD followed by a space and 4 digits
    if (!name.isEmpty()) {
        const int id = name.right(4).toInt();
        if (id == 6250 || id == 6310)   // Palm
            return Evergreen;

        if (id >= 6000 && id < 7000)
            return NorthernIslands;    // HD 6xxx

        if (id >= 5000 && id < 6000)
            return Evergreen;          // HD 5xxx

        if (id >= 4000 && id < 5000)
            return R700;               // HD 4xxx

        if (id >= 2000 && id < 4000)    // HD 2xxx/3xxx
            return R600;

        return UnknownRadeon;
    }

    name = extract(chipset, QStringLiteral("X[0-9]{3,4}")); // X followed by 3-4 digits
    if (!name.isEmpty()) {
        const int id = name.mid(1, -1).toInt();

        // X1xxx
        if (id >= 1300)
            return R500;

        // X7xx, X8xx, X12xx, 2100
        if ((id >= 700 && id < 1000) || id >= 1200)
            return R400;

        // X200, X3xx, X5xx, X6xx, X10xx, X11xx
        if ((id >= 300 && id < 700) || (id >= 1000 && id < 1200))
            return R300;

        return UnknownRadeon;
    }

    name = extract(chipset, QStringLiteral("\\b[0-9]{4}\\b")); // A group of 4 digits
    if (!name.isEmpty()) {
        const int id = name.toInt();

        // 7xxx
        if (id >= 7000 && id < 8000)
            return R100;

        // 8xxx, 9xxx
        if (id >= 8000 && id < 9500)
            return R200;

        // 9xxx
        if (id >= 9500)
            return R300;

        if (id == 2100)
            return R400;
    }

    return UnknownRadeon;
}

static ChipClass detectNVidiaClass(const QString &chipset)
{
    QString name = extract(chipset, QStringLiteral("\\bNV[0-9,A-F]{2}\\b")); // NV followed by two hexadecimal digits
    if (!name.isEmpty()) {
        const int id = chipset.mid(2, -1).toInt(0, 16); // Strip the 'NV' from the id

        switch(id & 0xf0) {
        case 0x00:
        case 0x10:
            return NV10;

        case 0x20:
            return NV20;

        case 0x30:
            return NV30;

        case 0x40:
        case 0x60:
            return NV40;

        case 0x50:
        case 0x80:
        case 0x90:
        case 0xA0:
            return G80;

        default:
            return UnknownNVidia;
        }
    }

    if (chipset.contains(QStringLiteral("GeForce2")) || chipset.contains(QStringLiteral("GeForce 256")))
        return NV10;

    if (chipset.contains(QStringLiteral("GeForce3")))
        return NV20;

    if (chipset.contains(QStringLiteral("GeForce4"))) {
        if (chipset.contains(QStringLiteral("MX 420"))  ||
                chipset.contains(QStringLiteral("MX 440"))  || // including MX 440SE
                chipset.contains(QStringLiteral("MX 460"))  ||
                chipset.contains(QStringLiteral("MX 4000")) ||
                chipset.contains(QStringLiteral("PCX 4300")))
            return NV10;

        return NV20;
    }

    // GeForce 5,6,7,8,9
    name = extract(chipset, QStringLiteral("GeForce (FX |PCX |Go )?\\d{4}(M|\\b)")).trimmed();
    if (!name.isEmpty()) {
        if (!name[name.length() - 1].isDigit())
            name.chop(1);

        const int id = name.right(4).toInt();
        if (id < 6000)
            return NV30;

        if (id >= 6000 && id < 8000)
            return NV40;

        if (id >= 8000)
            return G80;

        return UnknownNVidia;
    }

    // GeForce 100/200/300/400/500
    name = extract(chipset, QStringLiteral("GeForce (G |GT |GTX |GTS )?\\d{3}(M|\\b)")).trimmed();
    if (!name.isEmpty()) {
        if (!name[name.length() - 1].isDigit())
            name.chop(1);

        const int id = name.right(3).toInt();
        if (id >= 100 && id < 600) {
            if (id >= 400)
                return GF100;

            return G80;
        }
        return UnknownNVidia;
    }

    return UnknownNVidia;
}

static ChipClass detectIntelClass(const QByteArray &chipset)
{
    // see mesa repository: src/mesa/drivers/dri/intel/intel_context.c
    // GL 1.3, DX8? SM ?
    if (chipset.contains("845G")        ||
            chipset.contains("830M")        ||
            chipset.contains("852GM/855GM") ||
            chipset.contains("865G"))
        return I8XX;

    // GL 1.4, DX 9.0, SM 2.0
    if (chipset.contains("915G")   ||
            chipset.contains("E7221G") ||
            chipset.contains("915GM")  ||
            chipset.contains("945G")   ||   // DX 9.0c
            chipset.contains("945GM")  ||
            chipset.contains("945GME") ||
            chipset.contains("Q33")    ||   // GL1.5
            chipset.contains("Q35")    ||
            chipset.contains("G33")    ||
            chipset.contains("965Q")   ||   // GMA 3000, but apparently considered gen 4 by the driver
            chipset.contains("946GZ")  ||   // GMA 3000, but apparently considered gen 4 by the driver
            chipset.contains("IGD"))
        return I915;

    // GL 2.0, DX 9.0c, SM 3.0
    if (chipset.contains("965G")       ||
            chipset.contains("G45/G43")    || // SM 4.0
            chipset.contains("965GM")      || // GL 2.1
            chipset.contains("965GME/GLE") ||
            chipset.contains("GM45")       ||
            chipset.contains("Q45/Q43")    ||
            chipset.contains("G41")        ||
            chipset.contains("B43")        ||
            chipset.contains("Ironlake"))
        return I965;

    // GL 3.1, CL 1.1, DX 10.1
    if (chipset.contains("Sandybridge")) {
        return SandyBridge;
    }

    // GL4.0, CL1.1, DX11, SM 5.0
    if (chipset.contains("Ivybridge")) {
        return IvyBridge;
    }

    // GL4.0, CL1.2, DX11.1, SM 5.0
    if (chipset.contains("Haswell")) {
        return Haswell;
    }

    return UnknownIntel;
}

QString GLPlatform::versionToString(qint64 version)
{
    int major = (version >> 32);
    int minor = (version >> 16) & 0xffff;
    int patch = version & 0xffff;

    QString string = QString::number(major) + QStringLiteral(".") + QString::number(minor);
    if (patch != 0)
        string += QStringLiteral(".") + QString::number(patch);

    return string;
}

QString GLPlatform::driverToString(Driver driver)
{
    switch(driver) {
    case Driver_R100:
        return QStringLiteral("Radeon");
    case Driver_R200:
        return QStringLiteral("R200");
    case Driver_R300C:
        return QStringLiteral("R300C");
    case Driver_R300G:
        return QStringLiteral("R300G");
    case Driver_R600C:
        return QStringLiteral("R600C");
    case Driver_R600G:
        return QStringLiteral("R600G");
    case Driver_Nouveau:
        return QStringLiteral("Nouveau");
    case Driver_Intel:
        return QStringLiteral("Intel");
    case Driver_NVidia:
        return QStringLiteral("NVIDIA");
    case Driver_Catalyst:
        return QStringLiteral("Catalyst");
    case Driver_Swrast:
        return QStringLiteral("Software rasterizer");
    case Driver_Softpipe:
        return QStringLiteral("softpipe");
    case Driver_Llvmpipe:
        return QStringLiteral("LLVMpipe");
    case Driver_VirtualBox:
        return QStringLiteral("VirtualBox (Chromium)");
    case Driver_VMware:
        return QStringLiteral("VMware (SVGA3D)");

    default:
        return QStringLiteral("Unknown");
    }
}

QString GLPlatform::chipClassToString(ChipClass chipClass)
{
    switch(chipClass) {
    case R100:
        return QStringLiteral("R100");
    case R200:
        return QStringLiteral("R200");
    case R300:
        return QStringLiteral("R300");
    case R400:
        return QStringLiteral("R400");
    case R500:
        return QStringLiteral("R500");
    case R600:
        return QStringLiteral("R600");
    case R700:
        return QStringLiteral("R700");
    case Evergreen:
        return QStringLiteral("EVERGREEN");
    case NorthernIslands:
        return QStringLiteral("NI");

    case NV10:
        return QStringLiteral("NV10");
    case NV20:
        return QStringLiteral("NV20");
    case NV30:
        return QStringLiteral("NV30");
    case NV40:
        return QStringLiteral("NV40/G70");
    case G80:
        return QStringLiteral("G80/G90");
    case GF100:
        return QStringLiteral("GF100");

    case I8XX:
        return QStringLiteral("i830/i835");
    case I915:
        return QStringLiteral("i915/i945");
    case I965:
        return QStringLiteral("i965");
    case SandyBridge:
        return QStringLiteral("SandyBridge");
    case IvyBridge:
        return QStringLiteral("IvyBridge");
    case Haswell:
        return QStringLiteral("Haswell");

    default:
        return QStringLiteral("Unknown");
    }
}



// -------



GLPlatform::GLPlatform()
    : m_driver(Driver_Unknown),
      m_chipClass(UnknownChipClass),
      m_recommendedCompositor(XRenderCompositing),
      m_mesaVersion(0),
      m_galliumVersion(0),
      m_looseBinding(false),
      m_supportsGLSL(false),
      m_limitedGLSL(false),
      m_textureNPOT(false),
      m_limitedNPOT(false),
      m_virtualMachine(false),
      m_platformInterface(NoOpenGLPlatformInterface)
{
}

GLPlatform::~GLPlatform()
{
}

void GLPlatform::detect(OpenGLPlatformInterface platformInterface)
{
    m_platformInterface = platformInterface;

    m_vendor       = (const char*)glGetString(GL_VENDOR);
    m_renderer     = (const char*)glGetString(GL_RENDERER);
    m_version      = (const char*)glGetString(GL_VERSION);

    // Parse the OpenGL version
    const QList<QByteArray> versionTokens = m_version.split(' ');
    if (versionTokens.count() > 0) {
        const QByteArray version = QByteArray(m_version);
        m_glVersion = parseVersionString(version);
    }

#ifndef KWIN_HAVE_OPENGLES
    if (m_glVersion >= kVersionNumber(3, 0)) {
        int count;
        glGetIntegerv(GL_NUM_EXTENSIONS, &count);

        for (int i = 0; i < count; i++) {
            const char *name = (const char *) glGetStringi(GL_EXTENSIONS, i);
            m_extensions.insert(name);
        }
    } else
#endif
    {
        const QByteArray extensions = (const char *) glGetString(GL_EXTENSIONS);
        m_extensions = QSet<QByteArray>::fromList(extensions.split(' '));
    }

    // Parse the Mesa version
    const int mesaIndex = versionTokens.indexOf("Mesa");
    if (mesaIndex != -1) {
        const QByteArray version = versionTokens.at(mesaIndex + 1);
        m_mesaVersion = parseVersionString(version);
    }

    if (platformInterface == EglPlatformInterface) {
#ifdef KWIN_HAVE_OPENGLES
        m_supportsGLSL = true;
        m_textureNPOT = true;
#else
        m_supportsGLSL = m_extensions.contains("GL_ARB_shader_objects") &&
                         m_extensions.contains("GL_ARB_fragment_shader") &&
                         m_extensions.contains("GL_ARB_vertex_shader");

        m_textureNPOT = m_extensions.contains("GL_ARB_texture_non_power_of_two");
#endif
    } else if (platformInterface == GlxPlatformInterface) {
#ifndef KWIN_HAVE_OPENGLES
        m_supportsGLSL = m_extensions.contains("GL_ARB_shader_objects") &&
                         m_extensions.contains("GL_ARB_fragment_shader") &&
                         m_extensions.contains("GL_ARB_vertex_shader");

        m_textureNPOT = m_extensions.contains("GL_ARB_texture_non_power_of_two");
#endif
    }

    m_serverVersion = getXServerVersion();
    m_kernelVersion = getKernelVersion();

    m_glslVersion = 0;
    m_glsl_version = QByteArray();

    if (m_supportsGLSL) {
        // Parse the GLSL version
        m_glsl_version = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
        m_glslVersion = parseVersionString(m_glsl_version);
    }

    m_chipset = "Unknown";
    m_preferBufferSubData = false;


    // Mesa classic drivers
    // ====================================================

    // Radeon
    if (m_renderer.startsWith("Mesa DRI R")) {
        // Sample renderer string: Mesa DRI R600 (RV740 94B3) 20090101 x86/MMX/SSE2 TCL DRI2
        const QList<QByteArray> tokens = m_renderer.split(' ');
        const QByteArray chipClass = tokens.at(2);
        m_chipset = tokens.at(3).mid(1, -1); // Strip the leading '('

        if (chipClass == "R100")
            // Vendor: Tungsten Graphics, Inc.
            m_driver = Driver_R100;

        else if (chipClass == "R200")
            // Vendor: Tungsten Graphics, Inc.
            m_driver = Driver_R200;

        else if (chipClass == "R300")
            // Vendor: DRI R300 Project
            m_driver = Driver_R300C;

        else if (chipClass == "R600")
            // Vendor: Advanced Micro Devices, Inc.
            m_driver = Driver_R600C;

        m_chipClass = detectRadeonClass(QString::fromUtf8(m_chipset));
    }

    // Intel
    else if (m_renderer.contains("Intel")) {
        // Vendor: Tungsten Graphics, Inc.
        // Sample renderer string: Mesa DRI Mobile Intel® GM45 Express Chipset GEM 20100328 2010Q1

        QByteArray chipset;
        if (m_renderer.startsWith("Intel(R) Integrated Graphics Device"))
            chipset = "IGD";
        else
            chipset = m_renderer;

        m_driver = Driver_Intel;
        m_chipClass = detectIntelClass(chipset);
    }

    // Gallium drivers
    // ====================================================
    else if (m_renderer.contains("Gallium")) {
        // Sample renderer string: Gallium 0.4 on AMD RV740
        const QList<QByteArray> tokens = m_renderer.split(' ');
        m_galliumVersion = parseVersionString(tokens.at(1));
        m_chipset = (tokens.at(3) == "AMD" || tokens.at(3) == "ATI") ?
                    tokens.at(4) : tokens.at(3);

        // R300G
        if (m_vendor == "X.Org R300 Project") {
            m_chipClass = detectRadeonClass(QString::fromUtf8(m_chipset));
            m_driver = Driver_R300G;
        }

        // R600G
        else if (m_vendor == "X.Org" &&
                (m_renderer.contains("R6")        ||
                 m_renderer.contains("R7")        ||
                 m_renderer.contains("RV6")       ||
                 m_renderer.contains("RV7")       ||
                 m_renderer.contains("RS780")     ||
                 m_renderer.contains("RS880")     ||
                 m_renderer.contains("CEDAR")     ||
                 m_renderer.contains("REDWOOD")   ||
                 m_renderer.contains("JUNIPER")   ||
                 m_renderer.contains("CYPRESS")   ||
                 m_renderer.contains("HEMLOCK")   ||
                 m_renderer.contains("PALM")      ||
                 m_renderer.contains("EVERGREEN") ||
                 m_renderer.contains("SUMO")      ||
                 m_renderer.contains("SUMO2")     ||
                 m_renderer.contains("BARTS")     ||
                 m_renderer.contains("TURKS")     ||
                 m_renderer.contains("CAICOS")    ||
                 m_renderer.contains("CAYMAN"))) {
            m_chipClass = detectRadeonClass(QString::fromUtf8(m_chipset));
            m_driver = Driver_R600G;
        }

        // Nouveau
        else if (m_vendor == "nouveau") {
            m_chipClass = detectNVidiaClass(QString::fromUtf8(m_chipset));
            m_driver = Driver_Nouveau;
        }

        // softpipe
        else if (m_vendor == "VMware, Inc." && m_chipset == "softpipe" ) {
            m_driver = Driver_Softpipe;
        }

        // llvmpipe
        else if (m_vendor == "VMware, Inc." && m_chipset == "llvmpipe") {
            m_driver = Driver_Llvmpipe;
        }

        // SVGA3D
        else if (m_vendor == "VMware, Inc." && m_chipset.contains("SVGA3D")) {
            m_driver = Driver_VMware;
        }
    }


    // Properietary drivers
    // ====================================================
    else if (m_vendor == "ATI Technologies Inc.") {
        m_chipClass = detectRadeonClass(QString::fromUtf8(m_renderer));
        m_driver = Driver_Catalyst;

        if (versionTokens.count() > 1 && versionTokens.at(2)[0] == '(')
            m_driverVersion = parseVersionString(versionTokens.at(1));
        else if (versionTokens.count() > 0)
            m_driverVersion = parseVersionString(versionTokens.at(0));
        else
            m_driverVersion = 0;
    }

    else if (m_vendor == "NVIDIA Corporation") {
        m_chipClass = detectNVidiaClass(QString::fromUtf8(m_renderer));
        m_driver = Driver_NVidia;

        int index = versionTokens.indexOf("NVIDIA");
        if (versionTokens.count() > index)
            m_driverVersion = parseVersionString(versionTokens.at(index + 1));
        else
            m_driverVersion = 0;
    }

    else if (m_renderer == "Software Rasterizer") {
        m_driver = Driver_Swrast;
    }

    // Virtual Hardware
    // ====================================================
    else if (m_vendor == "Humper" && m_renderer == "Chromium") {
        // Virtual Box
        m_driver = Driver_VirtualBox;

        const int index = versionTokens.indexOf("Chromium");
        if (versionTokens.count() > index)
            m_driverVersion = parseVersionString(versionTokens.at(index + 1));
        else
            m_driverVersion = 0;
    }


    // Driver/GPU specific features
    // ====================================================
    if (isRadeon()) {
        // R200 technically has a programmable pipeline, but since it's SM 1.4,
        // it's too limited to to be of any practical value to us.
        if (m_chipClass < R300)
            m_supportsGLSL = false;

        m_limitedGLSL = false;
        m_limitedNPOT = false;

        if (m_chipClass < R600) {
            if (driver() == Driver_Catalyst)
                m_textureNPOT = m_limitedNPOT = false; // Software fallback
            else if (driver() == Driver_R300G)
                m_limitedNPOT = m_textureNPOT;

            m_limitedGLSL = m_supportsGLSL;
        }

        if (m_chipClass < R300) {
            // fallback to XRender for R100 and R200
            m_recommendedCompositor = XRenderCompositing;
        } else if (m_chipClass < R600) {
            // XRender due to NPOT limitations not supported by KWin's shaders
            m_recommendedCompositor = XRenderCompositing;
        } else {
            m_recommendedCompositor = OpenGL2Compositing;
        }

        if (driver() == Driver_R600G ||
                (driver() == Driver_R600C && m_renderer.contains("DRI2"))) {
            m_looseBinding = true;
        }
    }

    if (isNvidia()) {
        if (m_driver == Driver_NVidia && m_chipClass < NV40)
            m_supportsGLSL = false; // High likelihood of software emulation

        if (m_driver == Driver_NVidia) {
            m_looseBinding = true;
            m_preferBufferSubData = true;
        }

        if (m_chipClass < NV40) {
            m_recommendedCompositor = XRenderCompositing;
        } else {
            m_recommendedCompositor = OpenGL2Compositing;
        }

        m_limitedNPOT = m_textureNPOT && m_chipClass < NV40;
        m_limitedGLSL = m_supportsGLSL && m_chipClass < G80;
    }

    if (isIntel()) {
        if (m_chipClass < I915)
            m_supportsGLSL = false;

        m_limitedGLSL = m_supportsGLSL && m_chipClass < I965;
        // see https://bugs.freedesktop.org/show_bug.cgi?id=80349#c1
        m_looseBinding = false;

        if (m_chipClass < I915) {
            m_recommendedCompositor = XRenderCompositing;
        } else {
            m_recommendedCompositor = OpenGL2Compositing;
        }
    }

    if (isMesaDriver() && platformInterface == EglPlatformInterface) {
        // According to the reference implementation in
        // mesa/demos/src/egl/opengles1/texture_from_pixmap
        // the mesa egl implementation does not require a strict binding (so far).
        m_looseBinding = true;
    }

    if (isSoftwareEmulation()) {
        // we recommend XRender
        m_recommendedCompositor = XRenderCompositing;
        if (m_driver < Driver_Llvmpipe) {
            // Software emulation does not provide GLSL
            m_limitedGLSL = m_supportsGLSL = false;
        } else {
            // llvmpipe does support GLSL
            m_limitedGLSL = false;
            m_supportsGLSL = true;
        }
    }

    if (m_chipClass == UnknownChipClass && m_driver == Driver_Unknown) {
        // we don't know the hardware. Let's be optimistic and assume OpenGL compatible hardware
        m_recommendedCompositor = OpenGL2Compositing;
        m_supportsGLSL = true;
    }

    if (isVirtualBox()) {
        m_virtualMachine = true;
        m_recommendedCompositor = OpenGL2Compositing;
    }

    if (isVMware()) {
        m_virtualMachine = true;
        m_recommendedCompositor = OpenGL2Compositing;
    }
}

static void print(const QString &label, const QString &setting)
{
    std::cout << std::setw(40) << std::left
              << qPrintable(label) << qPrintable(setting) << std::endl;
}

void GLPlatform::printResults() const
{
    print(QStringLiteral("OpenGL vendor string:"),   QString::fromUtf8(m_vendor));
    print(QStringLiteral("OpenGL renderer string:"), QString::fromUtf8(m_renderer));
    print(QStringLiteral("OpenGL version string:"),  QString::fromUtf8(m_version));

    if (m_supportsGLSL)
        print(QStringLiteral("OpenGL shading language version string:"), QString::fromUtf8(m_glsl_version));

    print(QStringLiteral("Driver:"), driverToString(m_driver));
    if (!isMesaDriver())
        print(QStringLiteral("Driver version:"), versionToString(m_driverVersion));

    print(QStringLiteral("GPU class:"), chipClassToString(m_chipClass));

    print(QStringLiteral("OpenGL version:"), versionToString(m_glVersion));

    if (m_supportsGLSL)
        print(QStringLiteral("GLSL version:"), versionToString(m_glslVersion));

    if (isMesaDriver())
        print(QStringLiteral("Mesa version:"), versionToString(mesaVersion()));
    //if (galliumVersion() > 0)
    //    print("Gallium version:", versionToString(m_galliumVersion));
    if (serverVersion() > 0)
        print(QStringLiteral("X server version:"), versionToString(m_serverVersion));
    if (kernelVersion() > 0)
        print(QStringLiteral("Linux kernel version:"), versionToString(m_kernelVersion));

    print(QStringLiteral("Requires strict binding:"), !m_looseBinding ? QStringLiteral("yes") : QStringLiteral("no"));
    print(QStringLiteral("GLSL shaders:"), m_supportsGLSL ? (m_limitedGLSL ? QStringLiteral("limited") : QStringLiteral("yes")) : QStringLiteral("no"));
    print(QStringLiteral("Texture NPOT support:"), m_textureNPOT ? (m_limitedNPOT ? QStringLiteral("limited") : QStringLiteral("yes")) : QStringLiteral("no"));
    print(QStringLiteral("Virtual Machine:"), m_virtualMachine ? QStringLiteral("yes") : QStringLiteral("no"));
}

bool GLPlatform::supports(GLFeature feature) const
{
    switch(feature) {
    case LooseBinding:
        return m_looseBinding;

    case GLSL:
        return m_supportsGLSL;

    case LimitedGLSL:
        return m_limitedGLSL;

    case TextureNPOT:
        return m_textureNPOT;

    case LimitedNPOT:
        return m_limitedNPOT;

    default:
        return false;
    }
}

qint64 GLPlatform::glVersion() const
{
    return m_glVersion;
}

qint64 GLPlatform::glslVersion() const
{
    return m_glslVersion;
}

qint64 GLPlatform::mesaVersion() const
{
    return m_mesaVersion;
}

qint64 GLPlatform::galliumVersion() const
{
    return m_galliumVersion;
}

qint64 GLPlatform::serverVersion() const
{
    return m_serverVersion;
}

qint64 GLPlatform::kernelVersion() const
{
    return m_kernelVersion;
}

qint64 GLPlatform::driverVersion() const
{
    if (isMesaDriver())
        return mesaVersion();

    return m_driverVersion;
}

Driver GLPlatform::driver() const
{
    return m_driver;
}

ChipClass GLPlatform::chipClass() const
{
    return m_chipClass;
}

bool GLPlatform::isMesaDriver() const
{
    return mesaVersion() > 0;
}

bool GLPlatform::isGalliumDriver() const
{
    return galliumVersion() > 0;
}

bool GLPlatform::isRadeon() const
{
    return m_chipClass >= R100 && m_chipClass <= UnknownRadeon;
}

bool GLPlatform::isNvidia() const
{
    return m_chipClass >= NV10 && m_chipClass <= UnknownNVidia;
}

bool GLPlatform::isIntel() const
{
    return m_chipClass >= I8XX && m_chipClass <= UnknownIntel;
}

bool GLPlatform::isVirtualBox() const
{
    return m_driver == Driver_VirtualBox;
}

bool GLPlatform::isVMware() const
{
    return m_driver == Driver_VMware;
}

bool GLPlatform::isSoftwareEmulation() const
{
    return m_driver == Driver_Softpipe || m_driver == Driver_Swrast || m_driver == Driver_Llvmpipe;
}

const QByteArray &GLPlatform::glRendererString() const
{
    return m_renderer;
}

const QByteArray &GLPlatform::glVendorString() const
{
    return m_vendor;
}

const QByteArray &GLPlatform::glVersionString() const
{
    return m_version;
}

const QByteArray &GLPlatform::glShadingLanguageVersionString() const
{
    return m_glsl_version;
}

bool GLPlatform::isLooseBinding() const
{
    return m_looseBinding;
}

bool GLPlatform::isVirtualMachine() const
{
    return m_virtualMachine;
}

CompositingType GLPlatform::recommendedCompositor() const
{
    return m_recommendedCompositor;
}

bool GLPlatform::preferBufferSubData() const
{
    return m_preferBufferSubData;
}

OpenGLPlatformInterface GLPlatform::platformInterface() const
{
    return m_platformInterface;
}

bool GLPlatform::isGLES() const
{
#ifdef KWIN_HAVE_OPENGLES
    return true;
#else
    return false;
#endif
}

void GLPlatform::cleanup()
{
    delete s_platform;
    s_platform = nullptr;
}

} // namespace KWin

