Main Page | Namespace List | Class Hierarchy | Class List | Directories | File List | Namespace Members | Class Members | File Members | Examples

simpleWave.cpp

/**********************************************************************
 * 
 *  File: simpleWave.cpp
 * 
 *  Created: Jul 11, 2005
 *
 *  Version: $Id: simpleWave.cpp,v 1.13 2005/09/23 12:20:56 josse77 Exp $
 *
 *  Authors: Trond R. Hagen <trr@sintef.no>, 
 *       Jon Mikkelsen Hjelmervik <jami@sintef.no>,
 *       Johan S. Seland <johans@ifi.uio.no>
 *
 *  This file is part of the Shallows library.
 *  Copyright (C) 2005 by SINTEF.  All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  ("GPL") version 2 as published by the Free Software Foundation.
 *  See the file LICENSE.GPL at the root directory of this source
 *  distribution for additional information about the GNU GPL.
 * 
 *  For using Shallows with software that can not be combined with the
 *  GNU GPL, please contact SINTEF for aquiring a commercial license
 *  and support.
 *
 *  SINTEF, Pb 124 Blindern, N-0314 Oslo, Norway
 *  http://www.sintef.no
 *********************************************************************/

/* This program implements the basic wave equation with dampening.
 * It also visualises the result of the equation in a nice way.
 * 
 * The wave equation is dependent on the two previous timesteps, and hence 
 * we need a total of three rendertextures. 
 * 
 * This application demonstrates several common GPGPU tasks using shallows, 
 * including the following:
 * 
 *  - Using off-screen buffers for calculation
 *  - Ping-ponging of buffers as input to an algorithm
 *  - Creating textures from image files and memory
 *  - Reading data back from the GPU
 *  - Installing and setting parameters to shaders
 *  - Handling runtime errors in shaders
 */
 

/* First include some standard headers. */

#include <iostream>
#include <algorithm>
#include <iterator>
#include <vector>
#include <cmath>
#include <sstream>
#include <cstdlib>

/* For creating an OpenGL context and windows we use glut. */
#include <GL/glut.h>

/* Shallows uses the boost::shared pointer. This avoid many memory leaks,
 * and almost every use of basic pointer could be replaced by this. */
#include <boost/shared_ptr.hpp>

/* Shallows has some convenience headers which includes everything. Use them 
 * to get started. */
#include "shallows/shallows.hpp"
#include "shallows/utils/utils.hpp"

/* This is a toy application, so we pollute the global namespace */
using namespace shallows;
using namespace std;
using boost::shared_ptr;

/* The numerical simulation happens in the wave shader. */
shared_ptr<GLProgram> waveshader;

/* For visualization we use the rendershader. */
shared_ptr<GLProgram> rendershader;

/* We also need a shader to read in the inital values. */
shared_ptr<GLProgram> initProg;

/* We need an on-screen buffer and rendertarget for visualization and capture */
shared_ptr<OnScreenBuffer> ons_fb;
shared_ptr<OnScreenRenderTarget> ons_rt;

/* The wave equation uses the two last timesteps, hence we need a total
 * of three rendertextures. */
shared_ptr<RenderTexture2D> rt[3];

/* We will also give meanigful names to the rendertargets .*/
shared_ptr<RenderTexture2D> prev, curr, next;

/* We need a nice background for rendering. */
shared_ptr<Texture2D> background;

/* For doing benchmarks, shallows includes a simple timer class. */
shallows::utils::Timer rolex;

/* We statically define the size of the computational grid. */
unsigned int BUF_DIM = 256;

/* For visualization we need a light. */
GLfloat light_position[] = { 1.0/4.0, 0.5, 2.0, 0.0 };

int window_width = 0;
int window_height = 0;

void 
wavepass()  {
        
    const float min_u_ = 0.0;
    const float min_v_ = 0.0;
    const float max_u_ = 1.0;
    const float max_v_ = 1.0;
    
    /* This is the basic GPGPU quad. */
    glBegin(GL_QUADS);
        glTexCoord2f(min_u_, min_v_);
        glVertex3f(-1.0f, -1.0f, 0.5f);

        glTexCoord2f(max_u_, min_v_);
        glVertex3f(1.0f, -1.0f, 0.5f);

        glTexCoord2f(max_u_, max_v_);
        glVertex3f(1.0f, 1.0f, 0.5f);

        glTexCoord2f(min_u_, max_v_);
        glVertex3f(-1.0f, 1.0f, 0.5f);
    glEnd();
    
    /* Disable the shader, and randomy add raindrops. Notice that most
     * of the time we do not let a raindrop fall. We draw the raindrops as
     * gradually higher and narrower points. */
    setFixedFunction();
    if ( static_cast<double>( rand() )/ RAND_MAX > 0.999 ) {       
    float x = 2*static_cast<double>( rand() )/ RAND_MAX -1;
    float y = 2*static_cast<double>( rand() )/ RAND_MAX -1;
    glEnable( GL_POINT_SMOOTH );
    glPointSize( 15 );
    glBegin( GL_POINTS );
        glColor4f( .25, 0.0, 0.0, 0.0 );
        glVertex3f( x, y, 0.5f );
    glEnd();
   
   glPointSize( 10 );
   glBegin( GL_POINTS );
        glColor4f( 0.50, 0.0, 0.0, 0.0 );
        glVertex3f( x, y, 0.5f );
   glEnd();
   
   glPointSize( 5 );
   glBegin( GL_POINTS );
        glColor4f( 1.0, 0.0, 0.0, 0.0 );
        glVertex3f( x, y, 0.5f );
   glEnd();
   
   }
};


