/*
 * Copyright 1993-2015 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.
 *
 */

/*
    CUDA particle system with volumetric shadows
    sgreen 11/2008

    This sample demonstrates a technique for rendering realistic volumetric
    shadows through a cloud of particles. It uses CUDA for the simulation and
    depth sorting of the particles, and OpenGL for rendering.

    See the accompanying documentation for more details on the algorithm.

    This file handles OpenGL initialization and the user interface.
*/

#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <math.h>

#include <helper_gl.h>
#if defined(__APPLE__) || defined(__MACOSX)
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#include <GLUT/glut.h>
#else
#include <GL/freeglut.h>
#endif
#if defined (WIN32)
#include <GL/wglew.h>
#endif

#include <cuda_runtime.h>
#include <cuda_gl_interop.h>

#include <helper_cuda.h>
#include <helper_cuda_gl.h>

#include "ParticleSystem.h"
#include "ParticleSystem.cuh"
#include "SmokeRenderer.h"
#include "paramgl.h"
#include "GLSLProgram.h"
#include "SmokeShaders.h"

uint numParticles = 1<<16;

ParticleSystem *psystem = 0;
SmokeRenderer *renderer = 0;
GLSLProgram *floorProg = 0;

int winWidth = 1280, winHeight = 1024;
int g_TotalErrors = 0;

// view params
int ox, oy;
int buttonState = 0;
bool keyDown[256];

vec3f cameraPos(0, -1, -4);
vec3f cameraRot(0, 0, 0);
vec3f cameraPosLag(cameraPos);
vec3f cameraRotLag(cameraRot);
vec3f cursorPos(0, 1, 0);
vec3f cursorPosLag(cursorPos);

vec3f lightPos(5.0, 5.0, -5.0);

const float inertia = 0.1f;
const float translateSpeed = 0.002f;
const float cursorSpeed = 0.01f;
const float rotateSpeed = 0.2f;
const float walkSpeed = 0.05f;

enum { M_VIEW = 0, M_MOVE_CURSOR, M_MOVE_LIGHT };
int mode = 0;
int displayMode = (int) SmokeRenderer::VOLUMETRIC;

// QA AutoTest
bool g_bQAReadback = false;

// toggles
bool displayEnabled = true;
bool paused = false;
bool displaySliders = false;
bool wireframe = false;
bool animateEmitter = true;
bool emitterOn = true;
bool sort = true;
bool displayLightBuffer = false;
bool drawVectors = false;
bool doBlur = false;

float emitterVel = 0.0f;
uint emitterRate = 1000;
float emitterRadius = 0.25;
float emitterSpread = 0.0;
uint emitterIndex = 0;

// simulation parameters
float timestep = 0.5f;
float currentTime = 0.0f;
float spriteSize = 0.05f;
float alpha = 0.1f;
float shadowAlpha = 0.02f;
float particleLifetime = (float)numParticles / (float)emitterRate;
vec3f lightColor(1.0f, 1.0f, 0.8f);
vec3f colorAttenuation(0.5f, 0.75f, 1.0f);
float blurRadius = 2.0f;

int numSlices = 64;
int numDisplayedSlices = numSlices;

// fps
static int fpsCount = 0;
static int fpsLimit = 1;
StopWatchInterface *timer = NULL;

float modelView[16];
ParamListGL *params;

GLuint floorTex = 0;

// CheckRender object for verification
#define MAX_EPSILON_ERROR 10.0f
#define THRESHOLD          0.40f

// Define the files that are to be saved and the reference images for validation
const char *sSDKsample = "CUDA Smoke Particles";

const char *sRefBin[]  =
{
    "ref_smokePart_pos.bin",
    "ref_smokePart_vel.bin",
    NULL
};

void runEmitter();

// initialize particle system
void initParticles(int numParticles, bool bUseVBO, bool bUseGL)
{
    psystem = new ParticleSystem(numParticles, bUseVBO, bUseGL);
    psystem->reset(ParticleSystem::CONFIG_RANDOM);

    if (bUseVBO)
    {
        renderer = new SmokeRenderer(numParticles);
        renderer->setLightTarget(vec3f(0.0, 1.0, 0.0));

        sdkCreateTimer(&timer);
    }
}

