/*
* Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). 
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:   
*
*/



#include "HuiFxVg10OffscreenRenderbuffer.h"
#include "HuiVg10RenderPlugin.h"
#include "uiacceltk/HuiEnv.h"
#include "uiacceltk/HuiDisplay.h"

CHuiFxVg10OffscreenRenderbuffer* CHuiFxVg10OffscreenRenderbuffer::NewL(CHuiVg10RenderPlugin& aPlugin, const TSize& aSize)
    {
    CHuiFxVg10OffscreenRenderbuffer* e = new (ELeave) CHuiFxVg10OffscreenRenderbuffer();
    CleanupStack::PushL(e);
    e->ConstructL(aPlugin, aSize);
    CleanupStack::Pop(e);
    return e;
    }

void CHuiFxVg10OffscreenRenderbuffer::ConstructL(CHuiVg10RenderPlugin& aPlugin, const TSize& aSize)
    {
    CHuiFxRenderbuffer::ConstructL(aSize, EBufferTypeOffscreen);
    iPlugin = &aPlugin;

    PushEGLContext();
    
#ifndef __WINS__ // Should possibly query the supported mode instead?
    VGImageFormat imageInternalFormat = VG_sARGB_8888_PRE;
#else
    VGImageFormat imageInternalFormat = VG_sARGB_8888;
#endif

    iImage = vgCreateImage(imageInternalFormat, aSize.iWidth, aSize.iHeight, 
                           VG_IMAGE_QUALITY_NONANTIALIASED);

    HUIFX_VG_INVARIANT();

    const TInt BITS_PER_CHANNEL = 8;
    
    // Choose an EGL config
    const EGLint attrs[] =
        {
        EGL_SURFACE_TYPE,       EGL_PBUFFER_BIT,
        EGL_RENDERABLE_TYPE,    EGL_OPENVG_BIT,
        EGL_RED_SIZE,           BITS_PER_CHANNEL,
        EGL_GREEN_SIZE,         BITS_PER_CHANNEL,
        EGL_BLUE_SIZE,          BITS_PER_CHANNEL,
        EGL_ALPHA_SIZE,         BITS_PER_CHANNEL,
        EGL_NONE
        };
    TInt configCount = iPlugin->EglChooseConfig(attrs);
    ASSERT(configCount > 0);
    EGLConfig config = iPlugin->EglConfig(0);
    
    // Create a context
    iContext = eglCreateContext(iPlugin->EglDisplay(), config,
            iPlugin->EglSharedContext(), NULL);

    if(iContext == EGL_NO_CONTEXT)
        {
        EGLint err = eglGetError();
        RDebug::Print(_L("CHuiFxVg10OffscreenRenderbuffer::InitializeL() - EGLContext creation failed: EglError -- %04x"), err);
        User::Leave(KErrGeneral);
        }
    
    // Create a pbuffer surface
    CHuiVg10RenderPlugin& renderer = CHuiStatic::Vg10Renderer();
    iSurface = renderer.CreatePBufferSurface(iPlugin->EglDisplay(), EGL_OPENVG_IMAGE,                                 
                                    iImage, config);

    if(iSurface == EGL_NO_SURFACE)
         {
         EGLint err = eglGetError();
         RDebug::Print(_L("CHuiFxVg10OffscreenRenderbuffer::InitializeL() - EGLSurface creation failed: EglError -- %04x"), err);
         User::Leave(KErrGeneral);
         }
   
    // Initialize the context
    iGc = iPlugin->CreateGcL();
    this->InitGc(aSize);

    PopEGLContext();
    }

CHuiFxVg10OffscreenRenderbuffer::~CHuiFxVg10OffscreenRenderbuffer()
    {  
    delete iGc;
    
    if(iPlugin)
        {
        eglDestroySurface(iPlugin->EglDisplay(), iSurface);
        eglDestroyContext(iPlugin->EglDisplay(), iContext); 
        }
    
    if (iRotatedImage != VG_INVALID_HANDLE)
        {
        vgDestroyImage(iRotatedImage);
        HUIFX_VG_INVARIANT();
        }
    vgDestroyImage(iImage);
    HUIFX_VG_INVARIANT();
    }

