/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtOpenVG module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qwindowsurface_vgegl_p.h"
#include "qpaintengine_vg_p.h"
#include "qpixmapdata_vg_p.h"
#include "qvgimagepool_p.h"
#include "qvg_p.h"

#if !defined(QT_NO_EGL)

QT_BEGIN_NAMESPACE

// Turn off "direct to window" rendering if EGL cannot support it.
#if !defined(EGL_RENDER_BUFFER) || !defined(EGL_SINGLE_BUFFER)
#if defined(QVG_DIRECT_TO_WINDOW)
#undef QVG_DIRECT_TO_WINDOW
#endif
#endif

// Determine if preserved window contents should be used.
#if !defined(EGL_SWAP_BEHAVIOR) || !defined(EGL_BUFFER_PRESERVED)
#if !defined(QVG_NO_PRESERVED_SWAP)
#define QVG_NO_PRESERVED_SWAP 1
#endif
#endif

VGImageFormat qt_vg_config_to_vg_format(QEglContext *context)
{
    return qt_vg_image_to_vg_format
        (qt_vg_config_to_image_format(context));
}

QImage::Format qt_vg_config_to_image_format(QEglContext *context)
{
    EGLint red = 0;
    EGLint green = 0;
    EGLint blue = 0;
    EGLint alpha = 0;
    context->configAttrib(EGL_RED_SIZE, &red);
    context->configAttrib(EGL_GREEN_SIZE, &green);
    context->configAttrib(EGL_BLUE_SIZE, &blue);
    context->configAttrib(EGL_ALPHA_SIZE, &alpha);
    QImage::Format argbFormat;
#ifdef EGL_VG_ALPHA_FORMAT_PRE_BIT
    EGLint type = 0;
    context->configAttrib(EGL_SURFACE_TYPE, &type);
    if ((type & EGL_VG_ALPHA_FORMAT_PRE_BIT) != 0)
        argbFormat = QImage::Format_ARGB32_Premultiplied;
    else
        argbFormat = QImage::Format_ARGB32;
#else
    argbFormat = QImage::Format_ARGB32;
#endif
    if (red == 8 && green == 8 && blue == 8 && alpha == 8)
        return argbFormat;
    else if (red == 8 && green == 8 && blue == 8 && alpha == 0)
        return QImage::Format_RGB32;
    else if (red == 5 && green == 6 && blue == 5 && alpha == 0)
        return QImage::Format_RGB16;
    else if (red == 4 && green == 4 && blue == 4 && alpha == 4)
        return QImage::Format_ARGB4444_Premultiplied;
    else
        return argbFormat;       // XXX
}

#if !defined(QVG_NO_SINGLE_CONTEXT)

class QVGSharedContext
{
public:
    QVGSharedContext();
    ~QVGSharedContext();

    QEglContext *context;
    int refCount;
    int widgetRefCount;
    QVGPaintEngine *engine;
    EGLSurface surface;
    QVGPixmapData *firstPixmap;
};

QVGSharedContext::QVGSharedContext()
    : context(0)
    , refCount(0)
    , widgetRefCount(0)
    , engine(0)
    , surface(EGL_NO_SURFACE)
    , firstPixmap(0)
{
}

QVGSharedContext::~QVGSharedContext()
{
    // Don't accidentally destroy the QEglContext if the reference
    // count falls to zero while deleting the paint engine.
    ++refCount;

    if (context)
        context->makeCurrent(qt_vg_shared_surface());
    delete engine;
    if (context)
        context->doneCurrent();
    if (context && surface != EGL_NO_SURFACE)
        context->destroySurface(surface);
    delete context;
}

Q_GLOBAL_STATIC(QVGSharedContext, sharedContext);

QVGPaintEngine *qt_vg_create_paint_engine(void)
{
    QVGSharedContext *shared = sharedContext();
    if (!shared->engine)
        shared->engine = new QVGPaintEngine();
    return shared->engine;
}