void cleanup()
{
    if (psystem)
    {
        delete psystem;
    }

    if (renderer)
    {
        delete renderer;
    }

    if (floorProg)
    {
        delete floorProg;
    }

    sdkDeleteTimer(&timer);

    if (params)
    {
        delete params;
    }

    if (floorTex)
    {
        glDeleteTextures(1, &floorTex);
    }
}

void renderScene()
{
    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);

    // draw floor
    floorProg->enable();
    floorProg->bindTexture("tex", floorTex, GL_TEXTURE_2D, 0);
    floorProg->bindTexture("shadowTex", renderer->getShadowTexture(), GL_TEXTURE_2D, 1);
    floorProg->setUniformfv("lightPosEye", renderer->getLightPositionEyeSpace(), 3);
    floorProg->setUniformfv("lightColor", lightColor, 3);

    // set shadow matrix as texture matrix
    matrix4f shadowMatrix = renderer->getShadowMatrix();
    glActiveTexture(GL_TEXTURE0);
    glMatrixMode(GL_TEXTURE);
    glLoadMatrixf((GLfloat *) shadowMatrix.get_value());

    glColor3f(1.0, 1.0, 1.0);
    glNormal3f(0.0, 1.0, 0.0);
    glBegin(GL_QUADS);
    {
        float s = 20.f;
        float rep = 20.f;
        glTexCoord2f(0.f, 0.f);
        glVertex3f(-s, 0, -s);
        glTexCoord2f(rep, 0.f);
        glVertex3f(s, 0, -s);
        glTexCoord2f(rep, rep);
        glVertex3f(s, 0, s);
        glTexCoord2f(0.f, rep);
        glVertex3f(-s, 0, s);
    }
    glEnd();
    floorProg->disable();

    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();

    // draw light
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glTranslatef(lightPos.x, lightPos.y, lightPos.z);
    glColor3fv(&lightColor[0]);
    glutSolidSphere(0.1, 10, 5);
    glPopMatrix();
}

// main rendering loop
void display()
{
    sdkStartTimer(&timer);

    // move camera
    if (cameraPos[1] > 0.0f)
    {
        cameraPos[1] = 0.0f;
    }

    cameraPosLag += (cameraPos - cameraPosLag) * inertia;
    cameraRotLag += (cameraRot - cameraRotLag) * inertia;
    cursorPosLag += (cursorPos - cursorPosLag) * inertia;

    // view transform
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glRotatef(cameraRotLag[0], 1.0, 0.0, 0.0);
    glRotatef(cameraRotLag[1], 0.0, 1.0, 0.0);
    glTranslatef(cameraPosLag[0], cameraPosLag[1], cameraPosLag[2]);

    glGetFloatv(GL_MODELVIEW_MATRIX, modelView);

    // update the simulation
    if (!paused)
    {
        if (emitterOn)
        {
            runEmitter();
        }

        SimParams &p = psystem->getParams();
        p.cursorPos = make_float3(cursorPosLag.x, cursorPosLag.y, cursorPosLag.z);

        psystem->step(timestep);
        currentTime += timestep;
    }

    renderer->calcVectors();
    vec3f sortVector = renderer->getSortVector();

    psystem->setSortVector(make_float3(sortVector.x, sortVector.y, sortVector.z));
    psystem->setModelView(modelView);
    psystem->setSorting(sort);
    psystem->depthSort();

    // render
    glClearColor(0.0, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    renderScene();

    // draw particles
    if (displayEnabled)
    {
        // render scene to offscreen buffers to get correct occlusion
        renderer->beginSceneRender(SmokeRenderer::LIGHT_BUFFER);
        renderScene();
        renderer->endSceneRender(SmokeRenderer::LIGHT_BUFFER);

        renderer->beginSceneRender(SmokeRenderer::SCENE_BUFFER);
        renderScene();
        renderer->endSceneRender(SmokeRenderer::SCENE_BUFFER);

        renderer->setPositionBuffer(psystem->getPosBuffer());
        renderer->setVelocityBuffer(psystem->getVelBuffer());
        renderer->setIndexBuffer(psystem->getSortedIndexBuffer());

        renderer->setNumParticles(psystem->getNumParticles());
        renderer->setParticleRadius(spriteSize);
        renderer->setDisplayLightBuffer(displayLightBuffer);
        renderer->setAlpha(alpha);
        renderer->setShadowAlpha(shadowAlpha);
        renderer->setLightPosition(lightPos);
        renderer->setColorAttenuation(colorAttenuation);
        renderer->setLightColor(lightColor);
        renderer->setNumSlices(numSlices);
        renderer->setNumDisplayedSlices(numDisplayedSlices);
        renderer->setBlurRadius(blurRadius);

        renderer->render();

        if (drawVectors)
        {
            renderer->debugVectors();
        }
    }

    // display sliders
    if (displaySliders)
    {
        glDisable(GL_DEPTH_TEST);
        glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); // invert color
        glEnable(GL_BLEND);
        params->Render(0, 0);
        glDisable(GL_BLEND);
        glEnable(GL_DEPTH_TEST);
    }

    glutSwapBuffers();
    glutReportErrors();
    sdkStopTimer(&timer);

    fpsCount++;

    // this displays the frame rate updated every second (independent of frame rate)
    if (fpsCount >= fpsLimit)
    {
        char fps[256];
        float ifps = 1.f / (sdkGetAverageTimerValue(&timer) / 1000.f);
        sprintf(fps, "CUDA Smoke Particles (%d particles): %3.1f fps", numParticles, ifps);
        glutSetWindowTitle(fps);
        fpsCount = 0;
        fpsLimit = (ifps > 1.f) ? (int)ifps : 1;

        if (paused)
        {
            fpsLimit = 0;
        }

        sdkResetTimer(&timer);
    }


}

