/**
 * Copyright 1993-2013 NVIDIA Corporation.  All rights reserved.
 *
 * Please refer to the NVIDIA end user license agreement (EULA) associated
 * with this source code for terms and conditions that govern your use of
 * this software. Any use, reproduction, disclosure, or distribution of
 * this software and related documentation outside the terms of the EULA
 * is strictly prohibited.
 *
 */


#ifndef _RENDERCHECK_GL_H_
#define _RENDERCHECK_GL_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <vector>
#include <map>
#include <string>

#if defined(__APPLE__) || defined(MACOSX)
#include <GLUT/glut.h>
#else
#include <GL/freeglut.h>
#endif

#include <nvShaderUtils.h>

#include <helper_image.h>


using std::vector;
using std::map;
using std::string;

#define BUFFER_OFFSET(i) ((char *)NULL + (i))

#if _DEBUG
#define CHECK_FBO     checkStatus(__FILE__, __LINE__, true)
#else
#define CHECK_FBO     true
#endif



class CheckRender
{
    public:
        CheckRender(unsigned int width, unsigned int height, unsigned int Bpp,
                    bool bQAReadback, bool bUseFBO, bool bUsePBO) :
            m_Width(width), m_Height(height), m_Bpp(Bpp), m_bQAReadback(bQAReadback),
            m_bUseFBO(bUseFBO), m_bUsePBO(bUsePBO), m_PixelFormat(GL_BGRA), m_fThresholdCompare(0.0f)
        {
            allocateMemory(width, height, Bpp, bUseFBO, bUsePBO);
        }

        virtual ~CheckRender()
        {
            // Release PBO resources
            if (m_bUsePBO)
            {
                glDeleteBuffers(1, &m_pboReadback);
                m_pboReadback = 0;
            }

            free(m_pImageData);
        }