void qt_vg_destroy_paint_engine(QVGPaintEngine *engine)
{
    Q_UNUSED(engine);
}

void qt_vg_register_pixmap(QVGPixmapData *pd)
{
    QVGSharedContext *shared = sharedContext();
    pd->next = shared->firstPixmap;
    pd->prev = 0;
    if (shared->firstPixmap)
        shared->firstPixmap->prev = pd;
    shared->firstPixmap = pd;
}

void qt_vg_unregister_pixmap(QVGPixmapData *pd)
{
    if (pd->next)
        pd->next->prev = pd->prev;
    if (pd->prev) {
        pd->prev->next = pd->next;
    } else {
        QVGSharedContext *shared = sharedContext();
        if (shared) {
           shared->firstPixmap = pd->next;
        }
    }
}

#else

QVGPaintEngine *qt_vg_create_paint_engine(void)
{
    return new QVGPaintEngine();
}

void qt_vg_destroy_paint_engine(QVGPaintEngine *engine)
{
    delete engine;
}

void qt_vg_register_pixmap(QVGPixmapData *pd)
{
    Q_UNUSED(pd);
}

void qt_vg_unregister_pixmap(QVGPixmapData *pd)
{
    Q_UNUSED(pd);
}

#endif

#ifdef EGL_VG_ALPHA_FORMAT_PRE_BIT

static bool isPremultipliedContext(const QEglContext *context)
{
    EGLint value = 0;
    if (context->configAttrib(EGL_SURFACE_TYPE, &value))
        return (value & EGL_VG_ALPHA_FORMAT_PRE_BIT) != 0;
    else
        return false;
}

#endif

static QEglContext *createContext(QPaintDevice *device)
{
    QEglContext *context;

    // Create the context object and open the display.
    context = new QEglContext();
    context->setApi(QEgl::OpenVG);
    if (!context->openDisplay(device)) {
        delete context;
        return 0;
    }

    // Set the swap interval for the display.
    QByteArray interval = qgetenv("QT_VG_SWAP_INTERVAL");
    if (!interval.isEmpty())
        eglSwapInterval(context->display(), interval.toInt());
    else
        eglSwapInterval(context->display(), 1);

#ifdef EGL_RENDERABLE_TYPE
    // Has the user specified an explicit EGL configuration to use?
    QByteArray configId = qgetenv("QT_VG_EGL_CONFIG");
    if (!configId.isEmpty()) {
        EGLint cfgId = configId.toInt();
        EGLint properties[] = {
            EGL_CONFIG_ID, cfgId,
            EGL_NONE
        };
        EGLint matching = 0;
        EGLConfig cfg;
        if (eglChooseConfig
                    (context->display(), properties, &cfg, 1, &matching) &&
                matching > 0) {
            // Check that the selected configuration actually supports OpenVG
            // and then create the context with it.
            EGLint id = 0;
            EGLint type = 0;
            eglGetConfigAttrib
                (context->display(), cfg, EGL_CONFIG_ID, &id);
            eglGetConfigAttrib
                (context->display(), cfg, EGL_RENDERABLE_TYPE, &type);
            if (cfgId == id && (type & EGL_OPENVG_BIT) != 0) {
                context->setConfig(cfg);
                if (!context->createContext()) {
                    delete context;
                    return 0;
                }
                return context;
            } else {
                qWarning("QT_VG_EGL_CONFIG: %d is not a valid OpenVG configuration", int(cfgId));
            }
        }
    }
#endif

    // Choose an appropriate configuration for rendering into the device.
    QEglProperties configProps;
    configProps.setPaintDeviceFormat(device);
    int redSize = configProps.value(EGL_RED_SIZE);
    if (redSize == EGL_DONT_CARE || redSize == 0)
        configProps.setPixelFormat(QImage::Format_ARGB32);  // XXX
#ifndef QVG_SCISSOR_CLIP
    // If we are using the mask to clip, then explicitly request a mask.
    configProps.setValue(EGL_ALPHA_MASK_SIZE, 1);
#endif
#ifdef EGL_VG_ALPHA_FORMAT_PRE_BIT
    configProps.setValue(EGL_SURFACE_TYPE, EGL_WINDOW_BIT |
                         EGL_VG_ALPHA_FORMAT_PRE_BIT);
    configProps.setRenderableType(QEgl::OpenVG);
    if (!context->chooseConfig(configProps)) {
        // Try again without the "pre" bit.
        configProps.setValue(EGL_SURFACE_TYPE, EGL_WINDOW_BIT);
        if (!context->chooseConfig(configProps)) {
            delete context;
            return 0;
        }
    }
#else
    configProps.setValue(EGL_SURFACE_TYPE, EGL_WINDOW_BIT);
    configProps.setRenderableType(QEgl::OpenVG);
    if (!context->chooseConfig(configProps)) {
        delete context;
        return 0;
    }
#endif

    // Construct a new EGL context for the selected configuration.
    if (!context->createContext()) {
        delete context;
        return 0;
    }

    return context;
}