// GLUT callback functions
void reshape(int w, int h)
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0, (float) w / (float) h, 0.01, 100.0);

    glMatrixMode(GL_MODELVIEW);
    glViewport(0, 0, w, h);

    renderer->setFOV(60.0);
    renderer->setWindowSize(w, h);
}

void mouse(int button, int state, int x, int y)
{
    int mods;

    if (state == GLUT_DOWN)
    {
        buttonState |= 1<<button;
    }
    else if (state == GLUT_UP)
    {
        buttonState = 0;
    }

    mods = glutGetModifiers();

    if (mods & GLUT_ACTIVE_SHIFT)
    {
        buttonState = 2;
    }
    else if (mods & GLUT_ACTIVE_CTRL)
    {
        buttonState = 3;
    }

    ox = x;
    oy = y;

    if (displaySliders)
    {
        if (params->Mouse(x, y, button, state))
        {
            glutPostRedisplay();
            return;
        }
    }

    glutPostRedisplay();
}

// transform vector by matrix
void xform(vec3f &v, vec3f &r, float *m)
{
    r.x = v.x*m[0] + v.y*m[4] + v.z*m[8] + m[12];
    r.y = v.x*m[1] + v.y*m[5] + v.z*m[9] + m[13];
    r.z = v.x*m[2] + v.y*m[6] + v.z*m[10] + m[14];
}

// transform vector by transpose of matrix (assuming orthonormal)
void ixform(vec3f &v, vec3f &r, float *m)
{
    r.x = v.x*m[0] + v.y*m[1] + v.z*m[2];
    r.y = v.x*m[4] + v.y*m[5] + v.z*m[6];
    r.z = v.x*m[8] + v.y*m[9] + v.z*m[10];
}

void ixformPoint(vec3f &v, vec3f &r, float *m)
{
    vec3f x;
    x.x = v.x - m[12];
    x.y = v.y - m[13];
    x.z = v.z - m[14];
    ixform(x, r, m);
}