        virtual void allocateMemory(unsigned int width, unsigned int height, unsigned int Bpp,
                                    bool bUseFBO, bool bUsePBO)
        {
            // Create the PBO for readbacks
            if (bUsePBO)
            {
                glGenBuffers(1, &m_pboReadback);
                glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, m_pboReadback);
                glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, width*height*Bpp, NULL, GL_STREAM_READ);
                glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
            }

            m_pImageData = (unsigned char *)malloc(width*height*Bpp);  // This is the image data stored in system memory
        }


        virtual void setExecPath(char *path)
        {
            m_ExecPath = path;
        }
        virtual void EnableQAReadback(bool bStatus)
        {
            m_bQAReadback = bStatus;
        }
        virtual bool IsQAReadback()
        {
            return m_bQAReadback;
        }
        virtual bool IsFBO()
        {
            return m_bUseFBO;
        }
        virtual bool IsPBO()
        {
            return m_bUsePBO;
        }
        virtual void *imageData()
        {
            return m_pImageData;
        }

        // Interface to this class functions
        virtual void setPixelFormat(GLenum format)
        {
            m_PixelFormat = format;
        }
        virtual int  getPixelFormat()
        {
            return m_PixelFormat;
        }
        virtual bool checkStatus(const char *zfile, int line, bool silent) = 0;
        virtual bool readback(GLuint width, GLuint height) = 0;
        virtual bool readback(GLuint width, GLuint height, GLuint bufObject) = 0;
        virtual bool readback(GLuint width, GLuint height, unsigned char *membuf) = 0;

        virtual void bindReadback()
        {
            if (!m_bQAReadback)
            {
                printf("CheckRender::bindReadback() uninitialized!\n");
                return;
            }

            if (m_bUsePBO)
            {
                glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, m_pboReadback);   // Bind the PBO
            }
        }

        virtual void unbindReadback()
        {
            if (!m_bQAReadback)
            {
                printf("CheckRender::unbindReadback() uninitialized!\n");
                return;
            }

            if (m_bUsePBO)
            {
                glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, 0);   // Release the bind on the PBO
            }
        }

        virtual void savePGM(const char *zfilename, bool bInvert, void **ppReadBuf)
        {
            if (zfilename != NULL)
            {
                if (bInvert)
                {
                    unsigned char *readBuf;
                    unsigned char *writeBuf= (unsigned char *)malloc(m_Width * m_Height);

                    for (unsigned int y=0; y < m_Height; y++)
                    {
                        if (ppReadBuf)
                        {
                            readBuf = *(unsigned char **)ppReadBuf;
                        }
                        else
                        {
                            readBuf = (unsigned char *)m_pImageData;
                        }

                        memcpy(&writeBuf[m_Width*m_Bpp*y], (readBuf+ m_Width*(m_Height-1-y)), m_Width);
                    }

                    // we copy the results back to original system buffer
                    if (ppReadBuf)
                    {
                        memcpy(*ppReadBuf, writeBuf, m_Width*m_Height);
                    }
                    else
                    {
                        memcpy(m_pImageData, writeBuf, m_Width*m_Height);
                    }

                    free(writeBuf);
                }

                printf("> Saving PGM: <%s>\n", zfilename);

                if (ppReadBuf)
                {
                    sdkSavePGM<unsigned char>(zfilename, *(unsigned char **)ppReadBuf, m_Width, m_Height);
                }
                else
                {
                    sdkSavePGM<unsigned char>(zfilename, (unsigned char *)m_pImageData, m_Width, m_Height);
                }
            }
        }

        virtual void savePPM(const char *zfilename, bool bInvert, void **ppReadBuf)
        {
            if (zfilename != NULL)
            {
                if (bInvert)
                {
                    unsigned char *readBuf;
                    unsigned char *writeBuf= (unsigned char *)malloc(m_Width * m_Height * m_Bpp);

                    for (unsigned int y=0; y < m_Height; y++)
                    {
                        if (ppReadBuf)
                        {
                            readBuf = *(unsigned char **)ppReadBuf;
                        }
                        else
                        {
                            readBuf = (unsigned char *)m_pImageData;
                        }

                        memcpy(&writeBuf[m_Width*m_Bpp*y], (readBuf+ m_Width*m_Bpp*(m_Height-1-y)), m_Width*m_Bpp);
                    }

                    // we copy the results back to original system buffer
                    if (ppReadBuf)
                    {
                        memcpy(*ppReadBuf, writeBuf, m_Width*m_Height*m_Bpp);
                    }
                    else
                    {
                        memcpy(m_pImageData, writeBuf, m_Width*m_Height*m_Bpp);
                    }

                    free(writeBuf);
                }

                printf("> Saving PPM: <%s>\n", zfilename);

                if (ppReadBuf)
                {
                    sdkSavePPM4ub(zfilename, *(unsigned char **)ppReadBuf, m_Width, m_Height);
                }
                else
                {
                    sdkSavePPM4ub(zfilename, (unsigned char *)m_pImageData, m_Width, m_Height);
                }
            }
        }

        virtual bool PGMvsPGM(const char *src_file, const char *ref_file, const float epsilon, const float threshold = 0.0f)
        {
            unsigned char *src_data = NULL, *ref_data = NULL;
            unsigned long error_count = 0;
            unsigned int width, height;

            char *ref_file_path = sdkFindFilePath(ref_file, m_ExecPath.c_str());

            if (ref_file_path == NULL)
            {
                printf("CheckRender::PGMvsPGM unable to find <%s> in <%s> Aborting comparison!\n", ref_file, m_ExecPath.c_str());
                printf(">>> Check info.xml and [project//data] folder <%s> <<<\n", ref_file);
                printf("Aborting comparison!\n");
                printf("  FAILED\n");
                error_count++;
            }
            else
            {

                if (src_file == NULL || ref_file_path == NULL)
                {
                    printf("PGMvsPGM: Aborting comparison\n");
                    return false;
                }

                printf("   src_file <%s>\n", src_file);
                printf("   ref_file <%s>\n", ref_file_path);

                if (sdkLoadPPMub(ref_file_path, &ref_data, &width, &height) != true)
                {
                    printf("PGMvsPGM: unable to load ref image file: %s\n", ref_file_path);
                    return false;
                }

                if (sdkLoadPPMub(src_file, &src_data, &width, &height) != true)
                {
                    printf("PGMvsPGM: unable to load src image file: %s\n", src_file);
                    return false;
                }

                printf("PGMvsPGM: comparing images size (%d,%d) epsilon(%2.4f), threshold(%4.2f%%)\n", m_Height, m_Width, epsilon, threshold*100);

                if (compareDataAsFloatThreshold<unsigned char, float>(ref_data, src_data, m_Height*m_Width, epsilon, threshold) == false)
                {
                    error_count = 1;
                }
            }

            if (error_count == 0)
            {
                printf("  OK\n");
            }
            else
            {
                printf("  FAILURE: %d errors...\n", (unsigned int)error_count);
            }

            return (error_count == 0);  // returns true if all pixels pass
        }

        virtual bool PPMvsPPM(const char *src_file, const char *ref_file, const float epsilon, const float threshold = 0.0f)
        {
            unsigned long error_count = 0;

            char *ref_file_path = sdkFindFilePath(ref_file, m_ExecPath.c_str());

            if (ref_file_path == NULL)
            {
                printf("CheckRender::PPMvsPPM unable to find <%s> in <%s> Aborting comparison!\n", ref_file, m_ExecPath.c_str());
                printf(">>> Check info.xml and [project//data] folder <%s> <<<\n", ref_file);
                printf("Aborting comparison!\n");
                printf("  FAILED\n");
                error_count++;
            }

            if (src_file == NULL || ref_file_path == NULL)
            {
                printf("PPMvsPPM: Aborting comparison\n");
                return false;
            }

            printf("   src_file <%s>\n", src_file);
            printf("   ref_file <%s>\n", ref_file_path);
            return (sdkComparePPM(src_file, ref_file_path, epsilon, threshold, true) == true ? true : false);
        }


        void    setThresholdCompare(float value)
        {
            m_fThresholdCompare = value;
        }

        virtual void dumpBin(void *data, unsigned int bytes, const char *filename)
        {
            FILE *fp;
            printf("CheckRender::dumpBin: <%s>\n", filename);
            FOPEN(fp, filename, "wb");
            fwrite(data, bytes, 1, fp);
            fflush(fp);
            fclose(fp);
        }

        virtual bool compareBin2BinUint(const char *src_file, const char *ref_file, unsigned int nelements, const float epsilon, const float threshold)
        {
            unsigned int *src_buffer, *ref_buffer;
            FILE *src_fp = NULL, *ref_fp = NULL;

            unsigned long error_count = 0;
            size_t fsize = 0;

            FOPEN(src_fp, src_file, "rb");

            if (src_fp == NULL)
            {
                printf("compareBin2Bin <unsigned int> unable to open src_file: %s\n", src_file);
                error_count++;
            }

            char *ref_file_path = sdkFindFilePath(ref_file, m_ExecPath.c_str());

            if (ref_file_path == NULL)
            {
                printf("compareBin2Bin <unsigned int>  unable to find <%s> in <%s>\n", ref_file, m_ExecPath.c_str());
                printf(">>> Check info.xml and [project//data] folder <%s> <<<\n", ref_file);
                printf("Aborting comparison!\n");
                printf("  FAILED\n");
                error_count++;

                if (src_fp)
                {
                    fclose(src_fp);
                }

                if (ref_fp)
                {
                    fclose(ref_fp);
                }
            }
            else
            {
                FOPEN(ref_fp, ref_file_path, "rb");

                if (ref_fp == NULL)
                {
                    printf("compareBin2Bin <unsigned int>  unable to open ref_file: %s\n", ref_file_path);
                    error_count++;
                }

                if (src_fp && ref_fp)
                {
                    src_buffer = (unsigned int *)malloc(nelements*sizeof(unsigned int));
                    ref_buffer = (unsigned int *)malloc(nelements*sizeof(unsigned int));

                    fsize = fread(src_buffer, sizeof(unsigned int), nelements, src_fp);

                    if (fsize != nelements)
                    {
                        printf("compareBin2Bin <unsigned int>  failed to read %u elements from %s\n", nelements, src_file);
                        error_count++;
                    }

                    fsize = fread(ref_buffer, sizeof(unsigned int), nelements, ref_fp);

                    if (fsize == 0)
                    {
                        printf("compareBin2Bin <unsigned int>  failed to read %u elements from %s\n", nelements, ref_file_path);
                        error_count++;
                    }


                    printf("> compareBin2Bin <unsigned int> nelements=%d, epsilon=%4.2f, threshold=%4.2f\n", nelements, epsilon, threshold);
                    printf("   src_file <%s>\n", src_file);
                    printf("   ref_file <%s>\n", ref_file_path);

                    if (!compareData<unsigned int, float>(ref_buffer, src_buffer, nelements, epsilon, threshold))
                    {
                        error_count++;
                    }

                    fclose(src_fp);
                    fclose(ref_fp);

                    free(src_buffer);
                    free(ref_buffer);
                }
                else
                {
                    if (src_fp)
                    {
                        fclose(src_fp);
                    }

                    if (ref_fp)
                    {
                        fclose(ref_fp);
                    }
                }
            }

            if (error_count == 0)
            {
                printf("  OK\n");
            }
            else
            {
                printf("  FAILURE: %d errors...\n", (unsigned int)error_count);
            }

            return (error_count == 0);  // returns true if all pixels pass
        }

        virtual bool compareBin2BinFloat(const char *src_file, const char *ref_file, unsigned int nelements, const float epsilon, const float threshold)
        {
            float *src_buffer, *ref_buffer;
            FILE *src_fp = NULL, *ref_fp = NULL;
            size_t fsize = 0;

            unsigned long error_count = 0;

            FOPEN(src_fp, src_file, "rb");

            if (src_fp == NULL)
            {
                printf("compareBin2Bin <float> unable to open src_file: %s\n", src_file);
                error_count = 1;
            }

            char *ref_file_path = sdkFindFilePath(ref_file, m_ExecPath.c_str());

            if (ref_file_path == NULL)
            {
                printf("compareBin2Bin <float> unable to find <%s> in <%s>\n", ref_file, m_ExecPath.c_str());
                printf(">>> Check info.xml and [project//data] folder <%s> <<<\n", m_ExecPath.c_str());
                printf("Aborting comparison!\n");
                printf("  FAILED\n");
                error_count++;

                if (src_fp)
                {
                    fclose(src_fp);
                }

                if (ref_fp)
                {
                    fclose(ref_fp);
                }
            }
            else
            {
                FOPEN(ref_fp, ref_file_path, "rb");

                if (ref_fp == NULL)
                {
                    printf("compareBin2Bin <float> unable to open ref_file: %s\n", ref_file_path);
                    error_count = 1;
                }

                if (src_fp && ref_fp)
                {
                    src_buffer = (float *)malloc(nelements*sizeof(float));
                    ref_buffer = (float *)malloc(nelements*sizeof(float));

                    fsize = fread(src_buffer, sizeof(float), nelements, src_fp);

                    if (fsize != nelements)
                    {
                        printf("compareBin2Bin <float>  failed to read %u elements from %s\n", nelements, src_file);
                        error_count++;
                    }

                    fsize = fread(ref_buffer, sizeof(float), nelements, ref_fp);

                    if (fsize == 0)
                    {
                        printf("compareBin2Bin <float>  failed to read %u elements from %s\n", nelements, ref_file_path);
                        error_count++;
                    }

                    printf("> compareBin2Bin <float> nelements=%d, epsilon=%4.2f, threshold=%4.2f\n", nelements, epsilon, threshold);
                    printf("   src_file <%s>\n", src_file);
                    printf("   ref_file <%s>\n", ref_file_path);

                    if (!compareDataAsFloatThreshold<float, float>(ref_buffer, src_buffer, nelements, epsilon, threshold))
                    {
                        error_count++;
                    }

                    fclose(src_fp);
                    fclose(ref_fp);

                    free(src_buffer);
                    free(ref_buffer);
                }
                else
                {
                    if (src_fp)
                    {
                        fclose(src_fp);
                    }

                    if (ref_fp)
                    {
                        fclose(ref_fp);
                    }
                }
            }

            if (error_count == 0)
            {
                printf("  OK\n");
            }
            else
            {
                printf("  FAILURE: %d errors...\n", (unsigned int)error_count);
            }

            return (error_count == 0);  // returns true if all pixels pass
        }


    protected:
        unsigned int  m_Width, m_Height, m_Bpp;
        unsigned char *m_pImageData;  // This is the image data stored in system memory
        bool          m_bQAReadback, m_bUseFBO, m_bUsePBO;
        GLuint        m_pboReadback;
        GLenum        m_PixelFormat;
        float         m_fThresholdCompare;
        string        m_ExecPath;
};