void CHuiFxVg10OffscreenRenderbuffer::InitGc(const TSize& aSize)
    {
    BindAsRenderTarget();
    iGc->InitState();
    VGfloat color[] = 
                {
                 .0f, .0f, .0f, .0f
                };
    vgSetfv(VG_CLEAR_COLOR, sizeof(color) / sizeof(VGfloat), color);
    vgClear(0, 0, aSize.iWidth, aSize.iHeight);
    UnbindAsRenderTarget();
    
    // Let renderer know that we have been fiddlling with OpenVg state directly
    // "iGc->InitState" confuses scissoring setting, so lets notify it.
    CHuiVg10RenderPlugin& renderer = CHuiStatic::Vg10Renderer();
    renderer.AddRestoreStateFlags(EHuiVg10GcStateFlagDirtyScissor);    
    renderer.AddRestoreStateFlags(EHuiVg10GcStateFlagDirtyBlendMode);    
    }

void CHuiFxVg10OffscreenRenderbuffer::PrepareForReuse(const TSize& aReusedSize)
    {
    if (iBackgroundEnabled)
        {
        ReadBackground();
        }
    else
        {
        const TInt COLOR_COMPONENTS = 4;
        VGfloat savedColor[COLOR_COMPONENTS];
        vgGetfv(VG_CLEAR_COLOR, COLOR_COMPONENTS, savedColor);
        
        VGfloat color[COLOR_COMPONENTS] = 
                    {
                     .0f, .0f, .0f, .0f
                    };
        vgSetfv(VG_CLEAR_COLOR, COLOR_COMPONENTS, color);
        vgClearImage(Image(), 0, 0, aReusedSize.iWidth, aReusedSize.iHeight);
        vgSetfv(VG_CLEAR_COLOR, COLOR_COMPONENTS, savedColor);        
        }
    
    HUIFX_VG_INVARIANT();
    }

CHuiGc& CHuiFxVg10OffscreenRenderbuffer::BindAsRenderTarget()
    {
    // Save current context and surfaces
    iSavedContext = eglGetCurrentContext();
    iSavedDrawSurface = eglGetCurrentSurface(EGL_DRAW);
    iSavedReadSurface = eglGetCurrentSurface(EGL_READ);
    ASSERT(iSavedContext != iContext);
    ASSERT(iSavedDrawSurface != iSurface);
    ASSERT(iSavedReadSurface != iSurface);

    // Bind our own surface
    eglMakeCurrent(iPlugin->EglDisplay(), iSurface, iSurface, iContext);
    ASSERT(eglGetError() == EGL_SUCCESS);

    return *iGc;
    }

void CHuiFxVg10OffscreenRenderbuffer::BindAsTexture(THuiFxRenderbufferUsage aUsage)
    {
    // Nothing to do
    }

void CHuiFxVg10OffscreenRenderbuffer::UnbindAsTexture()
    {
    // Nothing to do
    }

void CHuiFxVg10OffscreenRenderbuffer::UnbindAsRenderTarget()
    {
    // Restore original surface & context
    eglMakeCurrent(iPlugin->EglDisplay(), iSavedDrawSurface, iSavedReadSurface,
        iSavedContext);
    ASSERT(eglGetError() == EGL_SUCCESS);
    }

VGImage CHuiFxVg10OffscreenRenderbuffer::Image() const
    {
    return iImage;
    }

