PostFX.cpp

00001 
00002 //
00003 // SFML - Simple and Fast Multimedia Library
00004 // Copyright (C) 2007 Laurent Gomila (laurent.gom@gmail.com)
00005 //
00006 // This software is provided 'as-is', without any express or implied warranty.
00007 // In no event will the authors be held liable for any damages arising from the use of this software.
00008 //
00009 // Permission is granted to anyone to use this software for any purpose,
00010 // including commercial applications, and to alter it and redistribute it freely,
00011 // subject to the following restrictions:
00012 //
00013 // 1. The origin of this software must not be misrepresented;
00014 //    you must not claim that you wrote the original software.
00015 //    If you use this software in a product, an acknowledgment
00016 //    in the product documentation would be appreciated but is not required.
00017 //
00018 // 2. Altered source versions must be plainly marked as such,
00019 //    and must not be misrepresented as being the original software.
00020 //
00021 // 3. This notice may not be removed or altered from any source distribution.
00022 //
00024 
00026 // Headers
00028 #include <SFML/Graphics/PostFX.hpp>
00029 #include <SFML/Graphics/GraphicsDevice.hpp>
00030 #include <SFML/Graphics/OpenGL.hpp>
00031 #include <SFML/Graphics/RenderWindow.hpp>
00032 #include <SFML/Window/OpenGLCaps.hpp>
00033 #include <fstream>
00034 #include <iostream>
00035 #include <set>
00036 #include <sstream>
00037 
00038 
00042 sfPostFX::sfPostFX() :
00043 myShaderProgram(0)
00044 {
00045 
00046 }
00047 
00048 
00052 sfPostFX::sfPostFX(const std::string& Filename) :
00053 myShaderProgram(0)
00054 {
00055     LoadFromFile(Filename);
00056 }
00057 
00058 
00062 sfPostFX::sfPostFX(const sfPostFX& Copy) :
00063 myShaderProgram (0),
00064 mySamplers      (Copy.mySamplers),
00065 myFragmentShader(Copy.myFragmentShader),
00066 myFrameBuffer   (Copy.myFrameBuffer)
00067 {
00068     // Create the shaders and the program
00069     if (Copy.myShaderProgram)
00070         CreateProgram();
00071 }
00072 
00073 
00077 sfPostFX::~sfPostFX()
00078 {
00079     DestroyVideoResources();
00080 }
00081 
00082 
00086 bool sfPostFX::LoadFromFile(const std::string& Filename)
00087 {
00088     // Load fragment shader source from file
00089     myFragmentShader = PreprocessEffect(Filename);
00090 
00091     // Create the shaders and the program
00092     CreateProgram();
00093 
00094     return myShaderProgram != 0;
00095 }
00096 
00097 
00101 void sfPostFX::SetParameter(const std::string& Name, float X)
00102 {
00103     if (myShaderProgram)
00104     {
00105         // Enable program
00106         sfGLCheck(glUseProgramObjectARB(myShaderProgram));
00107 
00108         // Get parameter location and assign it new values
00109         GLint Location = glGetUniformLocationARB(myShaderProgram, Name.c_str());
00110         if (Location != -1)
00111             sfGLCheck(glUniform1fARB(Location, X));
00112         else
00113             std::cerr << "Parameter \"" << Name << "\" not found in effect" << std::endl;
00114 
00115         // Disable program
00116         sfGLCheck(glUseProgramObjectARB(0));
00117     }
00118 }
00119 
00120 
00124 void sfPostFX::SetParameter(const std::string& Name, float X, float Y)
00125 {
00126     if (myShaderProgram)
00127     {
00128         // Enable program
00129         sfGLCheck(glUseProgramObjectARB(myShaderProgram));
00130 
00131         // Get parameter location and assign it new values
00132         GLint Location = glGetUniformLocationARB(myShaderProgram, Name.c_str());
00133         if (Location != -1)
00134             sfGLCheck(glUniform2fARB(Location, X, Y));
00135         else
00136             std::cerr << "Parameter \"" << Name << "\" not found in effect" << std::endl;
00137 
00138         // Disable program
00139         sfGLCheck(glUseProgramObjectARB(0));
00140     }
00141 }
00142 
00143 
00147 void sfPostFX::SetParameter(const std::string& Name, float X, float Y, float Z)
00148 {
00149     if (myShaderProgram)
00150     {
00151         // Enable program
00152         sfGLCheck(glUseProgramObjectARB(myShaderProgram));
00153 
00154         // Get parameter location and assign it new values
00155         GLint Location = glGetUniformLocationARB(myShaderProgram, Name.c_str());
00156         if (Location != -1)
00157             sfGLCheck(glUniform3fARB(Location, X, Y, Z));
00158         else
00159             std::cerr << "Parameter \"" << Name << "\" not found in effect" << std::endl;
00160 
00161         // Disable program
00162         sfGLCheck(glUseProgramObjectARB(0));
00163     }
00164 }
00165 
00166 
00170 void sfPostFX::SetParameter(const std::string& Name, float X, float Y, float Z, float W)
00171 {
00172     if (myShaderProgram)
00173     {
00174         // Enable program
00175         sfGLCheck(glUseProgramObjectARB(myShaderProgram));
00176 
00177         // Get parameter location and assign it new values
00178         GLint Location = glGetUniformLocationARB(myShaderProgram, Name.c_str());
00179         if (Location != -1)
00180             sfGLCheck(glUniform4fARB(Location, X, Y, Z, W));
00181         else
00182             std::cerr << "Parameter \"" << Name << "\" not found in effect" << std::endl;
00183 
00184         // Disable program
00185         sfGLCheck(glUseProgramObjectARB(0));
00186     }
00187 }
00188 
00189 
00193 void sfPostFX::SetTexture(const std::string& Name, sfImage* Image)
00194 {
00195     // Just store the texture for later use
00196     mySamplers[Name] = Image ? Image : &myFrameBuffer;
00197 }
00198 
00199 
00203 sfPostFX& sfPostFX::operator =(const sfPostFX& Other)
00204 {
00205     sfPostFX Temp(Other);
00206 
00207     std::swap(myShaderProgram,  Temp.myShaderProgram);
00208     std::swap(mySamplers,       Temp.mySamplers);
00209     std::swap(myFragmentShader, Temp.myFragmentShader);
00210     std::swap(myFrameBuffer,    Temp.myFrameBuffer);
00211 
00212     return *this;
00213 }
00214 
00215 
00219 bool sfPostFX::CanUsePostFX()
00220 {
00221     return sfOpenGLCaps::CheckExtension("GL_ARB_shading_language_100") &&
00222            sfOpenGLCaps::CheckExtension("GL_ARB_shader_objects")       &&
00223            sfOpenGLCaps::CheckExtension("GL_ARB_vertex_shader")        &&
00224            sfOpenGLCaps::CheckExtension("GL_ARB_fragment_shader");
00225 }
00226 
00227 
00231 void sfPostFX::Render(sfRenderWindow& Window)
00232 {
00233     // Check that we have a valid program
00234     if (!myShaderProgram)
00235         return;
00236 
00237     // If window dimensions have changed, recreate the frame buffer texture
00238     if ((Window.GetWidth() != myFrameBuffer.GetWidth()) || (Window.GetHeight() != myFrameBuffer.GetHeight()))
00239     {
00240         myFrameBuffer.Resize(Window.GetWidth(), Window.GetHeight());
00241         myFrameBuffer.SetSmooth(false);
00242         myFrameBuffer.SetRepeat(false);
00243     }
00244 
00245     // Copy the current framebuffer to our frame buffer texture
00246     myFrameBuffer.Bind();
00247     sfGLCheck(glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, myFrameBuffer.GetWidth(), myFrameBuffer.GetHeight()));
00248 
00249     // Enable program
00250     sfGLCheck(glUseProgramObjectARB(myShaderProgram));
00251 
00252     // Bind textures
00253     int Unit = 0;
00254     for (std::map<std::string, sfImage*>::iterator i = mySamplers.begin(); i != mySamplers.end(); ++i)
00255     {
00256         // Check that current texture unit is available
00257         if (Unit >= sfOpenGLCaps::GetMaxTextureUnits())
00258         {
00259             // Error : we have reached maximum texture units count
00260             std::cerr << "Impossible to use texture \"" << i->first << "\" for post-effect : all available texture units are used" << std::endl;
00261             break;
00262         }
00263 
00264         // Get the texture location in the effect
00265         int Location = glGetUniformLocationARB(myShaderProgram, i->first.c_str());
00266         if (Location != -1)
00267         {
00268             // Texture found : bind it and associate it to current texture unit
00269             sfGLCheck(glUniform1iARB(Location, Unit));
00270             sfGLCheck(glActiveTextureARB(GL_TEXTURE0_ARB + Unit));
00271             i->second->Bind();
00272             Unit++;
00273         }
00274         else
00275         {
00276             // Error : texture name not found in effect
00277             std::cerr << "Texture \"" << i->first << "\" not found in effect" << std::endl;
00278         }
00279     }
00280 
00281     // Compute the texture coordinates (in case the texture is larger than the screen)
00282     sfIntRect   FrameBufferRect(0, 0, myFrameBuffer.GetWidth(), myFrameBuffer.GetHeight());
00283     sfFloatRect TexCoords = myFrameBuffer.GetTexCoords(FrameBufferRect);
00284 
00285     // Render a fullscreen quad using the effect on our framebuffer
00286     sfFloatRect Screen = Window.GetViewRect();
00287     glBegin(GL_QUADS);
00288         glTexCoord2f(TexCoords.Left,  TexCoords.Top);    glVertex2f(Screen.Left,  Screen.Bottom);   
00289         glTexCoord2f(TexCoords.Left,  TexCoords.Bottom); glVertex2f(Screen.Left,  Screen.Top);
00290         glTexCoord2f(TexCoords.Right, TexCoords.Bottom); glVertex2f(Screen.Right, Screen.Top);
00291         glTexCoord2f(TexCoords.Right, TexCoords.Top);    glVertex2f(Screen.Right, Screen.Bottom);   
00292     glEnd();
00293 
00294     // Disable program
00295     sfGLCheck(glUseProgramObjectARB(0));
00296 
00297     // Disable texture units
00298     for (int i = 0; i < Unit; ++i)
00299     {
00300         sfGLCheck(glActiveTextureARB(GL_TEXTURE0_ARB + i));
00301         sfGLCheck(glBindTexture(GL_TEXTURE_2D, 0));
00302     }
00303     sfGLCheck(glActiveTextureARB(GL_TEXTURE0_ARB));
00304 }
00305 
00306 
00311 std::string sfPostFX::PreprocessEffect(const std::string& Filename)
00312 {
00313     // Open file to process
00314     std::ifstream File(Filename.c_str());
00315     if (!File)
00316     {
00317         std::cerr << "Failed to open effect file \"" << Filename << "\"" << std::endl;
00318         return "";
00319     }
00320 
00321     // Initialize output string
00322     std::set<std::string> myTextures;
00323     std::string Out = "";
00324 
00325     // Variable declarations
00326     std::string Line;
00327     while (std::getline(File, Line) && (Line.substr(0, 6) != "effect"))
00328     {
00329         // Remove the ending '\r', if any
00330         if (!Line.empty() && (Line[Line.size() - 1] == '\r'))
00331             Line.erase(Line.size() - 1);
00332 
00333         // Skip empty lines
00334         if (Line == "")
00335             continue;
00336 
00337         // Extract variables type and name and convert them
00338         std::string Type, Name;
00339         std::istringstream iss(Line);
00340         if (!(iss >> Type >> Name))
00341         {
00342             std::cerr << "Effect \"" << Filename << "\" : invalid declaration (should be \"[type][name]\")" << std::endl
00343                       << "> " << Line << std::endl;
00344             return "";
00345         }
00346 
00347         if (Type == "texture")
00348         {
00349             // Textures need some checking and conversion
00350             if (myTextures.find(Name) != myTextures.end())
00351             {
00352                 std::cerr << "Effect \"" << Filename << "\" : texture \"" << Name << "\" already exists" << std::endl;
00353                 return "";
00354             }
00355 
00356             Out += "uniform sampler2D " + Name + ";\n";
00357             myTextures.insert(Name);
00358         }
00359         else
00360         {
00361             // Other types are just copied to output with "uniform" prefix
00362             Out += "uniform " + Type + " " + Name + ";\n";
00363         }
00364     }
00365 
00366     // Effect code
00367     Out += "void main()\n";
00368     while (std::getline(File, Line))
00369     {
00370         // Replace any texture lookup "T(" by "texture2D(T, "
00371         for (std::set<std::string>::const_iterator i = myTextures.begin(); i != myTextures.end(); ++i)
00372         {
00373             std::string::size_type Pos = Line.find(*i);
00374             if (Pos != std::string::npos)
00375                 Line.replace(Pos, i->size() + 1, "texture2D(" + *i + ", ");
00376         }
00377 
00378         // Replace "_in" by "gl_TexCoord[0].xy"
00379         for (std::string::size_type Pos = Line.find("_in"); Pos != std::string::npos; Pos = Line.find("_in"))
00380             Line.replace(Pos, 3, "gl_TexCoord[0].xy");
00381 
00382         // Replace "_out" by "gl_FragColor"
00383         for (std::string::size_type Pos = Line.find("_out"); Pos != std::string::npos; Pos = Line.find("_out"))
00384             Line.replace(Pos, 4, "gl_FragColor");
00385 
00386         // Write modified line to output string
00387         Out += Line + "\n";
00388     }
00389 
00390     return Out;
00391 }
00392 
00393 
00397 bool sfPostFX::CreateAndAttachShader(const std::string& Source, unsigned int ShaderType)
00398 {
00399     // Create shader object
00400     GLhandleARB Shader = glCreateShaderObjectARB(ShaderType);
00401 
00402     // Load source
00403     int Length = static_cast<int>(Source.size());
00404     const char* Data = Source.data();
00405     sfGLCheck(glShaderSourceARB(Shader, 1, &Data, &Length));
00406 
00407     // Compile shader
00408     sfGLCheck(glCompileShaderARB(Shader));
00409 
00410     // Get compile log
00411     int Success;
00412     sfGLCheck(glGetObjectParameterivARB(Shader, GL_OBJECT_COMPILE_STATUS_ARB, &Success));
00413     if (Success == GL_FALSE)
00414     {
00415         // Oops... compile errors !
00416         char CompileLog[1024];
00417         sfGLCheck(glGetInfoLogARB(Shader, sizeof(CompileLog), 0, CompileLog));
00418         std::cerr << "Failed to compile effect :" << std::endl
00419                   << CompileLog << std::endl;
00420         sfGLCheck(glDeleteObjectARB(Shader));
00421         return false;
00422     }
00423 
00424     // Attach the shader to the program
00425     sfGLCheck(glAttachObjectARB(myShaderProgram, Shader));
00426 
00427     // We can now delete our shader
00428     sfGLCheck(glDeleteObjectARB(Shader));
00429 
00430     return true;
00431 }
00432 
00433 
00437 void sfPostFX::CreateProgram()
00438 {
00439     // Check that we can use post-FX !
00440     if (!CanUsePostFX())
00441     {
00442         std::cerr << "Failed to create a sfPostFX : your system doesn't support effects" << std::endl;
00443         return;
00444     }
00445 
00446     // Destroy effect program if it was already created
00447     if (myShaderProgram)
00448         sfGLCheck(glDeleteObjectARB(myShaderProgram));
00449 
00450     // Create the program
00451     myShaderProgram = glCreateProgramObjectARB();
00452 
00453     // Load vertex shader source (we provide it directly as it doesn't have to change)
00454     static const std::string VertexShader =
00455         "void main()"
00456         "{"
00457         "    gl_TexCoord[0] = gl_MultiTexCoord0;"
00458         "    gl_Position = ftransform();"
00459         "}";
00460 
00461     // Create vertex shader
00462     if (!CreateAndAttachShader(VertexShader, GL_VERTEX_SHADER_ARB))
00463     {
00464         sfGLCheck(glDeleteObjectARB(myShaderProgram));
00465         myShaderProgram = 0;
00466         return;
00467     }
00468 
00469     // Create fragment shader
00470     if (!CreateAndAttachShader(myFragmentShader, GL_FRAGMENT_SHADER_ARB))
00471     {
00472         sfGLCheck(glDeleteObjectARB(myShaderProgram));
00473         myShaderProgram = 0;
00474         return;
00475     }
00476 
00477     // Link the program
00478     sfGLCheck(glLinkProgramARB(myShaderProgram));
00479 
00480     // Get link log
00481     int Success;
00482     sfGLCheck(glGetObjectParameterivARB(myShaderProgram, GL_OBJECT_LINK_STATUS_ARB, &Success));
00483     if (Success == GL_FALSE)
00484     {
00485         // Oops... link errors !
00486         char LinkLog[1024];
00487         sfGLCheck(glGetInfoLogARB(myShaderProgram, sizeof(LinkLog), 0, LinkLog));
00488         std::cerr << "Failed to link effect :" << std::endl
00489                   << LinkLog << std::endl;
00490         sfGLCheck(glDeleteObjectARB(myShaderProgram));
00491         myShaderProgram = 0;
00492     }
00493 }
00494 
00495 
00499 void sfPostFX::DestroyVideoResources()
00500 {
00501     // Destroy effect program
00502     if (myShaderProgram)
00503     {
00504         sfGLCheck(glDeleteObjectARB(myShaderProgram));
00505         myShaderProgram = 0;
00506     }
00507 }