void motion(int x, int y)
{
    float dx, dy;
    dx = (float)(x - ox);
    dy = (float)(y - oy);

    if (displaySliders)
    {
        if (params->Motion(x, y))
        {
            ox = x;
            oy = y;
            glutPostRedisplay();
            return;
        }
    }

    switch (mode)
    {
        case M_VIEW:
            {
                if (buttonState == 1)
                {
                    // left = rotate
                    cameraRot[0] += dy * rotateSpeed;
                    cameraRot[1] += dx * rotateSpeed;
                }

                if (buttonState == 2)
                {
                    // middle = translate
                    vec3f v = vec3f(dx*translateSpeed, -dy*translateSpeed, 0.0f);
                    vec3f r;
                    ixform(v, r, modelView);
                    cameraPos += r;
                }

                if (buttonState == 3)
                {
                    // left+middle = zoom
                    vec3f v = vec3f(0.0, 0.0, dy*translateSpeed);
                    vec3f r;
                    ixform(v, r, modelView);
                    cameraPos += r;
                }
            }
            break;

        case M_MOVE_CURSOR:
            {
                if (buttonState==1)
                {
                    vec3f v = vec3f(dx*cursorSpeed, -dy*cursorSpeed, 0.0f);
                    vec3f r;
                    ixform(v, r, modelView);
                    cursorPos += r;
                }
                else if (buttonState==2)
                {
                    vec3f v = vec3f(0.0f, 0.0f, dy*cursorSpeed);
                    vec3f r;
                    ixform(v, r, modelView);
                    cursorPos += r;
                }
            }
            break;

        case M_MOVE_LIGHT:
            if (buttonState==1)
            {
                vec3f v = vec3f(dx*cursorSpeed, -dy*cursorSpeed, 0.0f);
                vec3f r;
                ixform(v, r, modelView);
                lightPos += r;
            }
            else if (buttonState==2)
            {
                vec3f v = vec3f(0.0f, 0.0f, dy*cursorSpeed);
                vec3f r;
                ixform(v, r, modelView);
                lightPos += r;
            }

            break;

    }

    ox = x;
    oy = y;
    glutPostRedisplay();
}

// commented out to remove unused parameter warnings in Linux
void key(unsigned char key, int /*x*/, int /*y*/)
{
    switch (key)
    {
        case ' ':
            paused = !paused;
            break;

        case 13:
            psystem->step(timestep);
            renderer->setPositionBuffer(psystem->getPosBuffer());
            renderer->setVelocityBuffer(psystem->getVelBuffer());
            break;

        case '\033':
            cleanup();
            exit(EXIT_SUCCESS);
            break;

        case 'v':
            mode = M_VIEW;
            animateEmitter = true;
            break;

        case 'm':
            mode = M_MOVE_CURSOR;
            animateEmitter = false;
            break;

        case 'l':
            mode = M_MOVE_LIGHT;
            break;

        case 'r':
            displayEnabled = !displayEnabled;
            break;

        case '1':
            psystem->reset(ParticleSystem::CONFIG_RANDOM);
            break;

        case '2':
            emitterOn ^= 1;
            break;

        case 'W':
            wireframe = !wireframe;
            break;

        case 'h':
            displaySliders = !displaySliders;
            break;

        case 'o':
            sort ^= 1;
            psystem->setSorting(sort);
            break;

        case 'D':
            displayLightBuffer ^= 1;
            break;

        case 'p':
            displayMode = (displayMode + 1) % SmokeRenderer::NUM_MODES;
            renderer->setDisplayMode((SmokeRenderer::DisplayMode) displayMode);
            break;

        case 'P':
            displayMode--;

            if (displayMode < 0)
            {
                displayMode = SmokeRenderer::NUM_MODES - 1;
            }

            renderer->setDisplayMode((SmokeRenderer::DisplayMode) displayMode);
            break;

        case 'V':
            drawVectors ^= 1;
            break;

        case '=':
            numSlices *= 2;

            if (numSlices > 256)
            {
                numSlices = 256;
            }

            numDisplayedSlices = numSlices;
            break;

        case '-':
            if (numSlices > 1)
            {
                numSlices /= 2;
            }

            numDisplayedSlices = numSlices;
            break;

        case 'b':
            doBlur ^= 1;
            renderer->setDoBlur(doBlur);
            break;
    }

    printf("numSlices = %d\n", numSlices);
    keyDown[key] = true;

    glutPostRedisplay();
}

void keyUp(unsigned char key, int /*x*/, int /*y*/)
{
    keyDown[key] = false;
}