class CheckBackBuffer : public CheckRender
{
    public:
        CheckBackBuffer(unsigned int width, unsigned int height, unsigned int Bpp, bool bUseOpenGL = true) :
            CheckRender(width, height, Bpp, false, false, bUseOpenGL)
        {
        }

        virtual ~CheckBackBuffer()
        {
        }

        virtual bool checkStatus(const char *zfile, int line, bool silent)
        {
            GLenum nErrorCode = glGetError();

            if (nErrorCode != GL_NO_ERROR)
            {
                if (!silent)
                {
                    printf("Assertion failed(%s,%d): %s\n", zfile, line, gluErrorString(nErrorCode));
                }
            }

            return true;
        }

        virtual bool readback(GLuint width, GLuint height)
        {
            bool ret = false;

            if (m_bUsePBO)
            {
                // binds the PBO for readback
                bindReadback();

                // Initiate the readback BLT from BackBuffer->PBO->membuf
                glReadPixels(0, 0, width, height, getPixelFormat(),      GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
                ret = checkStatus(__FILE__, __LINE__, true);

                if (!ret)
                {
                    printf("CheckBackBuffer::glReadPixels() checkStatus = %d\n", ret);
                }

                // map - unmap simulates readback without the copy
                void *ioMem = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY);
                memcpy(m_pImageData,    ioMem, width*height*m_Bpp);

                glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB);

                // release the PBO
                unbindReadback();
            }
            else
            {
                // reading direct from the backbuffer
                glReadBuffer(GL_FRONT);
                glReadPixels(0, 0, width, height, getPixelFormat(), GL_UNSIGNED_BYTE, m_pImageData);
            }