#if !defined(QVG_NO_SINGLE_CONTEXT)

QEglContext *qt_vg_create_context(QPaintDevice *device, int devType)
{
    QVGSharedContext *shared = sharedContext();
    if (devType == QInternal::Widget)
        ++(shared->widgetRefCount);
    if (shared->context) {
        ++(shared->refCount);
        return shared->context;
    } else {
        shared->context = createContext(device);
        shared->refCount = 1;
        return shared->context;
    }
}

static void qt_vg_destroy_shared_context(QVGSharedContext *shared)
{
    shared->context->makeCurrent(qt_vg_shared_surface());
    delete shared->engine;
    shared->engine = 0;
    shared->context->doneCurrent();
    if (shared->surface != EGL_NO_SURFACE) {
        eglDestroySurface(shared->context->display(), shared->surface);
        shared->surface = EGL_NO_SURFACE;
    }
    delete shared->context;
    shared->context = 0;
}

void qt_vg_hibernate_pixmaps(QVGSharedContext *shared)
{
    // Artificially increase the reference count to prevent the
    // context from being destroyed until after we have finished
    // the hibernation process.
    ++(shared->refCount);

    // We need a context current to hibernate the VGImage objects.
    shared->context->makeCurrent(qt_vg_shared_surface());

    // Scan all QVGPixmapData objects in the system and hibernate them.
    QVGPixmapData *pd = shared->firstPixmap;
    while (pd != 0) {
        pd->hibernate();
        pd = pd->next;
    }

    // Hibernate any remaining VGImage's in the image pool.
    QVGImagePool::instance()->hibernate();

    // Don't need the current context any more.
    shared->context->lazyDoneCurrent();

    // Decrease the reference count and destroy the context if necessary.
    if (--(shared->refCount) <= 0)
        qt_vg_destroy_shared_context(shared);
}

void qt_vg_destroy_context(QEglContext *context, int devType)
{
    QVGSharedContext *shared = sharedContext();
    if (shared->context != context) {
        // This is not the shared context.  Shouldn't happen!
        delete context;
        return;
    }
    if (devType == QInternal::Widget)
        --(shared->widgetRefCount);
    if (--(shared->refCount) <= 0) {
        qt_vg_destroy_shared_context(shared);
    } else if (shared->widgetRefCount <= 0 && devType == QInternal::Widget) {
        // All of the widget window surfaces have been destroyed
        // but we still have VG pixmaps active.  Ask them to hibernate
        // to free up GPU resources until a widget is shown again.
        // This may eventually cause the EGLContext to be destroyed
        // because nothing in the system needs a context, which will
        // free up even more GPU resources.
        qt_vg_hibernate_pixmaps(shared);
    }
}