void runEmitter()
{
    vec3f vel = vec3f(0, emitterVel, 0);
    vec3f vx(1, 0, 0);
    vec3f vy(0, 0, 1);
    vec3f spread(emitterSpread, 0.0f, emitterSpread);

    psystem->sphereEmitter(emitterIndex, cursorPosLag, vel, spread, emitterRadius, ftoi(emitterRate*timestep), particleLifetime, particleLifetime*0.1f);

    if (emitterIndex > numParticles-1)
    {
        emitterIndex = 0;
    }
}

void special(int k, int x, int y)
{
    if (displaySliders)
    {
        params->Special(k, x, y);
    }
}

void idle(void)
{
    // move camera in view direction
    /*
        0   4   8   12  x
        1   5   9   13  y
        2   6   10  14  z
    */
    if (keyDown['w'])
    {
        cameraPos[0] += modelView[2] * walkSpeed;
        cameraPos[1] += modelView[6] * walkSpeed;
        cameraPos[2] += modelView[10] * walkSpeed;
    }

    if (keyDown['s'])
    {
        cameraPos[0] -= modelView[2] * walkSpeed;
        cameraPos[1] -= modelView[6] * walkSpeed;
        cameraPos[2] -= modelView[10] * walkSpeed;
    }

    if (keyDown['a'])
    {
        cameraPos[0] += modelView[0] * walkSpeed;
        cameraPos[1] += modelView[4] * walkSpeed;
        cameraPos[2] += modelView[8] * walkSpeed;
    }

    if (keyDown['d'])
    {
        cameraPos[0] -= modelView[0] * walkSpeed;
        cameraPos[1] -= modelView[4] * walkSpeed;
        cameraPos[2] -= modelView[8] * walkSpeed;
    }

    if (keyDown['e'])
    {
        cameraPos[0] += modelView[1] * walkSpeed;
        cameraPos[1] += modelView[5] * walkSpeed;
        cameraPos[2] += modelView[9] * walkSpeed;
    }

    if (keyDown['q'])
    {
        cameraPos[0] -= modelView[1] * walkSpeed;
        cameraPos[1] -= modelView[5] * walkSpeed;
        cameraPos[2] -= modelView[9] * walkSpeed;
    }

    if (animateEmitter)
    {
        const float speed = 0.02f;
        cursorPos.x = sin(currentTime*speed)*1.5f;
        cursorPos.y = 1.5f + sin(currentTime*speed*1.3f);
        cursorPos.z = cos(currentTime*speed)*1.5f;
    }

    glutPostRedisplay();
}