            return ret;
        }

        virtual bool readback(GLuint width, GLuint height, GLuint bufObject)
        {
            bool ret = false;

            if (m_bUseFBO)
            {
                if (m_bUsePBO)
                {
                    printf("CheckBackBuffer::readback() FBO->PBO->m_pImageData\n");
                    // binds the PBO for readback
                    bindReadback();

                    // bind FBO buffer (we want to transfer FBO -> PBO)
                    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, bufObject);

                    // Now initiate the readback to PBO
                    glReadPixels(0, 0, width, height, getPixelFormat(),      GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
                    ret = checkStatus(__FILE__, __LINE__, true);

                    if (!ret)
                    {
                        printf("CheckBackBuffer::readback() FBO->PBO checkStatus = %d\n", ret);
                    }

                    // map - unmap simulates readback without the copy
                    void *ioMem = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY);
                    memcpy(m_pImageData,    ioMem, width*height*m_Bpp);

                    glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB);

                    // release the FBO
                    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

                    // release the PBO
                    unbindReadback();
                }
                else
                {
                    printf("CheckBackBuffer::readback() FBO->m_pImageData\n");
                    // Reading direct to FBO using glReadPixels
                    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, bufObject);
                    ret = checkStatus(__FILE__, __LINE__, true);

                    if (!ret)
                    {
                        printf("CheckBackBuffer::readback::glBindFramebufferEXT() fbo=%d checkStatus = %d\n", bufObject, ret);
                    }

                    glReadBuffer(static_cast<GLenum>(GL_COLOR_ATTACHMENT0_EXT));
                    ret &= checkStatus(__FILE__, __LINE__, true);

                    if (!ret)
                    {
                        printf("CheckBackBuffer::readback::glReadBuffer() fbo=%d checkStatus = %d\n", bufObject, ret);
                    }

                    glReadPixels(0, 0, width, height, getPixelFormat(), GL_UNSIGNED_BYTE, m_pImageData);

                    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
                }
            }
            else
            {

                printf("CheckBackBuffer::readback() PBO->m_pImageData\n");
                // read from bufObject (PBO) to system memorys image
                glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, bufObject);   // Bind the PBO

                // map - unmap simulates readback without the copy
                void *ioMem = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY);

                // allocate a buffer so we can flip the image
                unsigned char *temp_buf = (unsigned char *)malloc(width*height*m_Bpp);
                memcpy(temp_buf, ioMem, width*height*m_Bpp);

                // let's flip the image as we copy
                for (unsigned int y = 0; y < height; y++)
                {
                    memcpy((void *)&(m_pImageData[(height-y)*width*m_Bpp]), (void *)&(temp_buf[y*width*m_Bpp]), width*m_Bpp);
                }

                free(temp_buf);

                glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB);

                // read from bufObject (PBO) to system memory image
                glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, 0);   // unBind the PBO
            }

            return CHECK_FBO;
        }

        virtual bool readback(GLuint width, GLuint height, unsigned char *memBuf)
        {
            // let's flip the image as we copy
            for (unsigned int y = 0; y < height; y++)
            {
                memcpy((void *)&(m_pImageData[(height-y)*width*m_Bpp]), (void *)&(memBuf[y*width*m_Bpp]), width*m_Bpp);
            }

            return true;
        }

    private:
        virtual void bindFragmentProgram() {};
        virtual void bindRenderPath() {};
        virtual void unbindRenderPath() {};

        // bind to the BackBuffer to Texture
        virtual void bindTexture() {};

        // release this bind
        virtual void unbindTexture() {};
};