/* This function read the current framebuffer back to memory, and also tries
 * to generate a png file of it if possible. */
void
readback() {
    utils::Image<float> I(window_width, window_height, 4 );
    utils::readFloatBuffer( *ons_fb, *ons_rt, 4, window_width, window_height, 
                            I.data_ );
    try {
        writeImageToFile( "readback.png", I );
    } catch (shallows::utils::not_supported &e)
    {
        // This is probably due to missing Magick++ library
        std::cout << e.what() << std::endl;
    }
};


/* A basic keyboard callback. */
void
keyboard(unsigned char key, int winx, int winy)
{
  switch(key) {
    case 'q':
      exit( EXIT_SUCCESS );
    case 'r':
      readback();
  }
}


/* A very basic reshape function. Notice how we do not set the projection matrix,
 * and just rely on the identity matrix. */
void
reshape( const int width, const int height ) {
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glViewport( 0, 0, width, height );
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
}


/* A callback for the above reshape function. Since we might want to reshape
 * both the off-screen buffer and the on-screen buffer, and they do not share 
 * the same size, it is nice to split them into two different functions. */
void
reshape_callback( const int width, const int height ) {
   window_width = width;
   window_height = height;
   reshape(window_width, window_height);
}


/* The gist of the setup for the algorithm happens here. */
void
initOpenGL( string filename) {
    
   /* Read an image from file, and create a texture of it. */
   shared_ptr<utils::Image<char> > I = utils::getCharImage( filename, 4 );    
   /* Notice that this will bind the texture, and set texture parameters that
    * are sane for GPGPU work. For other settings, texture parameters must
    * be set manually. */
   background = I->createTexture( GL_RGBA8 ); 
      
    /* Create a buffer for off-screen rendering. */
    shared_ptr<OffScreenBuffer> fb(new OffScreenBuffer(BUF_DIM, BUF_DIM));
    
    /* We use the newly created framebuffers to generate renderTextures in it. */
    for ( unsigned int i = 0; i < 3; ++i )
        rt[i] = fb->createRenderTexture2D();
    
    /* Then we give the rendertextures meaningful names. */    
    prev = rt[0];
    curr = rt[1];
    next = rt[2];
    
    /* Create buffer for on screen rendering, and a rendertarget. 
     * We use shared_ptr::reset to assign a new pointer to the globals ons_fb 
     * and ons_rt */
    ons_fb.reset(new OnScreenBuffer);
    ons_rt.reset(new OnScreenRenderTarget(GL_BACK_LEFT));

    /* Now we can create the shaders, attach them to a framebuffer and set
     * som uniforms. */
    waveshader.reset( new GLProgram );
    waveshader->readFile( "waveequation.glshader" );
    waveshader->setFrameBuffer( fb );
    waveshader->setParam2f( "dXY", 1.0/BUF_DIM, 1.0/BUF_DIM );

    rendershader.reset( new GLProgram);
    rendershader->useNormalizedTexCoords();
    rendershader->readFile( "waverenderer.glshader" );
    rendershader->setFrameBuffer(ons_fb);
    rendershader->setOutputTarget(0, ons_rt);
    rendershader->setParam2f("dXY", 1.0/BUF_DIM, 1.0/BUF_DIM );
    rendershader->setInputTexture( "backgroundMap", background );
    
    /* Now it is time to generate the initial conditions (IC). In this examples
     * we simply set it to be flat water, and we also set the start-1 timestep
     * to be flat. In a real simulation, more interesting conditions can be
     * generated by perturbing the pixels. */    
    vector<float>  initial_conditions(BUF_DIM*BUF_DIM*4, 0 );
    
    /* Here we create a texture out the IC. */
    shared_ptr<Texture2D> init_tex = utils::createFloatTexture2D(
        BUF_DIM, BUF_DIM, 4, &initial_conditions[0] );
    
    /* Initprog is just a basic-passthrough shader, which reads a texture
     * and passes the value along. */
    initProg.reset( new GLProgram );
    initProg->useNormalizedTexCoords();
    initProg->readFile("showtex2D.shader");
   
    /* We tell it to use the off-screen framebuffer, and use our newly created
     * texture as input. */
    initProg->setFrameBuffer(fb);
    initProg->setInputTexture("texture", init_tex);

    /* We need to align the viewport to the size of the framebuffer. */
    reshape(BUF_DIM, BUF_DIM);
    
    /* Here we set the output target, and actually run the shader in the off-
     * screen buffer. Since we need to fill two timesteps, we run it twice. */
    initProg->setOutputTarget(0, prev);
    initProg->run();
    initProg->setOutputTarget(0, curr);
    initProg->run();
   
    /* Finally, we set the static lighposition for the rendering pass. */
    glLightfv( GL_LIGHT0, GL_POSITION, light_position );    
    glEnable( GL_LIGHTING );
    glEnable( GL_LIGHT0 );
    
    /* We are ready to start the calculation, and start our benchmark timer. */
    rolex.restart();
}