EGLSurface qt_vg_shared_surface(void)
{
    QVGSharedContext *shared = sharedContext();
    if (shared->surface == EGL_NO_SURFACE) {
        EGLint attribs[7];
        attribs[0] = EGL_WIDTH;
        attribs[1] = 16;
        attribs[2] = EGL_HEIGHT;
        attribs[3] = 16;
#ifdef EGL_VG_ALPHA_FORMAT_PRE_BIT
        if (isPremultipliedContext(shared->context)) {
            attribs[4] = EGL_VG_ALPHA_FORMAT;
            attribs[5] = EGL_VG_ALPHA_FORMAT_PRE;
            attribs[6] = EGL_NONE;
        } else
#endif
        {
            attribs[4] = EGL_NONE;
        }
        shared->surface = eglCreatePbufferSurface
            (shared->context->display(), shared->context->config(), attribs);
    }
    return shared->surface;
}

#else

QEglContext *qt_vg_create_context(QPaintDevice *device, int devType)
{
    Q_UNUSED(devType);
    return createContext(device);
}

void qt_vg_destroy_context(QEglContext *context, int devType)
{
    Q_UNUSED(devType);
    delete context;
}

EGLSurface qt_vg_shared_surface(void)
{
    return EGL_NO_SURFACE;
}

#endif

QVGEGLWindowSurfacePrivate::QVGEGLWindowSurfacePrivate(QWindowSurface *win)
{
    winSurface = win;
    engine = 0;
}

QVGEGLWindowSurfacePrivate::~QVGEGLWindowSurfacePrivate()
{
    // Destroy the paint engine if it hasn't been destroyed already.
    destroyPaintEngine();
}

QVGPaintEngine *QVGEGLWindowSurfacePrivate::paintEngine()
{
    if (!engine)
        engine = qt_vg_create_paint_engine();
    return engine;
}

VGImage QVGEGLWindowSurfacePrivate::surfaceImage() const
{
    return VG_INVALID_HANDLE;
}

void QVGEGLWindowSurfacePrivate::destroyPaintEngine()
{
    if (engine) {
        qt_vg_destroy_paint_engine(engine);
        engine = 0;
    }
}

QSize QVGEGLWindowSurfacePrivate::windowSurfaceSize(QWidget *widget) const
{
    Q_UNUSED(widget);

    QRect rect = winSurface->geometry();
    QSize newSize = rect.size();

#if defined(Q_WS_QWS)
    // Account for the widget mask, if any.
    if (widget && !widget->mask().isEmpty()) {
        const QRegion region = widget->mask()
                               & rect.translated(-widget->geometry().topLeft());
        newSize = region.boundingRect().size();
    }
#endif

    return newSize;
}

#if defined(QVG_VGIMAGE_BACKBUFFERS)

QVGEGLWindowSurfaceVGImage::QVGEGLWindowSurfaceVGImage(QWindowSurface *win)
    : QVGEGLWindowSurfacePrivate(win)
    , context(0)
    , backBuffer(VG_INVALID_HANDLE)
    , backBufferSurface(EGL_NO_SURFACE)
    , recreateBackBuffer(false)
    , isPaintingActive(false)
    , windowSurface(EGL_NO_SURFACE)
{
}

QVGEGLWindowSurfaceVGImage::~QVGEGLWindowSurfaceVGImage()
{
    destroyPaintEngine();
    if (context) {
        if (backBufferSurface != EGL_NO_SURFACE) {
            // We need a current context to be able to destroy the image.
            // We use the shared surface because the native window handle
            // associated with "windowSurface" may have been destroyed already.
            context->makeCurrent(qt_vg_shared_surface());
            context->destroySurface(backBufferSurface);
            vgDestroyImage(backBuffer);
            context->doneCurrent();
        }
        if (windowSurface != EGL_NO_SURFACE)
            context->destroySurface(windowSurface);
        qt_vg_destroy_context(context, QInternal::Widget);
    }
}