void CHuiFxVg10OffscreenRenderbuffer::ReadBackground()
    {
    if (iBackgroundEnabled)
        {
        TBool rotatedDisplay = EFalse;
        CHuiGc::TOrientation orientation = CHuiStatic::Env().PrimaryDisplay().Orientation(); 
        
        // The current context can be the screen context or, in case of nested effects, an "effect off-screen"
        // context. The latter uses VG image that is encapsulated inside PBuffer surface.
        // Only if we have the screen context, the surface content can be in rotated state.
        
        TBool isScreenContext = (iPlugin->EglSharedContext() == eglGetCurrentContext());
        if (isScreenContext)
            {
            rotatedDisplay = orientation == CHuiGc::EOrientationCCW90 || orientation == CHuiGc::EOrientationCW90;
            }


        // Read surface size from egl
        EGLDisplay eglDisp = eglGetCurrentDisplay();
        EGLSurface eglSurf = eglGetCurrentSurface(EGL_DRAW);
        EGLint sfWidth;
        EGLint sfHeight;
        eglQuerySurface(eglDisp, eglSurf, EGL_WIDTH, &sfWidth);
        eglQuerySurface(eglDisp, eglSurf, EGL_HEIGHT, &sfHeight);

        #ifdef HUIFX_TRACE  
        RDebug::Print(_L("CHuiFxVg10OffscreenRenderbuffer::ReadBackground - surface size: %i,%i "), sfWidth, sfHeight);
        #endif
        
        // Read pixels from surface        
        if (!rotatedDisplay)
            {
            // Much easier if no rotation !
            vgGetPixels(iImage, 0, 0, iPosition.iX, sfHeight - (iPosition.iY + iSize.iHeight), iSize.iWidth, iSize.iHeight);
            }
        else
            {
            // If screen is rotated but surface is not in native orientation, this gets difficult
            // because vgGetPixels is not affected by transformations.

            // display size = surface size rotated
            TInt displayHeight = sfWidth;
            TInt displayWidth = sfHeight;
       
            TSize newRotatedImageSize = TSize(iSize.iHeight, iSize.iWidth);
            if (iRotatedImage == VG_INVALID_HANDLE || iRotatedImageSize != newRotatedImageSize)
                {
                PushEGLContext();
                
                // *** Create new vg image
                
                // Remove existing vg image, if any
                if (iRotatedImage != VG_INVALID_HANDLE)
                    {
                    vgDestroyImage(iRotatedImage);
                    iRotatedImage = VG_INVALID_HANDLE;
                    HUIFX_VG_INVARIANT();
                    }
                
                #ifndef __WINS__ // Should possibly query the supported mode instead?
                    VGImageFormat imageInternalFormat = VG_sARGB_8888_PRE;
                #else
                    VGImageFormat imageInternalFormat = VG_sARGB_8888;
                #endif

                    iRotatedImageSize = newRotatedImageSize;
                    iRotatedImage = vgCreateImage(imageInternalFormat, iRotatedImageSize.iWidth, iRotatedImageSize.iHeight, 
                                       VG_IMAGE_QUALITY_NONANTIALIASED);
                    //iRotatedImage = vgCreateImage(imageInternalFormat, iSize.iWidth, iSize.iHeight, 
                    //                   VG_IMAGE_QUALITY_NONANTIALIASED);

                PopEGLContext();
                }
            
            
            // If we have rotation on CHuiGc level, we must manually take that into account when
            // accessing pixels directly                
            TSize rotatedSize = iSize;
            TPoint rotatedPos = iPosition;
            TRect renderBufferLocation = TRect(iPosition, iSize);
            if(orientation == CHuiGc::EOrientationCCW90)
                {
                // Rotate the buffer location relative to real surface coordinates
                rotatedSize = TSize(iSize.iHeight, iSize.iWidth);
                rotatedPos = TPoint(displayHeight - iPosition.iY - iSize.iHeight, iPosition.iX);                        
                renderBufferLocation = TRect(rotatedPos, rotatedSize);            
                }
            else if(orientation == CHuiGc::EOrientationCW90)
                {
                // Rotate the buffer location relative to real surface coordinates
                rotatedSize = TSize(iSize.iHeight, iSize.iWidth);
                rotatedPos = TPoint(iPosition.iY, displayWidth - iPosition.iX - iSize.iWidth);            
                renderBufferLocation = TRect(rotatedPos, rotatedSize);
                }
            else
                {
                // nothing
                }        
            
            #ifdef HUIFX_TRACE 
            RDebug::Print(_L("CHuiFxVg10OffscreenRenderbuffer::ReadBackground - renderBufferLocation: %i,%i, %i,%i "), 
                    renderBufferLocation.iTl.iX,
                    renderBufferLocation.iTl.iY,
                    renderBufferLocation.iBr.iX,
                    renderBufferLocation.iBr.iY);

            TRect vgRect(TPoint(renderBufferLocation.iTl.iX, sfHeight - renderBufferLocation.iTl.iY - rotatedSize.iHeight), rotatedSize);

            RDebug::Print(_L("CHuiFxVg10OffscreenRenderbuffer::ReadBackground - vgRect: %i,%i, %i,%i "), 
                    vgRect.iTl.iX,
                    vgRect.iTl.iY,
                    vgRect.iBr.iX,
                    vgRect.iBr.iY);
            #endif
            
            // So...first get pixels from surface into rotated image
            vgGetPixels(iRotatedImage, 0, 0, renderBufferLocation.iTl.iX, sfHeight - (renderBufferLocation.iTl.iY + rotatedSize.iHeight), rotatedSize.iWidth, rotatedSize.iHeight);
            
            // Draw rotated image into real buffer image, first bind it as render target...
            BindAsRenderTarget();            
            
            // ...store some states...
            const TInt VG_MATRIX_SIZE = 9;
            VGfloat oldMatrix[VG_MATRIX_SIZE];
            vgGetMatrix(oldMatrix);

            // ...set some vg states...
            vgLoadIdentity();
            vgSeti(VG_BLEND_MODE, VG_BLEND_SRC);
            vgSeti(VG_IMAGE_MODE, VG_DRAW_IMAGE_NORMAL);
            vgSeti(VG_SCISSORING, VG_FALSE);

            TInt w = iSize.iWidth;
            TInt h = iSize.iHeight;
            
            // ...select right rotation...
            if (orientation == CHuiGc::EOrientationCW90)
                {
                // Rotate around origo and move back to displayarea
                vgRotate(-90);
                vgTranslate(-h, 0);
                }
            else if (orientation == CHuiGc::EOrientationCCW90)
                {
                // Rotate around origo and move back to displayarea
                vgRotate(90);
                vgTranslate(0, -w);
                }
            else if (orientation == CHuiGc::EOrientation180)
                {
                // Rotate around origo and move back to displayarea
                vgRotate(180);
                vgTranslate(-w, -h);            
                }
            else
                {
                }        
            
            // ...Draw...
            if (iRotatedImage != VG_INVALID_HANDLE)
                {
                vgDrawImage(iRotatedImage);
                }
            
            // ..and restore default VG states
            vgSeti(VG_SCISSORING, VG_TRUE);
            vgSeti(VG_BLEND_MODE, VG_BLEND_SRC_OVER);
            vgLoadMatrix(oldMatrix);

            // ...finally unbind image and we should have the content correctly.
            UnbindAsRenderTarget();
            }
        }    
    }