/* This function just draws a nice string on the current framebuffer. Useful
 * for FPS counters and information. */
void 
renderBitmapString( float x, float y, float z, const string s) {  
  glRasterPos3f(x, y, z);
  for ( string::const_iterator it = s.begin(); it != s.end(); ++it ) {
    glutBitmapCharacter(GLUT_BITMAP_9_BY_15, *it);
  }
}


/* The main calculation loop of the algorithm happens here. Actually, two 
 * renderpasses are invoked to each call of display. First we calculate an 
 * iteration of the wave equation (this happens off-screen), then we do a 
 * rendering pass of the data. */
void
display() {
    
    /* Reshape the viewport to fit the computational grid. */
    reshape( BUF_DIM, BUF_DIM );
    /* Point to the two latest timesteps, and the output target. */
    waveshader->setInputTexture( "texCurrent", curr->getTexture() );
    waveshader->setInputTexture( "texLast", prev->getTexture() );
    waveshader->setOutputTarget( 0, next );
    
    /* Activate the shader, and do the actual rendering. Since the renderpass
     * do more than just the basic GPGPU quad, we call a function we have 
     * written ourselves. */
    waveshader->activate();
    wavepass();
            
    /* Now it is time to render the results. First we must adjust the viewport
     * to the size of the visible window. */
    reshape( window_width, window_height );
    glEnable( GL_LIGHTING );
    /* Point the rendershader to use the output of the above renderpass as 
     * input. Notice how the texture is shared between different frame buffer
     * objects. */
    rendershader->setInputTexture( "waveMap", next->getTexture() );
    
    /* Finally run it, just using the basic [-1,1]x[-1,1] rendering grid. */
    rendershader->run();
    
    /* After the rendering we must rename the buffers, to be ready for the next
     * pass */
    swap( next, prev );
    swap( prev, curr );
    
    /* Here follows just functionality to draw an FPS counter */
    static unsigned int frames = 0;
    static float fps;
    static ostringstream os;
    os.flags( ios_base::right | ios_base::fixed  );
    os.precision( 1 );
    static string fpsstring;
    
    frames++;
    if ( rolex.elapsed() > 1.0 ) {
                fps = static_cast<double>( frames ) / rolex.elapsed();
                frames = 0;
                rolex.restart();
                        
                os << "FPS: " << fps;
                fpsstring = os.str();
                os.str("");
    }           
        
    setFixedFunction();
    glDisable( GL_LIGHTING );
    glColor3f( 1.0f, 1.0f, 1.0f);
    renderBitmapString( -.95, 0.95, 1.0, fpsstring );
    
    /* Finally make sure we run this function as often as possible. */    
    glutSwapBuffers();
    glutPostRedisplay();
}


/* Start the program */
int 
main( int argc, char *argv[] ) {
   
   glutInit(&argc, argv);
   glutInitWindowPosition(100, 100);
   glutInitWindowSize(512, 512);
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
   int windowID = glutCreateWindow("Wave Equation");
   glutSetWindow(windowID);    
   
   glutReshapeFunc(reshape_callback);
   glutKeyboardFunc(keyboard);
   glutDisplayFunc(display);
   
   reshape_callback( 512, 512 );
   try {
      string filename;
      if ( argc == 2 )
          filename = argv[1];
      else 
          filename = "../../test/images/lena-color-512x512.tiff";
      initOpenGL( filename );
   } catch ( const shallows::compile_error &e)
   {
      std::cout << "Compile error:\n " << e.log() << std::endl;
      exit( EXIT_FAILURE );
   } catch (std::exception &e)
   {
      std::cout << e.what() << std::endl;
      exit( EXIT_FAILURE );
   }
   
   glutMainLoop();
   
   exit( EXIT_SUCCESS );   
}

Generated on Mon Sep 26 15:35:46 2005 for shallows by  doxygen 1.4.4