// structure defining the properties of a single buffer
struct bufferConfig
{
    string name;
    GLenum format;
    int bits;
};

// structures defining properties of an FBO
struct fboConfig
{
    string name;
    GLenum colorFormat;
    GLenum depthFormat;
    int redbits;
    int depthBits;
    int depthSamples;
    int coverageSamples;
};

struct fboData
{
    GLuint colorTex; //color texture
    GLuint depthTex; //depth texture
    GLuint fb;      // render framebuffer
    GLuint resolveFB; //multisample resolve target
    GLuint colorRB; //color render buffer
    GLuint depthRB; // depth render buffer
};


class CFrameBufferObject
{
    public:
        CFrameBufferObject(unsigned int width, unsigned int height, unsigned int Bpp, bool bUseFloat, GLenum eTarget) :
            m_Width(width),
            m_Height(height),
            m_bUseFloat(bUseFloat),
            m_eGLTarget(eTarget)
        {
            glGenFramebuffersEXT(1, &m_fboData.fb);

            m_fboData.colorTex = createTexture(m_eGLTarget, width, height,
                                               (bUseFloat ? GL_RGBA32F_ARB : GL_RGBA8), GL_RGBA);

            m_fboData.depthTex = createTexture(m_eGLTarget, width, height,
                                               GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT);

            attachTexture(m_eGLTarget, m_fboData.colorTex,   GL_COLOR_ATTACHMENT0_EXT);
            attachTexture(m_eGLTarget, m_fboData.depthTex,   GL_DEPTH_ATTACHMENT_EXT);

            //    bool ret = checkStatus(__FILE__, __LINE__, true);
        }

        virtual ~CFrameBufferObject()
        {
            //   freeResources();
        }

        GLuint createTexture(GLenum target, int w, int h, GLint internalformat, GLenum format)
        {
            GLuint texid;
            glGenTextures(1, &texid);
            glBindTexture(target, texid);

            glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

            glTexImage2D(target, 0, internalformat, w, h, 0, format, GL_FLOAT, 0);
            return texid;
        }

        void    attachTexture(GLenum texTarget,
                              GLuint texId,
                              GLenum attachment   = GL_COLOR_ATTACHMENT0_EXT,
                              int mipLevel        = 0,
                              int zSlice          = 0)
        {
            bindRenderPath();

            switch (texTarget)
            {
                case GL_TEXTURE_1D:
                    glFramebufferTexture1DEXT(GL_FRAMEBUFFER_EXT, attachment,
                                              GL_TEXTURE_1D, texId, mipLevel);
                    break;

                case GL_TEXTURE_3D:
                    glFramebufferTexture3DEXT(GL_FRAMEBUFFER_EXT, attachment,
                                              GL_TEXTURE_3D, texId, mipLevel, zSlice);
                    break;

                default:
                    // Default is GL_TEXTURE_2D, GL_TEXTURE_RECTANGLE_ARB, or cube faces
                    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attachment,
                                              texTarget, texId, mipLevel);
                    break;
            }

