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 
00039 namespace sf
00040 {
00044 PostFX::PostFX() :
00045 myShaderProgram(0)
00046 {
00047 
00048 }
00049 
00050 
00054 PostFX::PostFX(const std::string& Filename) :
00055 myShaderProgram(0)
00056 {
00057     LoadFromFile(Filename);
00058 }
00059 
00060 
00064 PostFX::PostFX(const PostFX& Copy) :
00065 Drawable        (Copy),
00066 VideoResource   (Copy),
00067 myShaderProgram (0),
00068 mySamplers      (Copy.mySamplers),
00069 myFragmentShader(Copy.myFragmentShader),
00070 myFrameBuffer   (Copy.myFrameBuffer)
00071 {
00072     // Create the shaders and the program
00073     if (Copy.myShaderProgram)
00074         CreateProgram();
00075 }
00076 
00077 
00081 PostFX::~PostFX()
00082 {
00083     DestroyVideoResources();
00084 }
00085 
00086 
00090 bool PostFX::LoadFromFile(const std::string& Filename)
00091 {
00092     // Load fragment shader source from file
00093     myFragmentShader = PreprocessEffect(Filename);
00094 
00095     // Create the shaders and the program
00096     CreateProgram();
00097 
00098     return myShaderProgram != 0;
00099 }
00100 
00101 
00105 void PostFX::SetParameter(const std::string& Name, float X)
00106 {
00107     if (myShaderProgram)
00108     {
00109         // Enable program
00110         GLCheck(glUseProgramObjectARB(myShaderProgram));
00111 
00112         // Get parameter location and assign it new values
00113         GLint Location = glGetUniformLocationARB(myShaderProgram, Name.c_str());
00114         if (Location != -1)
00115             GLCheck(glUniform1fARB(Location, X));
00116         else
00117             std::cerr << "Parameter \"" << Name << "\" not found in effect" << std::endl;
00118 
00119         // Disable program
00120         GLCheck(glUseProgramObjectARB(0));
00121     }
00122 }
00123 
00124 
00128 void PostFX::SetParameter(const std::string& Name, float X, float Y)
00129 {
00130     if (myShaderProgram)
00131     {
00132         // Enable program
00133         GLCheck(glUseProgramObjectARB(myShaderProgram));
00134 
00135         // Get parameter location and assign it new values
00136         GLint Location = glGetUniformLocationARB(myShaderProgram, Name.c_str());
00137         if (Location != -1)
00138             GLCheck(glUniform2fARB(Location, X, Y));
00139         else
00140             std::cerr << "Parameter \"" << Name << "\" not found in effect" << std::endl;
00141 
00142         // Disable program
00143         GLCheck(glUseProgramObjectARB(0));
00144     }
00145 }
00146 
00147 
00151 void PostFX::SetParameter(const std::string& Name, float X, float Y, float Z)
00152 {
00153     if (myShaderProgram)
00154     {
00155         // Enable program
00156         GLCheck(glUseProgramObjectARB(myShaderProgram));
00157 
00158         // Get parameter location and assign it new values
00159         GLint Location = glGetUniformLocationARB(myShaderProgram, Name.c_str());
00160         if (Location != -1)
00161             GLCheck(glUniform3fARB(Location, X, Y, Z));
00162         else
00163             std::cerr << "Parameter \"" << Name << "\" not found in effect" << std::endl;
00164 
00165         // Disable program
00166         GLCheck(glUseProgramObjectARB(0));
00167     }
00168 }
00169 
00170 
00174 void PostFX::SetParameter(const std::string& Name, float X, float Y, float Z, float W)
00175 {
00176     if (myShaderProgram)
00177     {
00178         // Enable program
00179         GLCheck(glUseProgramObjectARB(myShaderProgram));
00180 
00181         // Get parameter location and assign it new values
00182         GLint Location = glGetUniformLocationARB(myShaderProgram, Name.c_str());
00183         if (Location != -1)
00184             GLCheck(glUniform4fARB(Location, X, Y, Z, W));
00185         else
00186             std::cerr << "Parameter \"" << Name << "\" not found in effect" << std::endl;
00187 
00188         // Disable program
00189         GLCheck(glUseProgramObjectARB(0));
00190     }
00191 }
00192 
00193 
00197 void PostFX::SetTexture(const std::string& Name, Image* Texture)
00198 {
00199     // Just store the texture for later use
00200     mySamplers[Name] = Texture ? Texture : &myFrameBuffer;
00201 }
00202 
00203 
00207 PostFX& PostFX::operator =(const PostFX& Other)
00208 {
00209     PostFX Temp(Other);
00210 
00211     std::swap(myShaderProgram,  Temp.myShaderProgram);
00212     std::swap(mySamplers,       Temp.mySamplers);
00213     std::swap(myFragmentShader, Temp.myFragmentShader);
00214     std::swap(myFrameBuffer,    Temp.myFrameBuffer);
00215 
00216     return *this;
00217 }
00218 
00219 
00223 bool PostFX::CanUsePostFX()
00224 {
00225     return OpenGLCaps::CheckExtension("GL_ARB_shading_language_100") &&
00226            OpenGLCaps::CheckExtension("GL_ARB_shader_objects")       &&
00227            OpenGLCaps::CheckExtension("GL_ARB_vertex_shader")        &&
00228            OpenGLCaps::CheckExtension("GL_ARB_fragment_shader");
00229 }
00230 
00231 
00235 void PostFX::Render(RenderWindow& Window)
00236 {
00237     // Check that we have a valid program
00238     if (!myShaderProgram)
00239         return;
00240 
00241     // If window dimensions have changed, recreate the frame buffer texture
00242     if ((Window.GetWidth() != myFrameBuffer.GetWidth()) || (Window.GetHeight() != myFrameBuffer.GetHeight()))
00243     {
00244         myFrameBuffer.Resize(Window.GetWidth(), Window.GetHeight());
00245         myFrameBuffer.SetSmooth(false);
00246         myFrameBuffer.SetRepeat(false);
00247     }
00248 
00249     // Copy the current framebuffer to our frame buffer texture
00250     myFrameBuffer.Bind();
00251     GLCheck(glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, myFrameBuffer.GetWidth(), myFrameBuffer.GetHeight()));
00252 
00253     // Enable program
00254     GLCheck(glUseProgramObjectARB(myShaderProgram));
00255 
00256     // Bind textures
00257     int Unit = 0;
00258     for (std::map<std::string, Image*>::iterator i = mySamplers.begin(); i != mySamplers.end(); ++i)
00259     {
00260         // Check that current texture unit is available
00261         if (Unit >= OpenGLCaps::GetMaxTextureUnits())
00262         {
00263             // Error : we have reached maximum texture units count
00264             std::cerr << "Impossible to use texture \"" << i->first << "\" for post-effect : all available texture units are used" << std::endl;
00265             break;
00266         }
00267 
00268         // Get the texture location in the effect
00269         int Location = glGetUniformLocationARB(myShaderProgram, i->first.c_str());
00270         if (Location != -1)
00271         {
00272             // Texture found : bind it and associate it to current texture unit
00273             GLCheck(glUniform1iARB(Location, Unit));
00274             GLCheck(glActiveTextureARB(GL_TEXTURE0_ARB + Unit));
00275             i->second->Bind();
00276             Unit++;
00277         }
00278         else
00279         {
00280             // Error : texture name not found in effect
00281             std::cerr << "Texture \"" << i->first << "\" not found in effect" << std::endl;
00282         }
00283     }
00284 
00285     // Compute the texture coordinates (in case the texture is larger than the screen)
00286     IntRect   FrameBufferRect(0, 0, myFrameBuffer.GetWidth(), myFrameBuffer.GetHeight());
00287     FloatRect TexCoords = myFrameBuffer.GetTexCoords(FrameBufferRect);
00288 
00289     // Render a fullscreen quad using the effect on our framebuffer
00290     FloatRect Screen = Window.GetViewRect();
00291     glBegin(GL_QUADS);
00292         glTexCoord2f(TexCoords.Left,  TexCoords.Top);    glVertex2f(Screen.Left,  Screen.Bottom);   
00293         glTexCoord2f(TexCoords.Left,  TexCoords.Bottom); glVertex2f(Screen.Left,  Screen.Top);
00294         glTexCoord2f(TexCoords.Right, TexCoords.Bottom); glVertex2f(Screen.Right, Screen.Top);
00295         glTexCoord2f(TexCoords.Right, TexCoords.Top);    glVertex2f(Screen.Right, Screen.Bottom);   
00296     glEnd();
00297 
00298     // Disable program
00299     GLCheck(glUseProgramObjectARB(0));
00300 
00301     // Disable texture units
00302     for (int i = 0; i < Unit; ++i)
00303     {
00304         GLCheck(glActiveTextureARB(GL_TEXTURE0_ARB + i));
00305         GLCheck(glBindTexture(GL_TEXTURE_2D, 0));
00306     }
00307     GLCheck(glActiveTextureARB(GL_TEXTURE0_ARB));
00308 }
00309 
00310 
00315 std::string PostFX::PreprocessEffect(const std::string& Filename)
00316 {
00317     // Open file to process
00318     std::ifstream File(Filename.c_str());
00319     if (!File)
00320     {
00321         std::cerr << "Failed to open effect file \"" << Filename << "\"" << std::endl;
00322         return "";
00323     }
00324 
00325     // Initialize output string
00326     std::set<std::string> myTextures;
00327     std::string Out = "";
00328 
00329     // Variable declarations
00330     std::string Line;
00331     while (std::getline(File, Line) && (Line.substr(0, 6) != "effect"))
00332     {
00333         // Remove the ending '\r', if any
00334         if (!Line.empty() && (Line[Line.size() - 1] == '\r'))
00335             Line.erase(Line.size() - 1);
00336 
00337         // Skip empty lines
00338         if (Line == "")
00339             continue;
00340 
00341         // Extract variables type and name and convert them
00342         std::string Type, Name;
00343         std::istringstream iss(Line);
00344         if (!(iss >> Type >> Name))
00345         {
00346             std::cerr << "Effect \"" << Filename << "\" : invalid declaration (should be \"[type][name]\")" << std::endl
00347                       << "> " << Line << std::endl;
00348             return "";
00349         }
00350 
00351         if (Type == "texture")
00352         {
00353             // Textures need some checking and conversion
00354             if (myTextures.find(Name) != myTextures.end())
00355             {
00356                 std::cerr << "Effect \"" << Filename << "\" : texture \"" << Name << "\" already exists" << std::endl;
00357                 return "";
00358             }
00359 
00360             Out += "uniform sampler2D " + Name + ";\n";
00361             myTextures.insert(Name);
00362         }
00363         else
00364         {
00365             // Other types are just copied to output with "uniform" prefix
00366             Out += "uniform " + Type + " " + Name + ";\n";
00367         }
00368     }
00369 
00370     // Effect code
00371     Out += "void main()\n";
00372     while (std::getline(File, Line))
00373     {
00374         // Replace any texture lookup "T(" by "texture2D(T, "
00375         for (std::set<std::string>::const_iterator i = myTextures.begin(); i != myTextures.end(); ++i)
00376         {
00377             std::string::size_type Pos = Line.find(*i);
00378             if (Pos != std::string::npos)
00379                 Line.replace(Pos, i->size() + 1, "texture2D(" + *i + ", ");
00380         }
00381 
00382         // Replace "_in" by "gl_TexCoord[0].xy"
00383         for (std::string::size_type Pos = Line.find("_in"); Pos != std::string::npos; Pos = Line.find("_in"))
00384             Line.replace(Pos, 3, "gl_TexCoord[0].xy");
00385 
00386         // Replace "_out" by "gl_FragColor"
00387         for (std::string::size_type Pos = Line.find("_out"); Pos != std::string::npos; Pos = Line.find("_out"))
00388             Line.replace(Pos, 4, "gl_FragColor");
00389 
00390         // Write modified line to output string
00391         Out += Line + "\n";
00392     }
00393 
00394     return Out;
00395 }
00396 
00397 
00401 bool PostFX::CreateAndAttachShader(const std::string& Source, unsigned int ShaderType)
00402 {
00403     // Create shader object
00404     GLhandleARB Shader = glCreateShaderObjectARB(ShaderType);
00405 
00406     // Load source
00407     int Length = static_cast<int>(Source.size());
00408     const char* Data = Source.data();
00409     GLCheck(glShaderSourceARB(Shader, 1, &Data, (const GLint*)&Length));
00410 
00411     // Compile shader
00412     GLCheck(glCompileShaderARB(Shader));
00413 
00414     // Get compile log
00415     int Success;
00416     GLCheck(glGetObjectParameterivARB(Shader, GL_OBJECT_COMPILE_STATUS_ARB, (GLint*)&Success));
00417     if (Success == GL_FALSE)
00418     {
00419         // Oops... compile errors !
00420         char CompileLog[1024];
00421         GLCheck(glGetInfoLogARB(Shader, sizeof(CompileLog), 0, CompileLog));
00422         std::cerr << "Failed to compile effect :" << std::endl
00423                   << CompileLog << std::endl;
00424         GLCheck(glDeleteObjectARB(Shader));
00425         return false;
00426     }
00427 
00428     // Attach the shader to the program
00429     GLCheck(glAttachObjectARB(myShaderProgram, Shader));
00430 
00431     // We can now delete our shader
00432     GLCheck(glDeleteObjectARB(Shader));
00433 
00434     return true;
00435 }
00436 
00437 
00441 void PostFX::CreateProgram()
00442 {
00443     // Check that we can use post-FX !
00444     if (!CanUsePostFX())
00445     {
00446         std::cerr << "Failed to create a sfPostFX : your system doesn't support effects" << std::endl;
00447         return;
00448     }
00449 
00450     // Destroy effect program if it was already created
00451     if (myShaderProgram)
00452         GLCheck(glDeleteObjectARB(myShaderProgram));
00453 
00454     // Create the program
00455     myShaderProgram = glCreateProgramObjectARB();
00456 
00457     // Load vertex shader source (we provide it directly as it doesn't have to change)
00458     static const std::string VertexShader =
00459         "void main()"
00460         "{"
00461         "    gl_TexCoord[0] = gl_MultiTexCoord0;"
00462         "    gl_Position = ftransform();"
00463         "}";
00464 
00465     // Create vertex shader
00466     if (!CreateAndAttachShader(VertexShader, GL_VERTEX_SHADER_ARB))
00467     {
00468         GLCheck(glDeleteObjectARB(myShaderProgram));
00469         myShaderProgram = 0;
00470         return;
00471     }
00472 
00473     // Create fragment shader
00474     if (!CreateAndAttachShader(myFragmentShader, GL_FRAGMENT_SHADER_ARB))
00475     {
00476         GLCheck(glDeleteObjectARB(myShaderProgram));
00477         myShaderProgram = 0;
00478         return;
00479     }
00480 
00481     // Link the program
00482     GLCheck(glLinkProgramARB(myShaderProgram));
00483 
00484     // Get link log
00485     int Success;
00486     GLCheck(glGetObjectParameterivARB(myShaderProgram, GL_OBJECT_LINK_STATUS_ARB, (GLint*)&Success));
00487     if (Success == GL_FALSE)
00488     {
00489         // Oops... link errors !
00490         char LinkLog[1024];
00491         GLCheck(glGetInfoLogARB(myShaderProgram, sizeof(LinkLog), 0, LinkLog));
00492         std::cerr << "Failed to link effect :" << std::endl
00493                   << LinkLog << std::endl;
00494         GLCheck(glDeleteObjectARB(myShaderProgram));
00495         myShaderProgram = 0;
00496     }
00497 }
00498 
00499 
00503 void PostFX::DestroyVideoResources()
00504 {
00505     // Destroy effect program
00506     if (myShaderProgram)
00507     {
00508         GLCheck(glDeleteObjectARB(myShaderProgram));
00509         myShaderProgram = 0;
00510     }
00511 }
00512 
00513 } // namespace sf