// initialize sliders
void initParams()
{
    // create a new parameter list
    params = new ParamListGL("misc");

    params->AddParam(new Param<int>("displayed slices", numDisplayedSlices, 0, 256, 1, &numDisplayedSlices));

    params->AddParam(new Param<float>("time step", timestep, 0.0f, 1.0f, 0.001f, &timestep));

    SimParams &p = psystem->getParams();
    params->AddParam(new Param<float>("damping", 0.99f, 0.0f, 1.0f, 0.001f, &p.globalDamping));
    params->AddParam(new Param<float>("gravity", 0.0f, 0.01f, -0.01f, 0.0001f, &p.gravity.y));

    params->AddParam(new Param<float>("noise freq", 0.1f, 0.0f, 1.0f, 0.001f, &p.noiseFreq));
    params->AddParam(new Param<float>("noise strength", 0.001f, 0.0f, 0.01f, 0.001f, &p.noiseAmp));
    params->AddParam(new Param<float>("noise anim", 0.0f, -0.001f, 0.001f, 0.0001f, &p.noiseSpeed.y));

    params->AddParam(new Param<float>("sprite size", spriteSize, 0.0f, 0.1f, 0.001f, &spriteSize));
    params->AddParam(new Param<float>("alpha", alpha, 0.0f, 1.0f, 0.001f, &alpha));

    params->AddParam(new Param<float>("light color r", lightColor[0], 0.0f, 1.0f, 0.01f, &lightColor[0]));
    params->AddParam(new Param<float>("light color g", lightColor[1], 0.0f, 1.0f, 0.01f, &lightColor[1]));
    params->AddParam(new Param<float>("light color b", lightColor[2], 0.0f, 1.0f, 0.01f, &lightColor[2]));

    params->AddParam(new Param<float>("atten color r", colorAttenuation[0], 0.0f, 1.0f, 0.01f, &colorAttenuation[0]));
    params->AddParam(new Param<float>("atten color g", colorAttenuation[1], 0.0f, 1.0f, 0.01f, &colorAttenuation[1]));
    params->AddParam(new Param<float>("atten color b", colorAttenuation[2], 0.0f, 1.0f, 0.01f, &colorAttenuation[2]));
    params->AddParam(new Param<float>("shadow alpha", shadowAlpha, 0.0f, 0.1f, 0.001f, &shadowAlpha));

    params->AddParam(new Param<float>("blur radius", blurRadius, 0.0f, 10.0f, 0.1f, &blurRadius));

    params->AddParam(new Param<float>("emitter radius", emitterRadius, 0.0f, 2.0f, 0.01f, &emitterRadius));
    params->AddParam(new Param<uint>("emitter rate", emitterRate, 0, 10000, 1, &emitterRate));
    params->AddParam(new Param<float>("emitter velocity", emitterVel, 0.0f, 0.1f, 0.001f, &emitterVel));
    params->AddParam(new Param<float>("emitter spread", emitterSpread, 0.0f, 0.1f, 0.001f, &emitterSpread));

    params->AddParam(new Param<float>("particle lifetime", particleLifetime, 0.0f, 1000.0f, 1.0f, &particleLifetime));
}

void mainMenu(int i)
{
    key((unsigned char) i, 0, 0);
}

void initMenus()
{
    glutCreateMenu(mainMenu);
    glutAddMenuEntry("Reset block [1]", '1');
    glutAddMenuEntry("Toggle emitter [2]", '2');
    glutAddMenuEntry("Toggle animation [ ]", ' ');
    glutAddMenuEntry("Step animation [ret]", 13);
    glutAddMenuEntry("View mode [v]", 'v');
    glutAddMenuEntry("Move cursor mode [m]", 'm');
    glutAddMenuEntry("Move light mode [l]", 'l');
    glutAddMenuEntry("Toggle point rendering [p]", 'p');
    glutAddMenuEntry("Toggle sliders [h]", 'h');
    glutAddMenuEntry("Toggle sorting [o]", 'o');
    glutAddMenuEntry("Toggle vectors [V]", 'V');
    glutAddMenuEntry("Display light buffer [D]", 'D');
    glutAddMenuEntry("Toggle shadow blur [b]", 'b');
    glutAddMenuEntry("Increase no. slices [=]", '=');
    glutAddMenuEntry("Decrease no. slices [-]", '-');
    glutAddMenuEntry("Quit (esc)", '\033');
    glutAttachMenu(GLUT_RIGHT_BUTTON);
}