            unbindRenderPath();
        }

        bool initialize(unsigned width, unsigned height, fboConfig &rConfigFBO, fboData &rActiveFBO)
        {
            //Framebuffer config options
            vector<bufferConfig> colorConfigs;
            vector<bufferConfig> depthConfigs;
            bufferConfig temp;

            //add default color configs
            temp.name   = (m_bUseFloat ? "RGBA32F" : "RGBA8");
            temp.bits   = (m_bUseFloat ? 32 : 8);
            temp.format = (m_bUseFloat ? GL_RGBA32F_ARB : GL_RGBA8);
            colorConfigs.push_back(temp);

            //add default depth configs
            temp.name = "D24";
            temp.bits = 24;
            temp.format = GL_DEPTH_COMPONENT24;
            depthConfigs.push_back(temp);

            // If the FBO can be created, add it to the list of available configs, and make a menu entry
            string root = colorConfigs[0].name + " " + depthConfigs[0].name;

            rConfigFBO.colorFormat  = colorConfigs[0].format;
            rConfigFBO.depthFormat  = depthConfigs[0].format;
            rConfigFBO.redbits      = colorConfigs[0].bits;
            rConfigFBO.depthBits    = depthConfigs[0].bits;

            //single sample
            rConfigFBO.name             = root;
            rConfigFBO.coverageSamples  = 0;
            rConfigFBO.depthSamples     = 0;

            create(width, height, rConfigFBO, rActiveFBO);

            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

            if (m_bUseFloat)
            {
                // load fragment programs
                const char *strTextureProgram2D =
                    "!!ARBfp1.0\n"
                    "TEX result.color, fragment.texcoord[0], texture[0], 2D;\n"
                    "END\n";

                m_textureProgram = nv::CompileASMShader(GL_FRAGMENT_PROGRAM_ARB, strTextureProgram2D);

                const char *strOverlayProgram =
                    "!!ARBfp1.0\n"
                    "TEMP t;\n"
                    "TEX t, fragment.texcoord[0], texture[0], 2D;\n"
                    "MOV result.color, t;\n"
                    "END\n";

                m_overlayProgram = nv::CompileASMShader(GL_FRAGMENT_PROGRAM_ARB, strOverlayProgram);
            }

            return CHECK_FBO;
        }

        bool create(GLuint width, GLuint height, fboConfig &config, fboData &data)
        {
            bool multisample = config.depthSamples > 0;
            bool ret = true;
            GLint query;

            printf("\nCreating FBO <%s> (%dx%d) Float:%s\n", config.name.c_str(), (int)width, (int)height, (m_bUseFloat ? "Y":"N"));

            glGenFramebuffersEXT(1, &data.fb);
            glGenTextures(1, &data.colorTex);

            // init texture
            glBindTexture(m_eGLTarget, data.colorTex);
            glTexImage2D(m_eGLTarget, 0, config.colorFormat,
                         width, height, 0, GL_RGBA,
                         (m_bUseFloat ? GL_FLOAT : GL_UNSIGNED_BYTE),
                         NULL);

            glGenerateMipmapEXT(m_eGLTarget);

            glTexParameterf(m_eGLTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameterf(m_eGLTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            glTexParameterf(m_eGLTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);  // GL_LINEAR_MIPMAP_LINEAR);
            glTexParameterf(m_eGLTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);  // GL_LINEAR);

            {
                glGenTextures(1, &data.depthTex);
                data.depthRB = 0;
                data.colorRB = 0;
                data.resolveFB = 0;

                //non-multisample, so bind things directly to the FBO
                glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, data.fb);
                glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, m_eGLTarget, data.colorTex, 0);

                glBindTexture(m_eGLTarget, data.depthTex);
                glTexImage2D(m_eGLTarget, 0, config.depthFormat,
                             width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);

                glTexParameterf(m_eGLTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);  // GL_LINEAR);
                glTexParameterf(m_eGLTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);  // GL_LINEAR);
                glTexParameterf(m_eGLTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
                glTexParameterf(m_eGLTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
                glTexParameterf(m_eGLTarget, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE);

                glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, m_eGLTarget, data.depthTex, 0);

                ret &= checkStatus(__FILE__, __LINE__, true);
            }

            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, data.fb);
            glGetIntegerv(GL_RED_BITS, &query);

            if (query != config.redbits)
            {
                ret = false;
            }

            glGetIntegerv(GL_DEPTH_BITS, &query);

            if (query != config.depthBits)
            {
                ret = false;
            }

            if (multisample)
            {
                glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, data.resolveFB);
                glGetIntegerv(GL_RED_BITS, &query);

                if (query != config.redbits)
                {
                    ret = false;
                }
            }

            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

            ret &= checkStatus(__FILE__, __LINE__, true);

            return ret;
        }

        virtual void freeResources()
        {
            if (m_fboData.fb)
            {
                glDeleteFramebuffersEXT(1, &m_fboData.fb);
            }

            if (m_fboData.resolveFB)
            {
                glDeleteFramebuffersEXT(1, &m_fboData.resolveFB);
            }

            if (m_fboData.colorRB)
            {
                glDeleteRenderbuffersEXT(1, &m_fboData.colorRB);
            }

            if (m_fboData.depthRB)
            {
                glDeleteRenderbuffersEXT(1, &m_fboData.depthRB);
            }

            if (m_fboData.colorTex)
            {
                glDeleteTextures(1, &m_fboData.colorTex);
            }

            if (m_fboData.depthTex)
            {
                glDeleteTextures(1, &m_fboData.depthTex);
            }

            glDeleteProgramsARB(1, &m_textureProgram);
            glDeleteProgramsARB(1, &m_overlayProgram);
        }

        virtual bool checkStatus(const char *zfile, int line, bool silent)
        {
            GLenum status;
            status = (GLenum) glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);

            if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
            {
                printf("<%s : %d> - ", zfile, line);
            }

            switch (status)
            {
                case GL_FRAMEBUFFER_COMPLETE_EXT:
                    break;

                case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
                    if (!silent)
                    {
                        printf("Unsupported framebuffer format\n");
                    }

                    return false;

                case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
                    if (!silent)
                    {
                        printf("Framebuffer incomplete, missing attachment\n");
                    }

                    return false;

                case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
                    if (!silent)
                    {
                        printf("Framebuffer incomplete, duplicate attachment\n");
                    }

                    return false;

                case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
                    if (!silent)
                    {
                        printf("Framebuffer incomplete, attached images must have same dimensions\n");
                    }

                    return false;

                case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
                    if (!silent)
                    {
                        printf("Framebuffer incomplete, attached images must have same format\n");
                    }

                    return false;

                case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
                    if (!silent)
                    {
                        printf("Framebuffer incomplete, missing draw buffer\n");
                    }

                    return false;

                case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
                    if (!silent)
                    {
                        printf("Framebuffer incomplete, missing read buffer\n");
                    }

                    return false;

                default:
                    assert(0);
                    return false;
            }

            return true;
        }

        virtual void renderQuad(int width, int height, GLenum eTarget)
        {
            float width_norm  = (float)width/(float)m_Width,
                  height_norm = (float)height/(float)m_Height;

            // Bind the FBO texture for the display path
            glBindTexture(eTarget, m_fboData.colorTex);

            glGenerateMipmapEXT(GL_TEXTURE_2D);
            glBindTexture(eTarget, 0);

            // now render to the full screen using this texture
            glClearColor(0.2f, 0.2f, 0.2f, 0.0f);
            glClear(GL_COLOR_BUFFER_BIT);

            glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_textureProgram);
            glEnable(GL_FRAGMENT_PROGRAM_ARB);
            glDisable(GL_DEPTH_TEST);

            glBegin(GL_QUADS);
            {
                glVertex2f(0.0f      , 0.0f);
                glTexCoord2f(0.0f      , 0.0f);
                glVertex2f(0.0f      , height_norm);
                glTexCoord2f(width_norm, 0.0f);
                glVertex2f(width_norm, height_norm);
                glTexCoord2f(width_norm, height_norm);
                glVertex2f(width_norm, 0.0f);
                glTexCoord2f(0.0f      , height_norm);
            }
            glEnd();

            // Release the FBO texture (finished rendering)
            glBindTexture(eTarget, 0);
        }

        // bind to the Fragment Program
        void bindFragmentProgram()
        {
            glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_textureProgram);
            glEnable(GL_FRAGMENT_PROGRAM_ARB);
        }

        // bind to the FrameBuffer Object
        void bindRenderPath()
        {
            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_fboData.fb);
        }

        // release current FrameBuffer Object
        void unbindRenderPath()
        {
            glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
        }

        // bind to the FBO to Texture
        void bindTexture()
        {
            glBindTexture(m_eGLTarget, m_fboData.colorTex);
        }

        // release this bind
        void unbindTexture()
        {
            glBindTexture(m_eGLTarget, 0);
        }

        GLuint getFbo()
        {
            return m_fboData.fb;
        }
        GLuint getTex()
        {
            return m_fboData.colorTex;
        }
        GLuint getDepthTex()
        {
            return m_fboData.depthTex;
        }

    private:
        GLuint    m_Width, m_Height;
        fboData   m_fboData;
        fboConfig m_fboConfig;

        GLuint    m_textureProgram;
        GLuint    m_overlayProgram;

        bool      m_bUseFloat;
        GLenum    m_eGLTarget;
};