QEglContext *QVGEGLWindowSurfaceVGImage::ensureContext(QWidget *widget)
{
    QSize newSize = windowSurfaceSize(widget);
    if (context && size != newSize) {
        // The surface size has changed, so we need to recreate
        // the back buffer.  Keep the same context and paint engine.
        size = newSize;
        if (isPaintingActive)
            context->doneCurrent();
        isPaintingActive = false;
        recreateBackBuffer = true;
    }
    if (!context) {
        // Create a new EGL context.  We create the surface in beginPaint().
        size = newSize;
        context = qt_vg_create_context(widget, QInternal::Widget);
        if (!context)
            return 0;
        isPaintingActive = false;
    }
    return context;
}

void QVGEGLWindowSurfaceVGImage::beginPaint(QWidget *widget)
{
    QEglContext *context = ensureContext(widget);
    if (context) {
        if (recreateBackBuffer || backBufferSurface == EGL_NO_SURFACE) {
            // Create a VGImage object to act as the back buffer
            // for this window.  We have to create the VGImage with a
            // current context, so activate the main surface for the window.
            context->makeCurrent(mainSurface());
            recreateBackBuffer = false;
            if (backBufferSurface != EGL_NO_SURFACE) {
                eglDestroySurface(context->display(), backBufferSurface);
                backBufferSurface = EGL_NO_SURFACE;
            }
            if (backBuffer != VG_INVALID_HANDLE) {
                vgDestroyImage(backBuffer);
            }
            VGImageFormat format = qt_vg_config_to_vg_format(context);
            backBuffer = vgCreateImage
                (format, size.width(), size.height(),
                 VG_IMAGE_QUALITY_FASTER);
            if (backBuffer != VG_INVALID_HANDLE) {
                // Create an EGL surface for rendering into the VGImage.
                backBufferSurface = eglCreatePbufferFromClientBuffer
                    (context->display(), EGL_OPENVG_IMAGE,
                     (EGLClientBuffer)(backBuffer),
                     context->config(), NULL);
                if (backBufferSurface == EGL_NO_SURFACE) {
                    vgDestroyImage(backBuffer);
                    backBuffer = VG_INVALID_HANDLE;
                }
            }
        }
        if (backBufferSurface != EGL_NO_SURFACE)
            context->makeCurrent(backBufferSurface);
        else
            context->makeCurrent(mainSurface());
        isPaintingActive = true;
    }
}

void QVGEGLWindowSurfaceVGImage::endPaint
        (QWidget *widget, const QRegion& region, QImage *image)
{
    Q_UNUSED(region);
    Q_UNUSED(image);
    QEglContext *context = ensureContext(widget);
    if (context) {
        if (backBufferSurface != EGL_NO_SURFACE) {
            if (isPaintingActive)
                vgFlush();
            context->lazyDoneCurrent();
        }
        isPaintingActive = false;
    }
}

VGImage QVGEGLWindowSurfaceVGImage::surfaceImage() const
{
    return backBuffer;
}

EGLSurface QVGEGLWindowSurfaceVGImage::mainSurface() const
{
    if (windowSurface != EGL_NO_SURFACE)
        return windowSurface;
    else
        return qt_vg_shared_surface();
}

#endif // QVG_VGIMAGE_BACKBUFFERS

QVGEGLWindowSurfaceDirect::QVGEGLWindowSurfaceDirect(QWindowSurface *win)
    : QVGEGLWindowSurfacePrivate(win)
    , context(0)
    , isPaintingActive(false)
    , needToSwap(false)
    , windowSurface(EGL_NO_SURFACE)
{
}

QVGEGLWindowSurfaceDirect::~QVGEGLWindowSurfaceDirect()
{
    destroyPaintEngine();
    if (context) {
        if (windowSurface != EGL_NO_SURFACE)
            context->destroySurface(windowSurface);
        qt_vg_destroy_context(context, QInternal::Widget);
    }
}