GLuint createTexture(GLenum target, GLint internalformat, GLenum format, int w, int h, void *data)
{
    GLuint tex;
    glGenTextures(1, &tex);
    glBindTexture(target, tex);
    glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(target, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(target, 0, internalformat, w, h, 0, format, GL_UNSIGNED_BYTE, data);
    return tex;
}

GLuint loadTexture(char *filename)
{
    unsigned char *data = 0;
    unsigned int width, height;
    sdkLoadPPM4ub(filename, &data, &width, &height);

    if (!data)
    {
        printf("Error opening file '%s'\n", filename);
        return 0;
    }

    printf("Loaded '%s', %d x %d pixels\n", filename, width, height);

    return createTexture(GL_TEXTURE_2D, GL_RGBA8, GL_RGBA, width, height, data);
}

// initialize OpenGL
void initGL(int *argc, char **argv)
{
    glutInit(argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
    glutInitWindowSize(winWidth, winHeight);
    glutCreateWindow("CUDA Smoke Particles");

    if (!isGLVersionSupported(2,0))
    {
        fprintf(stderr, "The following required OpenGL extensions missing:\n\tGL_VERSION_2_0\n\tGL_VERSION_1_5\n");
        exit(EXIT_SUCCESS);
    }

    if (!areGLExtensionsSupported("GL_ARB_multitexture GL_ARB_vertex_buffer_object GL_EXT_geometry_shader4"))
    {
        fprintf(stderr, "The following required OpenGL extensions missing:\n\tGL_ARB_multitexture\n\tGL_ARB_vertex_buffer_object\n\tGL_EXT_geometry_shader4.\n");
        exit(EXIT_SUCCESS);
    }

#if defined (WIN32)

    if (wglewIsSupported("WGL_EXT_swap_control"))
    {
        // disable vertical sync
        wglSwapIntervalEXT(0);
    }

#endif

    glEnable(GL_DEPTH_TEST);

    // load floor texture
    char *imagePath = sdkFindFilePath("floortile.ppm", argv[0]);

    if (imagePath == NULL)
    {
        fprintf(stderr, "Error finding floor image file\n");
        exit(EXIT_FAILURE);
    }

    floorTex = loadTexture(imagePath);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16.0f);

    floorProg = new GLSLProgram(floorVS, floorPS);

    glutReportErrors();
}


////////////////////////////////////////////////////////////////////////////////
// Program main
////////////////////////////////////////////////////////////////////////////////
int
main(int argc, char **argv)
{
#if defined(__linux__)
    setenv ("DISPLAY", ":0", 0);
#endif

    printf("%s Starting...\n\n", sSDKsample);

    printf("NOTE: The CUDA Samples are not meant for performance measurements. Results may vary when GPU Boost is enabled.\n\n");

    if (argc > 1)
    {
        if (checkCmdLineFlag(argc, (const char **)argv, "n"))
        {
            numParticles = getCmdLineArgumentInt(argc, (const char **) argv, "n");
        }

        if (checkCmdLineFlag(argc, (const char **)argv, "qatest"))
        {
            g_bQAReadback = true;
        }
    }

    if (g_bQAReadback)
    {
        // For Automated testing, we do not use OpenGL/CUDA interop
        findCudaDevice(argc, (const char **)argv);

        // This code path is used for Automated Testing
        initParticles(numParticles, false, false);
        initParams();

        if (emitterOn)
        {
            runEmitter();
        }

        SimParams &params = psystem->getParams();
        params.cursorPos = make_float3(cursorPosLag.x, cursorPosLag.y, cursorPosLag.z);

        psystem->step(timestep);

        float4 *pos = NULL, *vel = NULL;

        psystem->dumpBin(&pos, &vel);

        sdkDumpBin(pos, numParticles*sizeof(float4), "smokeParticles_pos.bin");
        sdkDumpBin(vel, numParticles*sizeof(float4), "smokeParticles_vel.bin");

        if (!sdkCompareBin2BinFloat("smokeParticles_pos.bin", sRefBin[0], numParticles*sizeof(float4),
                                    MAX_EPSILON_ERROR, THRESHOLD, argv[0]))
        {
            g_TotalErrors++;
        }

        if (!sdkCompareBin2BinFloat("smokeParticles_vel.bin", sRefBin[1], numParticles*sizeof(float4),
                                    MAX_EPSILON_ERROR, THRESHOLD, argv[0]))
        {
            g_TotalErrors++;
        }

        delete psystem;
    }
    else
    {
        // Normal smokeParticles rendering path
        // 1st initialize OpenGL context, so we can properly set the GL for CUDA.
        // This is needed to achieve optimal performance with OpenGL/CUDA interop.
        initGL(&argc, argv);

        findCudaGLDevice(argc, (const char **)argv);

        // This is the normal code path for SmokeParticles
        initParticles(numParticles, true, true);
        initParams();
        initMenus();

        glutDisplayFunc(display);
        glutReshapeFunc(reshape);
        glutMouseFunc(mouse);
        glutMotionFunc(motion);
        glutKeyboardFunc(key);
        glutKeyboardUpFunc(keyUp);
        glutSpecialFunc(special);
        glutIdleFunc(idle);

        glutMainLoop();
    }

    exit(g_TotalErrors > 0 ? EXIT_FAILURE : EXIT_SUCCESS);
}