// CheckFBO - render and verify contents of the FBO
class CheckFBO: public CheckRender
{
    public:
        CheckFBO(unsigned int width, unsigned int height, unsigned int Bpp) :
            CheckRender(width, height, Bpp, false, false, true),
            m_pFrameBufferObject(NULL)
        {
        }

        CheckFBO(unsigned int width, unsigned int height, unsigned int Bpp, CFrameBufferObject *pFrameBufferObject) :
            CheckRender(width, height, Bpp, false, true, true),
            m_pFrameBufferObject(pFrameBufferObject)
        {
        }

        virtual ~CheckFBO()
        {
        }

        virtual bool checkStatus(const char *zfile, int line, bool silent)
        {
            GLenum status;
            status = (GLenum) glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);

            if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
            {
                printf("<%s : %d> - ", zfile, line);
            }

            switch (status)
            {
                case GL_FRAMEBUFFER_COMPLETE_EXT:
                    break;

                case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
                    if (!silent)
                    {
                        printf("Unsupported framebuffer format\n");
                    }

                    return false;

                case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
                    if (!silent)
                    {
                        printf("Framebuffer incomplete, missing attachment\n");
                    }

                    return false;

                case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
                    if (!silent)
                    {
                        printf("Framebuffer incomplete, duplicate attachment\n");
                    }

                    return false;

                case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
                    if (!silent)
                    {
                        printf("Framebuffer incomplete, attached images must have same dimensions\n");
                    }

                    return false;

                case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
                    if (!silent)
                    {
                        printf("Framebuffer incomplete, attached images must have same format\n");
                    }

                    return false;

                case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
                    if (!silent)
                    {
                        printf("Framebuffer incomplete, missing draw buffer\n");
                    }

                    return false;

                case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
                    if (!silent)
                    {
                        printf("Framebuffer incomplete, missing read buffer\n");
                    }

                    return false;

                default:
                    assert(0);
                    return false;
            }