void CHuiFxVg10OffscreenRenderbuffer::PushEGLContext()
    {
    iPreviousEGLState.iContext= eglGetCurrentContext(); 
    TEGLState& state = iPlugin->GetUploadState();
    if (state.iContext == KErrNotFound)
        {
        TEGLState& state = iPlugin->GetUploadState();
        // the first context used for uploading will be used for all texture uploads
        state.iContext      = iPreviousEGLState.iContext; 
        state.iDrawSurface  = eglGetCurrentSurface(EGL_DRAW);
        state.iReadSurface  = eglGetCurrentSurface(EGL_READ);
        state.iDisplay      = eglGetCurrentDisplay();
        }
    else
        {
        // change context only if necessary
        if (iPreviousEGLState.iContext != state.iContext)
            {
            iPreviousEGLState.iDrawSurface  = eglGetCurrentSurface(EGL_DRAW);
            iPreviousEGLState.iReadSurface  = eglGetCurrentSurface(EGL_READ);
            iPreviousEGLState.iDisplay      = eglGetCurrentDisplay();
            eglMakeCurrent(state.iDisplay, state.iDrawSurface, state.iReadSurface, state.iContext);
            }
        }
    }

void CHuiFxVg10OffscreenRenderbuffer::PopEGLContext()
    {
    if (iPreviousEGLState.iContext != iPlugin->GetUploadState().iContext)
        {
        eglMakeCurrent(iPreviousEGLState.iDisplay,  iPreviousEGLState.iDrawSurface, iPreviousEGLState.iReadSurface,iPreviousEGLState.iContext);
        }
    }
