/********************************************************************** * * 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 ); }
1.4.4