            return true;
        }

        virtual bool readback(GLuint width, GLuint height)
        {
            bool ret = false;

            if (m_bUsePBO)
            {
                // binds the PBO for readback
                bindReadback();

                // bind FBO buffer (we want to transfer FBO -> PBO)
                glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_pFrameBufferObject->getFbo());

                // Now initiate the readback to PBO
                glReadPixels(0, 0, width, height, getPixelFormat(),      GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
                ret = checkStatus(__FILE__, __LINE__, true);

                if (!ret)
                {
                    printf("CheckFBO::readback() FBO->PBO checkStatus = %d\n", ret);
                }

                // map - unmap simulates readback without the copy
                void *ioMem = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY);
                memcpy(m_pImageData,    ioMem, width*height*m_Bpp);

                glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB);

                // release the FBO
                glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

                // release the PBO
                unbindReadback();
            }
            else
            {
                // Reading back from FBO using glReadPixels
                glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_pFrameBufferObject->getFbo());
                ret = checkStatus(__FILE__, __LINE__, true);

                if (!ret)
                {
                    printf("CheckFBO::readback::glBindFramebufferEXT() checkStatus = %d\n", ret);
                }

                glReadBuffer(static_cast<GLenum>(GL_COLOR_ATTACHMENT0_EXT));
                ret &= checkStatus(__FILE__, __LINE__, true);

                if (!ret)
                {
                    printf("CheckFBO::readback::glReadBuffer() checkStatus = %d\n", ret);
                }

                glReadPixels(0, 0, width, height, getPixelFormat(), GL_UNSIGNED_BYTE, m_pImageData);

                glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
            }

            return CHECK_FBO;
        }

        virtual bool readback(GLuint width, GLuint height, GLuint bufObject)
        {
            bool ret = false;

            if (m_bUseFBO)
            {
                if (m_bUsePBO)
                {
                    printf("CheckFBO::readback() FBO->PBO->m_pImageData\n");
                    // binds the PBO for readback
                    bindReadback();

                    // bind FBO buffer (we want to transfer FBO -> PBO)
                    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, bufObject);

                    // Now initiate the readback to PBO
                    glReadPixels(0, 0, width, height, getPixelFormat(),      GL_UNSIGNED_BYTE, BUFFER_OFFSET(0));
                    ret = checkStatus(__FILE__, __LINE__, true);

                    if (!ret)
                    {
                        printf("CheckFBO::readback() FBO->PBO checkStatus = %d\n", ret);
                    }

                    // map - unmap simulates readback without the copy
                    void *ioMem = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY);
                    memcpy(m_pImageData,    ioMem, width*height*m_Bpp);

                    glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB);

                    // release the FBO
                    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

                    // release the PBO
                    unbindReadback();
                }
                else
                {
                    printf("CheckFBO::readback() FBO->m_pImageData\n");
                    // Reading direct to FBO using glReadPixels
                    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, bufObject);
                    ret = checkStatus(__FILE__, __LINE__, true);

                    if (!ret)
                    {
                        printf("CheckFBO::readback::glBindFramebufferEXT() fbo=%d checkStatus = %d\n", (int)bufObject, (int)ret);
                    }

                    glReadBuffer(static_cast<GLenum>(GL_COLOR_ATTACHMENT0_EXT));
                    ret &= checkStatus(__FILE__, __LINE__, true);

                    if (!ret)
                    {
                        printf("CheckFBO::readback::glReadBuffer() fbo=%d checkStatus = %d\n", (int)bufObject, (int)ret);
                    }

                    glReadPixels(0, 0, width, height, getPixelFormat(), GL_UNSIGNED_BYTE, m_pImageData);

                    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
                }
            }
            else
            {
                printf("CheckFBO::readback() PBO->m_pImageData\n");
                // read from bufObject (PBO) to system memorys image
                glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, bufObject);   // Bind the PBO

                // map - unmap simulates readback without the copy
                void *ioMem = glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY);
                memcpy(m_pImageData,    ioMem, width*height*m_Bpp);

                glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB);

                // read from bufObject (PBO) to system memory image
                glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, 0);   // unBind the PBO
            }

            return CHECK_FBO;
        }

        virtual bool readback(GLuint width, GLuint height, unsigned char *memBuf)
        {
            // let's flip the image as we copy
            for (unsigned int y = 0; y < height; y++)
            {
                memcpy((void *)&(m_pImageData[(height-y)*width*m_Bpp]), (void *)&(memBuf[y*width*m_Bpp]), width*m_Bpp);
            }

            return true;
        }

    private:
        CFrameBufferObject *m_pFrameBufferObject;
};

#endif // _RENDERCHECK_GL_H_