QEglContext *QVGEGLWindowSurfaceDirect::ensureContext(QWidget *widget)
{
    QSize newSize = windowSurfaceSize(widget);
    QEglProperties surfaceProps;

#if defined(QVG_RECREATE_ON_SIZE_CHANGE)
#if !defined(QVG_NO_SINGLE_CONTEXT)
    if (context && size != newSize) {
        // The surface size has changed, so we need to recreate it.
        // We can keep the same context and paint engine.
        size = newSize;
        if (isPaintingActive)
            context->doneCurrent();
        context->destroySurface(windowSurface);
#if defined(EGL_VG_ALPHA_FORMAT_PRE_BIT)
        if (isPremultipliedContext(context)) {
            surfaceProps.setValue
                (EGL_VG_ALPHA_FORMAT, EGL_VG_ALPHA_FORMAT_PRE);
        } else {
            surfaceProps.removeValue(EGL_VG_ALPHA_FORMAT);
        }
#endif
        windowSurface = context->createSurface(widget, &surfaceProps);
        isPaintingActive = false;
    }
#else
    if (context && size != newSize) {
        // The surface size has changed, so we need to recreate
        // the EGL context for the widget.  We also need to recreate
        // the surface's paint engine if context sharing is not
        // enabled because we cannot reuse the existing paint objects
        // in the new context.
        qt_vg_destroy_paint_engine(engine);
        engine = 0;
        context->destroySurface(windowSurface);
        qt_vg_destroy_context(context, QInternal::Widget);
        context = 0;
        windowSurface = EGL_NO_SURFACE;
    }
#endif
#endif
    if (!context) {
        // Create a new EGL context and bind it to the widget surface.
        size = newSize;
        context = qt_vg_create_context(widget, QInternal::Widget);
        if (!context)
            return 0;
        // We want a direct to window rendering surface if possible.
#if defined(QVG_DIRECT_TO_WINDOW)
        surfaceProps.setValue(EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER);
#endif
#if defined(EGL_VG_ALPHA_FORMAT_PRE_BIT)
        if (isPremultipliedContext(context)) {
            surfaceProps.setValue
                (EGL_VG_ALPHA_FORMAT, EGL_VG_ALPHA_FORMAT_PRE);
        } else {
            surfaceProps.removeValue(EGL_VG_ALPHA_FORMAT);
        }
#endif
        EGLSurface surface = context->createSurface(widget, &surfaceProps);
        if (surface == EGL_NO_SURFACE) {
            qt_vg_destroy_context(context, QInternal::Widget);
            context = 0;
            return 0;
        }
        needToSwap = true;
#if defined(QVG_DIRECT_TO_WINDOW)
        // Did we get a direct to window rendering surface?
        EGLint buffer = 0;
        if (eglQueryContext(context->display(), context->context(),
                            EGL_RENDER_BUFFER, &buffer) &&
                buffer == EGL_SINGLE_BUFFER) {
            needToSwap = false;
        }
#endif
#if !defined(QVG_NO_PRESERVED_SWAP)
        // Try to force the surface back buffer to preserve its contents.
        if (needToSwap) {
            eglGetError();  // Clear error state first.
            eglSurfaceAttrib(context->display(), surface,
                             EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED);
            if (eglGetError() != EGL_SUCCESS) {
                qWarning("QVG: could not enable preserved swap");
            }
        }
#endif
        windowSurface = surface;
        isPaintingActive = false;
    }
    return context;
}

void QVGEGLWindowSurfaceDirect::beginPaint(QWidget *widget)
{
    QEglContext *context = ensureContext(widget);
    if (context) {
        context->makeCurrent(windowSurface);
        isPaintingActive = true;
    }
}

void QVGEGLWindowSurfaceDirect::endPaint
        (QWidget *widget, const QRegion& region, QImage *image)
{
    Q_UNUSED(region);
    Q_UNUSED(image);
    QEglContext *context = ensureContext(widget);
    if (context) {
        if (needToSwap) {
            if (!isPaintingActive)
                context->makeCurrent(windowSurface);
            context->swapBuffers(windowSurface);
            context->lazyDoneCurrent();
        } else if (isPaintingActive) {
            vgFlush();
            context->lazyDoneCurrent();
        }
        isPaintingActive = false;
    }
}

QT_END_NAMESPACE

#endif
