PostFX.cpp
1 //
3 // SFML - Simple and Fast Multimedia Library
4 // Copyright (C) 2007-2009 Laurent Gomila (laurent.gom@gmail.com)
5 //
6 // This software is provided 'as-is', without any express or implied warranty.
7 // In no event will the authors be held liable for any damages arising from the use of this software.
8 //
9 // Permission is granted to anyone to use this software for any purpose,
10 // including commercial applications, and to alter it and redistribute it freely,
11 // subject to the following restrictions:
12 //
13 // 1. The origin of this software must not be misrepresented;
14 // you must not claim that you wrote the original software.
15 // If you use this software in a product, an acknowledgment
16 // in the product documentation would be appreciated but is not required.
17 //
18 // 2. Altered source versions must be plainly marked as such,
19 // and must not be misrepresented as being the original software.
20 //
21 // 3. This notice may not be removed or altered from any source distribution.
22 //
24 
25 
27 // Headers
29 #include <SFML/Graphics/PostFX.hpp>
30 #include <SFML/Graphics/RenderWindow.hpp>
31 #include <SFML/Graphics/GraphicsContext.hpp>
32 #include <fstream>
33 #include <iostream>
34 #include <set>
35 #include <sstream>
36 
37 
38 namespace sf
39 {
44 myShaderProgram(0)
45 {
46  // No filtering on frame buffer
47  myFrameBuffer.SetSmooth(false);
48 }
49 
50 
54 PostFX::PostFX(const PostFX& Copy) :
55 Drawable (Copy),
56 myShaderProgram (0),
57 myTextures (Copy.myTextures),
58 myFragmentShader(Copy.myFragmentShader),
59 myFrameBuffer (Copy.myFrameBuffer)
60 {
61  // No filtering on frame buffer
62  myFrameBuffer.SetSmooth(false);
63 
64  // Create the shaders and the program
65  if (Copy.myShaderProgram)
66  CreateProgram();
67 }
68 
69 
74 {
75  // Destroy effect program
76  if (myShaderProgram)
77  {
78  // Make sure we have a valid context
79  priv::GraphicsContext Ctx;
80 
81  GLCheck(glDeleteObjectARB(myShaderProgram));
82  }
83 }
84 
85 
89 bool PostFX::LoadFromFile(const std::string& Filename)
90 {
91  // Open the file
92  std::ifstream File(Filename.c_str());
93  if (!File)
94  {
95  std::cerr << "Failed to open effect file \"" << Filename << "\"" << std::endl;
96  return false;
97  }
98 
99  // Apply the preprocessing pass to the fragment shader code
100  myFragmentShader = PreprocessEffect(File);
101 
102  // Create the shaders and the program
103  CreateProgram();
104 
105  return myShaderProgram != 0;
106 }
107 
108 
112 bool PostFX::LoadFromMemory(const std::string& Effect)
113 {
114  // Open a stream and copy the effect code
115  std::istringstream Stream(Effect.c_str());
116 
117  // Apply the preprocessing pass to the fragment shader code
118  myFragmentShader = PreprocessEffect(Stream);
119 
120  // Create the shaders and the program
121  CreateProgram();
122 
123  return myShaderProgram != 0;
124 }
125 
126 
130 void PostFX::SetParameter(const std::string& Name, float X)
131 {
132  if (myShaderProgram)
133  {
134  // Enable program
135  GLCheck(glUseProgramObjectARB(myShaderProgram));
136 
137  // Get parameter location and assign it new values
138  GLint Location = glGetUniformLocationARB(myShaderProgram, Name.c_str());
139  if (Location != -1)
140  GLCheck(glUniform1fARB(Location, X));
141  else
142  std::cerr << "Parameter \"" << Name << "\" not found in post-effect" << std::endl;
143 
144  // Disable program
145  GLCheck(glUseProgramObjectARB(0));
146  }
147 }
148 
149 
153 void PostFX::SetParameter(const std::string& Name, float X, float Y)
154 {
155  if (myShaderProgram)
156  {
157  // Enable program
158  GLCheck(glUseProgramObjectARB(myShaderProgram));
159 
160  // Get parameter location and assign it new values
161  GLint Location = glGetUniformLocationARB(myShaderProgram, Name.c_str());
162  if (Location != -1)
163  GLCheck(glUniform2fARB(Location, X, Y));
164  else
165  std::cerr << "Parameter \"" << Name << "\" not found in post-effect" << std::endl;
166 
167  // Disable program
168  GLCheck(glUseProgramObjectARB(0));
169  }
170 }
171 
172 
176 void PostFX::SetParameter(const std::string& Name, float X, float Y, float Z)
177 {
178  if (myShaderProgram)
179  {
180  // Enable program
181  GLCheck(glUseProgramObjectARB(myShaderProgram));
182 
183  // Get parameter location and assign it new values
184  GLint Location = glGetUniformLocationARB(myShaderProgram, Name.c_str());
185  if (Location != -1)
186  GLCheck(glUniform3fARB(Location, X, Y, Z));
187  else
188  std::cerr << "Parameter \"" << Name << "\" not found in post-effect" << std::endl;
189 
190  // Disable program
191  GLCheck(glUseProgramObjectARB(0));
192  }
193 }
194 
195 
199 void PostFX::SetParameter(const std::string& Name, float X, float Y, float Z, float W)
200 {
201  if (myShaderProgram)
202  {
203  // Enable program
204  GLCheck(glUseProgramObjectARB(myShaderProgram));
205 
206  // Get parameter location and assign it new values
207  GLint Location = glGetUniformLocationARB(myShaderProgram, Name.c_str());
208  if (Location != -1)
209  GLCheck(glUniform4fARB(Location, X, Y, Z, W));
210  else
211  std::cerr << "Parameter \"" << Name << "\" not found in post-effect" << std::endl;
212 
213  // Disable program
214  GLCheck(glUseProgramObjectARB(0));
215  }
216 }
217 
218 
222 void PostFX::SetTexture(const std::string& Name, Image* Texture)
223 {
224  // Check that the current texture unit is available
225  GLint MaxUnits;
226  GLCheck(glGetIntegerv(GL_MAX_TEXTURE_COORDS_ARB, &MaxUnits));
227  if (myTextures.size() >= static_cast<std::size_t>(MaxUnits))
228  {
229  std::cerr << "Impossible to use texture \"" << Name << "\" for post-effect : all available texture units are used" << std::endl;
230  return;
231  }
232 
233  // Make sure the given name is a valid variable in the effect
234  int Location = glGetUniformLocationARB(myShaderProgram, Name.c_str());
235  if (Location == -1)
236  {
237  std::cerr << "Texture \"" << Name << "\" not found in post-effect" << std::endl;
238  return;
239  }
240 
241  // Store the texture for later use
242  myTextures[Name] = Texture ? Texture : &myFrameBuffer;
243 }
244 
245 
250 {
251  PostFX Temp(Other);
252 
253  std::swap(myShaderProgram, Temp.myShaderProgram);
254  std::swap(myTextures, Temp.myTextures);
255  std::swap(myFragmentShader, Temp.myFragmentShader);
256  std::swap(myFrameBuffer, Temp.myFrameBuffer);
257 
258  return *this;
259 }
260 
261 
266 {
267  // Make sure we have a valid context
268  priv::GraphicsContext Ctx;
269 
270  return glewIsSupported("GL_ARB_shading_language_100") != 0 &&
271  glewIsSupported("GL_ARB_shader_objects") != 0 &&
272  glewIsSupported("GL_ARB_vertex_shader") != 0 &&
273  glewIsSupported("GL_ARB_fragment_shader") != 0;
274 }
275 
276 
280 void PostFX::Render(RenderTarget& Target) const
281 {
282  // Check that we have a valid program
283  if (!myShaderProgram)
284  return;
285 
286  // Copy the current framebuffer pixels to our frame buffer texture
287  // The ugly cast is temporary until PostFx are rewritten :)
288  myFrameBuffer.CopyScreen((RenderWindow&)Target);
289 
290  // Enable program
291  GLCheck(glUseProgramObjectARB(myShaderProgram));
292 
293  // Bind textures
294  TextureTable::const_iterator ItTex = myTextures.begin();
295  for (std::size_t i = 0; i < myTextures.size(); ++i)
296  {
297  int Location = glGetUniformLocationARB(myShaderProgram, ItTex->first.c_str());
298  GLCheck(glUniform1iARB(Location, static_cast<GLint>(i)));
299  GLCheck(glActiveTextureARB(static_cast<GLenum>(GL_TEXTURE0_ARB + i)));
300  ItTex->second->Bind();
301  ItTex++;
302  }
303 
304  // Compute the texture coordinates (in case the texture is larger than the screen, or flipped)
305  IntRect FrameBufferRect(0, 0, myFrameBuffer.GetWidth(), myFrameBuffer.GetHeight());
306  FloatRect TexCoords = myFrameBuffer.GetTexCoords(FrameBufferRect);
307 
308  // Render a fullscreen quad using the effect on our framebuffer
309  FloatRect Screen = Target.GetView().GetRect();
310  glBegin(GL_QUADS);
311  glTexCoord2f(TexCoords.Left, TexCoords.Top); glVertex2f(Screen.Left, Screen.Bottom);
312  glTexCoord2f(TexCoords.Right, TexCoords.Top); glVertex2f(Screen.Right, Screen.Bottom);
313  glTexCoord2f(TexCoords.Right, TexCoords.Bottom); glVertex2f(Screen.Right, Screen.Top);
314  glTexCoord2f(TexCoords.Left, TexCoords.Bottom); glVertex2f(Screen.Left, Screen.Top);
315  glEnd();
316 
317  // Disable program
318  GLCheck(glUseProgramObjectARB(0));
319 
320  // Disable texture units
321  for (std::size_t i = 0; i < myTextures.size(); ++i)
322  {
323  GLCheck(glActiveTextureARB(static_cast<GLenum>(GL_TEXTURE0_ARB + i)));
324  GLCheck(glBindTexture(GL_TEXTURE_2D, 0));
325  }
326  GLCheck(glActiveTextureARB(GL_TEXTURE0_ARB));
327 }
328 
329 
334 std::string PostFX::PreprocessEffect(std::istream& File)
335 {
336  // Initialize output string
337  std::set<std::string> myTextures;
338  std::string Out = "";
339 
340  // Variable declarations
341  std::string Line;
342  while (std::getline(File, Line) && (Line.substr(0, 6) != "effect"))
343  {
344  // Remove the ending '\r', if any
345  if (!Line.empty() && (Line[Line.size() - 1] == '\r'))
346  Line.erase(Line.size() - 1);
347 
348  // Skip empty lines
349  if (Line == "")
350  continue;
351 
352  // Extract variables type and name and convert them
353  std::string Type, Name;
354  std::istringstream iss(Line);
355  if (!(iss >> Type >> Name))
356  {
357  std::cerr << "Post-effect error : invalid declaration (should be \"[type][name]\")" << std::endl
358  << "> " << Line << std::endl;
359  return "";
360  }
361 
362  if (Type == "texture")
363  {
364  // Textures need some checking and conversion
365  if (myTextures.find(Name) != myTextures.end())
366  {
367  std::cerr << "Post-effect error : texture \"" << Name << "\" already exists" << std::endl;
368  return "";
369  }
370 
371  Out += "uniform sampler2D " + Name + ";\n";
372  myTextures.insert(Name);
373  }
374  else
375  {
376  // Other types are just copied to output with "uniform" prefix
377  Out += "uniform " + Type + " " + Name + ";\n";
378  }
379  }
380 
381  // Effect code
382  Out += "void main()\n";
383  while (std::getline(File, Line))
384  {
385  // Replace any texture lookup "T(" by "texture2D(T, "
386  for (std::set<std::string>::const_iterator i = myTextures.begin(); i != myTextures.end(); ++i)
387  {
388  std::string::size_type Pos = Line.find(*i);
389  if (Pos != std::string::npos)
390  Line.replace(Pos, i->size() + 1, "texture2D(" + *i + ", ");
391  }
392 
393  // Replace "_in" by "gl_TexCoord[0].xy"
394  for (std::string::size_type Pos = Line.find("_in"); Pos != std::string::npos; Pos = Line.find("_in"))
395  Line.replace(Pos, 3, "gl_TexCoord[0].xy");
396 
397  // Replace "_out" by "gl_FragColor"
398  for (std::string::size_type Pos = Line.find("_out"); Pos != std::string::npos; Pos = Line.find("_out"))
399  Line.replace(Pos, 4, "gl_FragColor");
400 
401  // Write modified line to output string
402  Out += Line + "\n";
403  }
404 
405  return Out;
406 }
407 
408 
412 void PostFX::CreateProgram()
413 {
414  // Make sure we have a valid context
415  priv::GraphicsContext Ctx;
416 
417  // Check that we can use post-FX !
418  if (!CanUsePostFX())
419  {
420  std::cerr << "Failed to create a PostFX : your system doesn't support effects" << std::endl;
421  return;
422  }
423 
424  // Destroy effect program if it was already created
425  if (myShaderProgram)
426  GLCheck(glDeleteObjectARB(myShaderProgram));
427 
428  // Define vertex shader source (we provide it directly as it doesn't have to change)
429  static const std::string VertexShaderSrc =
430  "void main()"
431  "{"
432  " gl_TexCoord[0] = gl_MultiTexCoord0;"
433  " gl_Position = ftransform();"
434  "}";
435 
436  // Create the program
437  myShaderProgram = glCreateProgramObjectARB();
438 
439  // Create the shaders
440  GLhandleARB VertexShader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);
441  GLhandleARB FragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
442 
443  // Compile them
444  const char* VertexSrc = VertexShaderSrc.c_str();
445  const char* FragmentSrc = myFragmentShader.c_str();
446  GLCheck(glShaderSourceARB(VertexShader, 1, &VertexSrc, NULL));
447  GLCheck(glShaderSourceARB(FragmentShader, 1, &FragmentSrc, NULL));
448  GLCheck(glCompileShaderARB(VertexShader));
449  GLCheck(glCompileShaderARB(FragmentShader));
450 
451  // Check the compile logs
452  GLint Success;
453  GLCheck(glGetObjectParameterivARB(VertexShader, GL_OBJECT_COMPILE_STATUS_ARB, &Success));
454  if (Success == GL_FALSE)
455  {
456  char CompileLog[1024];
457  GLCheck(glGetInfoLogARB(VertexShader, sizeof(CompileLog), 0, CompileLog));
458  std::cerr << "Failed to compile post-effect :" << std::endl
459  << CompileLog << std::endl;
460  GLCheck(glDeleteObjectARB(VertexShader));
461  GLCheck(glDeleteObjectARB(FragmentShader));
462  GLCheck(glDeleteObjectARB(myShaderProgram));
463  myShaderProgram = 0;
464  return;
465  }
466  GLCheck(glGetObjectParameterivARB(FragmentShader, GL_OBJECT_COMPILE_STATUS_ARB, &Success));
467  if (Success == GL_FALSE)
468  {
469  char CompileLog[1024];
470  GLCheck(glGetInfoLogARB(FragmentShader, sizeof(CompileLog), 0, CompileLog));
471  std::cerr << "Failed to compile post-effect :" << std::endl
472  << CompileLog << std::endl;
473  GLCheck(glDeleteObjectARB(VertexShader));
474  GLCheck(glDeleteObjectARB(FragmentShader));
475  GLCheck(glDeleteObjectARB(myShaderProgram));
476  myShaderProgram = 0;
477  return;
478  }
479 
480  // Attach the shaders to the program
481  GLCheck(glAttachObjectARB(myShaderProgram, VertexShader));
482  GLCheck(glAttachObjectARB(myShaderProgram, FragmentShader));
483 
484  // We can now delete the shaders
485  GLCheck(glDeleteObjectARB(VertexShader));
486  GLCheck(glDeleteObjectARB(FragmentShader));
487 
488  // Link the program
489  GLCheck(glLinkProgramARB(myShaderProgram));
490 
491  // Get link log
492  GLCheck(glGetObjectParameterivARB(myShaderProgram, GL_OBJECT_LINK_STATUS_ARB, &Success));
493  if (Success == GL_FALSE)
494  {
495  // Oops... link errors
496  char LinkLog[1024];
497  GLCheck(glGetInfoLogARB(myShaderProgram, sizeof(LinkLog), 0, LinkLog));
498  std::cerr << "Failed to link post-effect :" << std::endl
499  << LinkLog << std::endl;
500  GLCheck(glDeleteObjectARB(myShaderProgram));
501  myShaderProgram = 0;
502  return;
503  }
504 }
505 
506 } // namespace sf
PostFX & operator=(const PostFX &Other)
Assignment operator.
Definition: PostFX.cpp:249
void SetTexture(const std::string &Name, Image *Texture)
Set a texture parameter.
Definition: PostFX.cpp:222
~PostFX()
Destructor.
Definition: PostFX.cpp:73
void SetSmooth(bool Smooth)
Enable or disable image smooth filter.
Definition: Image.cpp:490
bool LoadFromFile(const std::string &Filename)
Load the effect from a file.
Definition: PostFX.cpp:89
Abstract base class for every object that can be drawn into a render window.
Definition: Drawable.hpp:58
Simple wrapper for sf::Window that allows easy 2D rendering.
const sf::FloatRect & GetRect() const
Get the bounding rectangle of the view.
Definition: View.cpp:126
T Right
Right coordinate of the rectangle.
Definition: Rect.hpp:114
virtual void Render(RenderTarget &Target) const
/see Drawable::Render
Definition: PostFX.cpp:280
PostFX()
Default constructor.
Definition: PostFX.cpp:43
void SetParameter(const std::string &Name, float X)
Change a parameter of the effect (1 float)
Definition: PostFX.cpp:130
Image is the low-level class for loading and manipulating images.
Definition: Image.hpp:46
bool CopyScreen(RenderWindow &Window, const IntRect &SourceRect=IntRect(0, 0, 0, 0))
Create the image from the current contents of the given window.
Definition: Image.cpp:358
const View & GetView() const
Get the current view.
PostFX is used to apply a post effect to a window.
Definition: PostFX.hpp:43
unsigned int GetWidth() const
Return the width of the image.
Definition: Image.cpp:517
T Bottom
Bottom coordinate of the rectangle.
Definition: Rect.hpp:115
FloatRect GetTexCoords(const IntRect &Rect) const
Convert a subrect expressed in pixels, into float texture coordinates.
Definition: Image.cpp:545
Base class for all render targets (window, image, ...)
unsigned int GetHeight() const
Return the height of the image.
Definition: Image.cpp:526
T Top
Top coordinate of the rectangle.
Definition: Rect.hpp:113
static bool CanUsePostFX()
Tell whether or not the system supports post-effects.
Definition: PostFX.cpp:265
T Left
Left coordinate of the rectangle.
Definition: Rect.hpp:112
bool LoadFromMemory(const std::string &Effect)
Load the effect from a text in memory.
Definition: PostFX.cpp:112