JNIEXPORT void JNICALL Java_org_cyanogenmod_focal_pano_MosaicRenderer_setWarping(
        JNIEnv * env, jobject obj, jboolean flag)
{
    // TODO: Review this logic
    if(gWarpImage != (bool) flag) //switching from viewfinder to capture or vice-versa
    {
        // Clear gBuffer[0]
        gWarper1.SetupGraphics(&gBuffer[0]);
        gWarper1.Clear(0.0, 0.0, 0.0, 1.0);
        // Clear gBuffer[1]
        gWarper1.SetupGraphics(&gBuffer[1]);
        gWarper1.Clear(0.0, 0.0, 0.0, 1.0);
        // Clear the screen to black.
        gPreview.Clear(0.0, 0.0, 0.0, 1.0);

        gLastTx = 0.0f;
        gPanOffset = 0.0f;
        gPanViewfinder = true;

        db_Identity3x3(gThisH1t);
        db_Identity3x3(gLastH1t);
        // Make sure g_dAffinetransGL and g_dAffinetransPanGL are updated.
        // Otherwise, the first frame after setting the flag to true will be
        // incorrectly drawn.
        if ((bool) flag) {
            UpdateWarpTransformation(g_dIdent3x3);
        }
    }

    gWarpImage = (bool)flag;
}
JNIEXPORT jint JNICALL Java_org_cyanogenmod_focal_pano_MosaicRenderer_init(
        JNIEnv * env, jobject obj)
{
    gSurfTexRenderer[LR].InitializeGLProgram();
    gSurfTexRenderer[HR].InitializeGLProgram();
    gYVURenderer[LR].InitializeGLProgram();
    gYVURenderer[HR].InitializeGLProgram();
    gWarper1.InitializeGLProgram();
    gWarper2.InitializeGLProgram();
    gPreview.InitializeGLProgram();
    gBuffer[0].InitializeGLContext();
    gBuffer[1].InitializeGLContext();
    gBufferInput[LR].InitializeGLContext();
    gBufferInput[HR].InitializeGLContext();
    gBufferInputYVU[LR].InitializeGLContext();
    gBufferInputYVU[HR].InitializeGLContext();

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    glGenTextures(1, gSurfaceTextureID);
    // bind the surface texture
    bindSurfaceTexture(gSurfaceTextureID[0]);

    return (jint) gSurfaceTextureID[0];
}
JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_setWarping(
        JNIEnv * env, jobject obj, jboolean flag)
{
    // TODO: Review this logic
    if(gWarpImage != (bool) flag) //switching from viewfinder to capture or vice-versa
    {
        // Clear gBuffer[0]
        gWarper1.SetupGraphics(&gBuffer[0]);
        gWarper1.Clear(0.0, 0.0, 0.0, 1.0);
        // Clear gBuffer[1]
        gWarper1.SetupGraphics(&gBuffer[1]);
        gWarper1.Clear(0.0, 0.0, 0.0, 1.0);
        // Clear the screen to black.
        gPreview.Clear(0.0, 0.0, 0.0, 1.0);

        gLastTx = 0.0f;
        gPanOffset = 0.0f;
        gPanViewfinder = true;

        db_Identity3x3(gThisH1t);
        db_Identity3x3(gLastH1t);
    }

    gWarpImage = (bool)flag;
}
JNIEXPORT void JNICALL Java_org_cyanogenmod_focal_pano_MosaicRenderer_step(
        JNIEnv * env, jobject obj)
{
    if(!gWarpImage) // ViewFinder
    {
        gWarper2.SetupGraphics(&gBuffer[gCurrentFBOIndex]);
        gPreview.SetInputTextureName(gBuffer[gCurrentFBOIndex].GetTextureName());

        gWarper2.DrawTexture(g_dTranslationToFBOCenterGL);

        //if (gIsLandscapeOrientation) {
            gPreview.DrawTexture(g_dAffinetransIdentGL);
        //} else {
        //    gPreview.DrawTexture(g_dAffinetransRotation90GL);
        //}
    }
    else
    {
        gWarper1.SetupGraphics(&gBuffer[gCurrentFBOIndex]);
        // Clear the destination so that we can paint on it afresh
        gWarper1.Clear(0.0, 0.0, 0.0, 1.0);
        gWarper1.SetInputTextureName(
                gBuffer[1 - gCurrentFBOIndex].GetTextureName());
        gWarper2.SetupGraphics(&gBuffer[gCurrentFBOIndex]);
        gPreview.SetInputTextureName(gBuffer[gCurrentFBOIndex].GetTextureName());

        gWarper1.DrawTexture(g_dAffinetransGL);
        gWarper2.DrawTexture(g_dTranslationToFBOCenterGL);
        gPreview.DrawTexture(g_dAffinetransPanGL);

        gCurrentFBOIndex = 1 - gCurrentFBOIndex;
    }
}
JNIEXPORT void JNICALL Java_org_cyanogenmod_focal_pano_MosaicRenderer_reset(
        JNIEnv * env, jobject obj,  jint width, jint height, jboolean isLandscapeOrientation)
{
    gIsLandscapeOrientation = isLandscapeOrientation;
    calculateUILayoutScaling(width, height, gIsLandscapeOrientation);

    gBuffer[0].Init(gPreviewFBOWidth, gPreviewFBOHeight, GL_RGBA);
    gBuffer[1].Init(gPreviewFBOWidth, gPreviewFBOHeight, GL_RGBA);

    gBufferInput[LR].Init(gPreviewImageWidth[LR],
            gPreviewImageHeight[LR], GL_RGBA);

    gBufferInput[HR].Init(gPreviewImageWidth[HR],
            gPreviewImageHeight[HR], GL_RGBA);

    gBufferInputYVU[LR].Init(gPreviewImageWidth[LR],
            gPreviewImageHeight[LR], GL_RGBA);

    gBufferInputYVU[HR].Init(gPreviewImageWidth[HR],
            gPreviewImageHeight[HR], GL_RGBA);

    // bind the surface texture
    bindSurfaceTexture(gSurfaceTextureID[0]);

    // To speed up, there is no need to clear the destination buffers
    // (offscreen/screen buffers) of gSurfTexRenderer, gYVURenderer
    // and gPreview because we always fill the whole destination buffers
    // when we draw something to those offscreen/screen buffers.
    gSurfTexRenderer[LR].SetupGraphics(&gBufferInput[LR]);
    gSurfTexRenderer[LR].SetViewportMatrix(1, 1, 1, 1);
    gSurfTexRenderer[LR].SetScalingMatrix(1.0f, -1.0f);
    gSurfTexRenderer[LR].SetInputTextureName(gSurfaceTextureID[0]);
    gSurfTexRenderer[LR].SetInputTextureType(GL_TEXTURE_EXTERNAL_OES_ENUM);

    gSurfTexRenderer[HR].SetupGraphics(&gBufferInput[HR]);
    gSurfTexRenderer[HR].SetViewportMatrix(1, 1, 1, 1);
    gSurfTexRenderer[HR].SetScalingMatrix(1.0f, -1.0f);
    gSurfTexRenderer[HR].SetInputTextureName(gSurfaceTextureID[0]);
    gSurfTexRenderer[HR].SetInputTextureType(GL_TEXTURE_EXTERNAL_OES_ENUM);

    gYVURenderer[LR].SetupGraphics(&gBufferInputYVU[LR]);
    gYVURenderer[LR].SetInputTextureName(gBufferInput[LR].GetTextureName());
    gYVURenderer[LR].SetInputTextureType(GL_TEXTURE_2D);

    gYVURenderer[HR].SetupGraphics(&gBufferInputYVU[HR]);
    gYVURenderer[HR].SetInputTextureName(gBufferInput[HR].GetTextureName());
    gYVURenderer[HR].SetInputTextureType(GL_TEXTURE_2D);

    // gBuffer[1-gCurrentFBOIndex] --> gWarper1 --> gBuffer[gCurrentFBOIndex]
    gWarper1.SetupGraphics(&gBuffer[gCurrentFBOIndex]);

    // Clear the destination buffer of gWarper1.
    gWarper1.Clear(0.0, 0.0, 0.0, 1.0);
    gWarper1.SetViewportMatrix(1, 1, 1, 1);
    gWarper1.SetScalingMatrix(1.0f, 1.0f);
    gWarper1.SetInputTextureName(gBuffer[1 - gCurrentFBOIndex].GetTextureName());
    gWarper1.SetInputTextureType(GL_TEXTURE_2D);

    // gBufferInput[HR] --> gWarper2 --> gBuffer[gCurrentFBOIndex]
    gWarper2.SetupGraphics(&gBuffer[gCurrentFBOIndex]);

    // gWarp2's destination buffer is the same to gWarp1's. No need to clear it
    // again.
    gWarper2.SetViewportMatrix(gPreviewImageWidth[HR],
            gPreviewImageHeight[HR], gBuffer[gCurrentFBOIndex].GetWidth(),
            gBuffer[gCurrentFBOIndex].GetHeight());
    gWarper2.SetScalingMatrix(1.0f, 1.0f);
    gWarper2.SetInputTextureName(gBufferInput[HR].GetTextureName());
    gWarper2.SetInputTextureType(GL_TEXTURE_2D);

    // gBuffer[gCurrentFBOIndex] --> gPreview --> Screen
    gPreview.SetupGraphics(width, height);
    gPreview.SetViewportMatrix(1, 1, 1, 1);

    // Scale the previewFBO so that the viewfinder window fills the layout height
    // while maintaining the image aspect ratio
    gPreview.SetScalingMatrix(gUILayoutScalingX, -1.0f * gUILayoutScalingY);
    gPreview.SetInputTextureName(gBuffer[gCurrentFBOIndex].GetTextureName());
    gPreview.SetInputTextureType(GL_TEXTURE_2D);
}
JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_reset(
        JNIEnv * env, jobject obj,  jint width, jint height, jboolean isLandscapeOrientation)
{
    gIsLandscapeOrientation = isLandscapeOrientation;
    calculateUILayoutScaling(width, height, gIsLandscapeOrientation);

    gBuffer[0].Init(gPreviewFBOWidth, gPreviewFBOHeight, GL_RGBA);
    gBuffer[1].Init(gPreviewFBOWidth, gPreviewFBOHeight, GL_RGBA);

    gBufferInput[LR].Init(gPreviewImageWidth[LR],
            gPreviewImageHeight[LR], GL_RGBA);

    gBufferInput[HR].Init(gPreviewImageWidth[HR],
            gPreviewImageHeight[HR], GL_RGBA);

    gBufferInputYVU[LR].Init(gPreviewImageWidth[LR],
            gPreviewImageHeight[LR], GL_RGBA);

    gBufferInputYVU[HR].Init(gPreviewImageWidth[HR],
            gPreviewImageHeight[HR], GL_RGBA);

    sem_wait(&gPreviewImage_semaphore);
    ClearPreviewImage(LR);
    ClearPreviewImage(HR);
    sem_post(&gPreviewImage_semaphore);

    // bind the surface texture
    bindSurfaceTexture(gSurfaceTextureID[0]);

    gSurfTexRenderer[LR].SetupGraphics(&gBufferInput[LR]);
    gSurfTexRenderer[LR].Clear(0.0, 0.0, 0.0, 1.0);
    gSurfTexRenderer[LR].SetViewportMatrix(1, 1, 1, 1);
    gSurfTexRenderer[LR].SetScalingMatrix(1.0f, -1.0f);
    gSurfTexRenderer[LR].SetInputTextureName(gSurfaceTextureID[0]);
    gSurfTexRenderer[LR].SetInputTextureType(GL_TEXTURE_EXTERNAL_OES_ENUM);

    gSurfTexRenderer[HR].SetupGraphics(&gBufferInput[HR]);
    gSurfTexRenderer[HR].Clear(0.0, 0.0, 0.0, 1.0);
    gSurfTexRenderer[HR].SetViewportMatrix(1, 1, 1, 1);
    gSurfTexRenderer[HR].SetScalingMatrix(1.0f, -1.0f);
    gSurfTexRenderer[HR].SetInputTextureName(gSurfaceTextureID[0]);
    gSurfTexRenderer[HR].SetInputTextureType(GL_TEXTURE_EXTERNAL_OES_ENUM);

    gYVURenderer[LR].SetupGraphics(&gBufferInputYVU[LR]);
    gYVURenderer[LR].Clear(0.0, 0.0, 0.0, 1.0);
    gYVURenderer[LR].SetInputTextureName(gBufferInput[LR].GetTextureName());
    gYVURenderer[LR].SetInputTextureType(GL_TEXTURE_2D);

    gYVURenderer[HR].SetupGraphics(&gBufferInputYVU[HR]);
    gYVURenderer[HR].Clear(0.0, 0.0, 0.0, 1.0);
    gYVURenderer[HR].SetInputTextureName(gBufferInput[HR].GetTextureName());
    gYVURenderer[HR].SetInputTextureType(GL_TEXTURE_2D);

    // gBuffer[1-gCurrentFBOIndex] --> gWarper1 --> gBuffer[gCurrentFBOIndex]
    gWarper1.SetupGraphics(&gBuffer[gCurrentFBOIndex]);
    gWarper1.Clear(0.0, 0.0, 0.0, 1.0);
    gWarper1.SetViewportMatrix(1, 1, 1, 1);
    gWarper1.SetScalingMatrix(1.0f, 1.0f);
    gWarper1.SetInputTextureName(gBuffer[1 - gCurrentFBOIndex].GetTextureName());
    gWarper1.SetInputTextureType(GL_TEXTURE_2D);

    // gBufferInput[HR] --> gWarper2 --> gBuffer[gCurrentFBOIndex]
    gWarper2.SetupGraphics(&gBuffer[gCurrentFBOIndex]);
    gWarper2.Clear(0.0, 0.0, 0.0, 1.0);
    gWarper2.SetViewportMatrix(gPreviewImageWidth[HR],
            gPreviewImageHeight[HR], gBuffer[gCurrentFBOIndex].GetWidth(),
            gBuffer[gCurrentFBOIndex].GetHeight());
    gWarper2.SetScalingMatrix(1.0f, 1.0f);
    gWarper2.SetInputTextureName(gBufferInput[HR].GetTextureName());
    gWarper2.SetInputTextureType(GL_TEXTURE_2D);

    gPreview.SetupGraphics(width, height);
    gPreview.Clear(0.0, 0.0, 0.0, 1.0);
    gPreview.SetViewportMatrix(1, 1, 1, 1);

    // Scale the previewFBO so that the viewfinder window fills the layout height
    // while maintaining the image aspect ratio
    gPreview.SetScalingMatrix(gUILayoutScalingX, -1.0f * gUILayoutScalingY);
    gPreview.SetInputTextureName(gBuffer[gCurrentFBOIndex].GetTextureName());
    gPreview.SetInputTextureType(GL_TEXTURE_2D);
}