From 191730ac0d639cfdef8fae560cdbc7d7e8c67db1 Mon Sep 17 00:00:00 2001
From: Laurent Gomila <laurent.gom@gmail.com>
Date: Sun, 25 Dec 2011 22:30:38 +0100
Subject: [PATCH] Added a render states cache to improve performances

---
 include/SFML/Graphics/RenderTarget.hpp        |  61 ++++-
 include/SFML/Graphics/Texture.hpp             |   3 +
 src/SFML/Graphics/CMakeLists.txt              |   2 +
 src/SFML/Graphics/RenderTarget.cpp            | 250 +++++++++++++-----
 .../Graphics/RenderTextureImplDefault.cpp     |   4 +
 src/SFML/Graphics/RenderTextureImplFBO.cpp    |   2 +-
 src/SFML/Graphics/Texture.cpp                 |  55 +++-
 src/SFML/Graphics/TextureSaver.cpp            |  50 ++++
 src/SFML/Graphics/TextureSaver.hpp            |  75 ++++++
 9 files changed, 435 insertions(+), 67 deletions(-)
 create mode 100644 src/SFML/Graphics/TextureSaver.cpp
 create mode 100644 src/SFML/Graphics/TextureSaver.hpp

diff --git a/include/SFML/Graphics/RenderTarget.hpp b/include/SFML/Graphics/RenderTarget.hpp
index 548c09df..e87ffc3b 100644
--- a/include/SFML/Graphics/RenderTarget.hpp
+++ b/include/SFML/Graphics/RenderTarget.hpp
@@ -36,12 +36,12 @@
 #include <SFML/Graphics/BlendMode.hpp>
 #include <SFML/Graphics/RenderStates.hpp>
 #include <SFML/Graphics/PrimitiveType.hpp>
+#include <SFML/Graphics/Vertex.hpp>
 
 
 namespace sf
 {
 class Drawable;
-class Vertex;
 
 ////////////////////////////////////////////////////////////
 /// \brief Base class for all render targets (window, texture, ...)
@@ -303,6 +303,44 @@ protected :
     ////////////////////////////////////////////////////////////
     void Initialize();
 
+    ////////////////////////////////////////////////////////////
+    /// \brief Apply the current view
+    ///
+    ////////////////////////////////////////////////////////////
+    void ApplyCurrentView();
+
+    ////////////////////////////////////////////////////////////
+    /// \brief Apply a new blending mode
+    ///
+    /// \param mode Blending mode to apply
+    ///
+    ////////////////////////////////////////////////////////////
+    void ApplyBlendMode(BlendMode mode);
+
+    ////////////////////////////////////////////////////////////
+    /// \brief Apply a new transform
+    ///
+    /// \param transform Transform to apply
+    ///
+    ////////////////////////////////////////////////////////////
+    void ApplyTransform(const Transform& transform);
+
+    ////////////////////////////////////////////////////////////
+    /// \brief Apply a new texture
+    ///
+    /// \param texture Texture to apply
+    ///
+    ////////////////////////////////////////////////////////////
+    void ApplyTexture(const Texture* texture);
+
+    ////////////////////////////////////////////////////////////
+    /// \brief Apply a new shader
+    ///
+    /// \param shader Shader to apply
+    ///
+    ////////////////////////////////////////////////////////////
+    void ApplyShader(const Shader* shader);
+
 private :
 
     ////////////////////////////////////////////////////////////
@@ -319,12 +357,27 @@ private :
     ////////////////////////////////////////////////////////////
     virtual bool Activate(bool active) = 0;
 
+    ////////////////////////////////////////////////////////////
+    /// \brief Render states cache
+    ///
+    ////////////////////////////////////////////////////////////
+    struct StatesCache
+    {
+        enum {VertexCacheSize = 4};
+
+        bool      ViewChanged;    ///< Has the current view changed since last draw?
+        BlendMode LastBlendMode;  ///< Cached blending mode
+        Uint64    LastTextureId;  ///< Cached texture
+        bool      UseVertexCache; ///< Did we previously use the vertex cache?
+        Vertex    VertexCache[VertexCacheSize]; ///< Pre-transformed vertices cache
+    };
+
     ////////////////////////////////////////////////////////////
     // Member data
     ////////////////////////////////////////////////////////////
-    View myDefaultView; ///< Default view
-    View myView;        ///< Current view
-    bool myViewChanged; ///< Has the current view changed since last Draw?
+    View        myDefaultView; ///< Default view
+    View        myView;        ///< Current view
+    StatesCache myCache;       ///< Render states cache
 };
 
 } // namespace sf
diff --git a/include/SFML/Graphics/Texture.hpp b/include/SFML/Graphics/Texture.hpp
index c500ab5f..7a2ee5e4 100644
--- a/include/SFML/Graphics/Texture.hpp
+++ b/include/SFML/Graphics/Texture.hpp
@@ -35,6 +35,7 @@
 namespace sf
 {
 class Window;
+class RenderTarget;
 class RenderTexture;
 class InputStream;
 
@@ -467,6 +468,7 @@ public :
 private :
 
     friend class RenderTexture;
+    friend class RenderTarget;
 
     ////////////////////////////////////////////////////////////
     /// \brief Get a valid image size according to hardware support
@@ -494,6 +496,7 @@ private :
     bool         myIsSmooth;      ///< Status of the smooth filter
     bool         myIsRepeated;    ///< Is the texture in repeat mode?
     mutable bool myPixelsFlipped; ///< To work around the inconsistency in Y orientation
+    Uint64       myCacheId;       ///< Unique number that identifies the texture to the render target's cache
 };
 
 } // namespace sf
diff --git a/src/SFML/Graphics/CMakeLists.txt b/src/SFML/Graphics/CMakeLists.txt
index 1adabaed..8856fb12 100644
--- a/src/SFML/Graphics/CMakeLists.txt
+++ b/src/SFML/Graphics/CMakeLists.txt
@@ -51,6 +51,8 @@ set(SRC
     ${INCROOT}/Text.hpp
     ${SRCROOT}/Texture.cpp
     ${INCROOT}/Texture.hpp
+    ${SRCROOT}/TextureSaver.cpp
+    ${SRCROOT}/TextureSaver.hpp
     ${SRCROOT}/Transform.cpp
     ${INCROOT}/Transform.hpp
     ${SRCROOT}/Transformable.cpp
diff --git a/src/SFML/Graphics/RenderTarget.cpp b/src/SFML/Graphics/RenderTarget.cpp
index 3fbf730c..cea7386b 100644
--- a/src/SFML/Graphics/RenderTarget.cpp
+++ b/src/SFML/Graphics/RenderTarget.cpp
@@ -40,7 +40,7 @@ namespace sf
 RenderTarget::RenderTarget() :
 myDefaultView(),
 myView       (),
-myViewChanged(false)
+myCache      ()
 {
 }
 
@@ -66,7 +66,7 @@ void RenderTarget::Clear(const Color& color)
 void RenderTarget::SetView(const View& view)
 {
     myView = view;
-    myViewChanged = true;
+    myCache.ViewChanged = true;
 }
 
 
@@ -136,81 +136,78 @@ void RenderTarget::Draw(const Vertex* vertices, unsigned int verticesCount,
 
     if (Activate(true))
     {
-        // Apply the new view if needed
-        if (myViewChanged)
+        // Check if the vertex count is low enough so that we can pre-transform them
+        bool useVertexCache = (verticesCount <= StatesCache::VertexCacheSize);
+        if (useVertexCache)
         {
-            // Set the viewport
-            IntRect viewport = GetViewport(myView);
-            int top = GetHeight() - (viewport.Top + viewport.Height);
-            GLCheck(glViewport(viewport.Left, top, viewport.Width, viewport.Height));
+            // Pre-transform the vertices and store them into the vertex cache
+            for (unsigned int i = 0; i < verticesCount; ++i)
+            {
+                Vertex& vertex = myCache.VertexCache[i];
+                vertex.Position = states.Transform * vertices[i].Position;
+                vertex.Color = vertices[i].Color;
+                vertex.TexCoords = vertices[i].TexCoords;
+            }
 
-            // Set the projection matrix
-            GLCheck(glMatrixMode(GL_PROJECTION));
-            GLCheck(glLoadMatrixf(myView.GetTransform().GetMatrix()));
-
-            myViewChanged = false;
+            // Since vertices are transformed, we must use an identity transform to render them
+            if (!myCache.UseVertexCache)
+                ApplyTransform(Transform::Identity);
+        }
+        else
+        {
+            ApplyTransform(states.Transform);
         }
 
-        // Apply the transform
-        GLCheck(glMatrixMode(GL_MODELVIEW));
-        GLCheck(glLoadMatrixf(states.Transform.GetMatrix()));
+        // Apply the view
+        if (myCache.ViewChanged)
+            ApplyCurrentView();
 
         // Apply the blend mode
-        switch (states.BlendMode)
-        {
-            // Alpha blending
-            // glBlendFuncSeparateEXT is used when available to avoid an incorrect alpha value when the target
-            // is a RenderTexture -- in this case the alpha value must be written directly to the target buffer
-            default :
-            case BlendAlpha :
-                if (GLEW_EXT_blend_func_separate)
-                    GLCheck(glBlendFuncSeparateEXT(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
-                else
-                    GLCheck(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
-                break;
-
-            // Additive blending
-            case BlendAdd :
-                GLCheck(glBlendFunc(GL_SRC_ALPHA, GL_ONE));
-                break;
-
-            // Multiplicative blending
-            case BlendMultiply :
-                GLCheck(glBlendFunc(GL_DST_COLOR, GL_ZERO));
-                break;
-
-            // No blending
-            case BlendNone :
-                GLCheck(glBlendFunc(GL_ONE, GL_ZERO));
-                break;
-        }
+        if (states.BlendMode != myCache.LastBlendMode)
+            ApplyBlendMode(states.BlendMode);
 
         // Apply the texture
-        if (states.Texture)
-            states.Texture->Bind(Texture::Pixels);
-        else
-            GLCheck(glBindTexture(GL_TEXTURE_2D, 0));
+        Uint64 textureId = states.Texture ? states.Texture->myCacheId : 0;
+        if (textureId != myCache.LastTextureId)
+            ApplyTexture(states.Texture);
 
         // Apply the shader
         if (states.Shader)
-            states.Shader->Bind();
-        else
-            GLCheck(glUseProgramObjectARB(0));
+            ApplyShader(states.Shader);
+
+        // If we pre-transform the vertices, we must use our internal vertex cache
+        if (useVertexCache)
+        {
+            // ... and if we already used it previously, we don't need to set the pointers again
+            if (!myCache.UseVertexCache)
+                vertices = myCache.VertexCache;
+            else
+                vertices = NULL;
+        }
 
         // Setup the pointers to the vertices' components
-        const char* data = reinterpret_cast<const char*>(vertices);
-        GLCheck(glVertexPointer(2, GL_FLOAT, sizeof(Vertex), data + 0));
-        GLCheck(glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), data + 8));
-        GLCheck(glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), data + 12));
+        if (vertices)
+        {
+            const char* data = reinterpret_cast<const char*>(vertices);
+            GLCheck(glVertexPointer(2, GL_FLOAT, sizeof(Vertex), data + 0));
+            GLCheck(glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Vertex), data + 8));
+            GLCheck(glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), data + 12));
+        }
 
         // Find the OpenGL primitive type
-        static const GLenum modes[] = {GL_POINTS, GL_LINES, GL_LINE_STRIP,
-                                       GL_TRIANGLES, GL_TRIANGLE_STRIP,
-                                       GL_TRIANGLE_FAN, GL_QUADS};
+        static const GLenum modes[] = {GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_TRIANGLES,
+                                       GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_QUADS};
         GLenum mode = modes[type];
 
         // Draw the primitives
         GLCheck(glDrawArrays(mode, 0, verticesCount));
+
+        // Unbind the shader, if any
+        if (states.Shader)
+            ApplyShader(NULL);
+
+        // Update the cache
+        myCache.UseVertexCache = useVertexCache;
     }
 }
 
@@ -257,16 +254,26 @@ void RenderTarget::ResetGLStates()
         // Make sure that GLEW is initialized
         priv::EnsureGlewInit();
 
+        // Define the default OpenGL states
         GLCheck(glDisable(GL_LIGHTING));
         GLCheck(glDisable(GL_DEPTH_TEST));
         GLCheck(glEnable(GL_TEXTURE_2D));
         GLCheck(glEnable(GL_ALPHA_TEST));
         GLCheck(glEnable(GL_BLEND));
         GLCheck(glAlphaFunc(GL_GREATER, 0));
+        GLCheck(glMatrixMode(GL_MODELVIEW));
         GLCheck(glEnableClientState(GL_VERTEX_ARRAY));
         GLCheck(glEnableClientState(GL_COLOR_ARRAY));
         GLCheck(glEnableClientState(GL_TEXTURE_COORD_ARRAY));
 
+        // Apply the default SFML states
+        ApplyBlendMode(BlendAlpha);
+        ApplyTransform(Transform::Identity);
+        ApplyTexture(NULL);
+        ApplyShader(NULL);
+        myCache.UseVertexCache = false;
+
+        // Set the default view
         SetView(GetView());
     }
 }
@@ -275,12 +282,135 @@ void RenderTarget::ResetGLStates()
 ////////////////////////////////////////////////////////////
 void RenderTarget::Initialize()
 {
-    // Setup the default view
+    // Setup the default and current views
     myDefaultView.Reset(FloatRect(0, 0, static_cast<float>(GetWidth()), static_cast<float>(GetHeight())));
-    SetView(myDefaultView);
+    myView = myDefaultView;
 
     // Initialize the default OpenGL render-states
     ResetGLStates();
 }
 
+
+////////////////////////////////////////////////////////////
+void RenderTarget::ApplyCurrentView()
+{
+    // Set the viewport
+    IntRect viewport = GetViewport(myView);
+    int top = GetHeight() - (viewport.Top + viewport.Height);
+    GLCheck(glViewport(viewport.Left, top, viewport.Width, viewport.Height));
+
+    // Set the projection matrix
+    GLCheck(glMatrixMode(GL_PROJECTION));
+    GLCheck(glLoadMatrixf(myView.GetTransform().GetMatrix()));
+
+    // Go back to model-view mode
+    GLCheck(glMatrixMode(GL_MODELVIEW));
+
+    myCache.ViewChanged = false;
+}
+
+
+////////////////////////////////////////////////////////////
+void RenderTarget::ApplyBlendMode(BlendMode mode)
+{
+    switch (mode)
+    {
+        // Alpha blending
+        // glBlendFuncSeparateEXT is used when available to avoid an incorrect alpha value when the target
+        // is a RenderTexture -- in this case the alpha value must be written directly to the target buffer
+        default :
+        case BlendAlpha :
+            if (GLEW_EXT_blend_func_separate)
+                GLCheck(glBlendFuncSeparateEXT(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA));
+            else
+                GLCheck(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
+            break;
+
+        // Additive blending
+        case BlendAdd :
+            GLCheck(glBlendFunc(GL_SRC_ALPHA, GL_ONE));
+            break;
+
+        // Multiplicative blending
+        case BlendMultiply :
+            GLCheck(glBlendFunc(GL_DST_COLOR, GL_ZERO));
+            break;
+
+        // No blending
+        case BlendNone :
+            GLCheck(glBlendFunc(GL_ONE, GL_ZERO));
+            break;
+    }
+
+    myCache.LastBlendMode = mode;
+}
+
+
+////////////////////////////////////////////////////////////
+void RenderTarget::ApplyTransform(const Transform& transform)
+{
+    // No need to call glMatrixMode(GL_MODELVIEW), it is always the
+    // current mode (for optimization purpose, since it's the most used)
+    GLCheck(glLoadMatrixf(transform.GetMatrix()));
+}
+
+
+////////////////////////////////////////////////////////////
+void RenderTarget::ApplyTexture(const Texture* texture)
+{
+    if (texture)
+        texture->Bind(Texture::Pixels);
+    else
+        GLCheck(glBindTexture(GL_TEXTURE_2D, 0));
+
+    myCache.LastTextureId = texture ? texture->myCacheId : 0;
+}
+
+
+////////////////////////////////////////////////////////////
+void RenderTarget::ApplyShader(const Shader* shader)
+{
+    if (shader)
+        shader->Bind();
+    else
+        GLCheck(glUseProgramObjectARB(0));
+}
+
 } // namespace sf
+
+
+////////////////////////////////////////////////////////////
+// Render states caching strategies
+//
+// * View
+//   If SetView was called since last draw, the projection
+//   matrix is updated. We don't need more, the view doesn't
+//   change frequently.
+//
+// * Transform
+//   The transform matrix is usually expensive because each
+//   entity will most likely use a different transform. This can
+//   lead, in worst case, to changing it every 4 vertices.
+//   To avoid that, when the vertex count is low enough, we
+//   pre-transform them and therefore use an identity transform
+//   to render them.
+//
+// * Blending mode
+//   It's a simple integral value, so we can easily check
+//   whether the value to apply is the same as before or not.
+//
+// * Texture
+//   Storing the pointer or OpenGL ID of the last used texture
+//   is not enough; if the sf::Texture instance is destroyed,
+//   both the pointer and the OpenGL ID might be recycled in
+//   a new texture instance. We need to use our own unique
+//   identifier system to ensure consistent caching.
+//
+// * Shader
+//   Shaders are very hard to optimize, because they have
+//   parameters that can be hard (if not impossible) to track,
+//   like matrices or textures. The only optimization that we
+//   do is that we avoid setting a null shader if there was
+//   already none for the previous draw.
+// 
+////////////////////////////////////////////////////////////
diff --git a/src/SFML/Graphics/RenderTextureImplDefault.cpp b/src/SFML/Graphics/RenderTextureImplDefault.cpp
index 7cffc63c..481eed64 100644
--- a/src/SFML/Graphics/RenderTextureImplDefault.cpp
+++ b/src/SFML/Graphics/RenderTextureImplDefault.cpp
@@ -27,6 +27,7 @@
 ////////////////////////////////////////////////////////////
 #include <SFML/Graphics/RenderTextureImplDefault.hpp>
 #include <SFML/Graphics/GLCheck.hpp>
+#include <SFML/Graphics/TextureSaver.hpp>
 #include <SFML/Window/Context.hpp>
 #include <SFML/System/Err.hpp>
 
@@ -77,6 +78,9 @@ bool RenderTextureImplDefault::Activate(bool active)
 ////////////////////////////////////////////////////////////
 void RenderTextureImplDefault::UpdateTexture(unsigned int textureId)
 {
+    // Make sure that the current texture binding will be preserved
+    priv::TextureSaver save;
+
     // Copy the rendered pixels to the texture
     GLCheck(glBindTexture(GL_TEXTURE_2D, textureId));
     GLCheck(glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, myWidth, myHeight));
diff --git a/src/SFML/Graphics/RenderTextureImplFBO.cpp b/src/SFML/Graphics/RenderTextureImplFBO.cpp
index 326452ae..d7ded0de 100644
--- a/src/SFML/Graphics/RenderTextureImplFBO.cpp
+++ b/src/SFML/Graphics/RenderTextureImplFBO.cpp
@@ -83,7 +83,7 @@ bool RenderTextureImplFBO::IsAvailable()
 ////////////////////////////////////////////////////////////
 bool RenderTextureImplFBO::Create(unsigned int width, unsigned int height, unsigned int textureId, bool depthBuffer)
 {
-    //Create the context
+    // Create the context
     myContext = new Context;
 
     // Create the framebuffer object
diff --git a/src/SFML/Graphics/Texture.cpp b/src/SFML/Graphics/Texture.cpp
index 197baf33..22207fe7 100644
--- a/src/SFML/Graphics/Texture.cpp
+++ b/src/SFML/Graphics/Texture.cpp
@@ -28,12 +28,33 @@
 #include <SFML/Graphics/Texture.hpp>
 #include <SFML/Graphics/Image.hpp>
 #include <SFML/Graphics/GLCheck.hpp>
+#include <SFML/Graphics/TextureSaver.hpp>
 #include <SFML/Window/Window.hpp>
+#include <SFML/System/Mutex.hpp>
+#include <SFML/System/Lock.hpp>
 #include <SFML/System/Err.hpp>
 #include <cassert>
 #include <cstring>
 
 
+////////////////////////////////////////////////////////////
+// Private data
+////////////////////////////////////////////////////////////
+namespace
+{
+    // Thread-safe unique identifier generator,
+    // is used for states cache (see RenderTarget)
+    sf::Uint64 GetUniqueId()
+    {
+        static sf::Uint64 id = 1; // start at 1, zero is "no texture"
+        static sf::Mutex mutex;
+
+        sf::Lock lock(mutex);
+        return id++;
+    }
+}
+
+
 namespace sf
 {
 ////////////////////////////////////////////////////////////
@@ -45,7 +66,8 @@ myTextureHeight(0),
 myTexture      (0),
 myIsSmooth     (false),
 myIsRepeated   (false),
-myPixelsFlipped(false)
+myPixelsFlipped(false),
+myCacheId      (GetUniqueId())
 {
 
 }
@@ -60,7 +82,8 @@ myTextureHeight(0),
 myTexture      (0),
 myIsSmooth     (copy.myIsSmooth),
 myIsRepeated   (copy.myIsRepeated),
-myPixelsFlipped(false)
+myPixelsFlipped(false),
+myCacheId      (GetUniqueId())
 {
     if (copy.myTexture)
         LoadFromImage(copy.CopyToImage());
@@ -123,6 +146,9 @@ bool Texture::Create(unsigned int width, unsigned int height)
         myTexture = static_cast<unsigned int>(texture);
     }
 
+    // Make sure that the current texture binding will be preserved
+    priv::TextureSaver save;
+
     // Initialize the texture
     GLCheck(glBindTexture(GL_TEXTURE_2D, myTexture));
     GLCheck(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, myTextureWidth, myTextureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL));
@@ -130,6 +156,7 @@ bool Texture::Create(unsigned int width, unsigned int height)
     GLCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, myIsRepeated ? GL_REPEAT : GL_CLAMP_TO_EDGE));
     GLCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, myIsSmooth ? GL_LINEAR : GL_NEAREST));
     GLCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, myIsSmooth ? GL_LINEAR : GL_NEAREST));
+    myCacheId = GetUniqueId();
 
     return true;
 }
@@ -195,6 +222,9 @@ bool Texture::LoadFromImage(const Image& image, const IntRect& area)
         // Create the texture and upload the pixels
         if (Create(rectangle.Width, rectangle.Height))
         {
+            // Make sure that the current texture binding will be preserved
+            priv::TextureSaver save;
+
             // Copy the pixels to the texture, row by row
             const Uint8* pixels = image.GetPixelsPtr() + 4 * (rectangle.Left + (width * rectangle.Top));
             GLCheck(glBindTexture(GL_TEXTURE_2D, myTexture));
@@ -237,6 +267,9 @@ Image Texture::CopyToImage() const
 
     EnsureGlContext();
 
+    // Make sure that the current texture binding will be preserved
+    priv::TextureSaver save;
+
     // Create an array of pixels
     std::vector<Uint8> pixels(myWidth * myHeight * 4);
 
@@ -302,10 +335,14 @@ void Texture::Update(const Uint8* pixels, unsigned int width, unsigned int heigh
     {
         EnsureGlContext();
 
+        // Make sure that the current texture binding will be preserved
+        priv::TextureSaver save;
+
         // Copy pixels from the given array to the texture
         GLCheck(glBindTexture(GL_TEXTURE_2D, myTexture));
         GLCheck(glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
         myPixelsFlipped = false;
+        myCacheId = GetUniqueId();
     }
 }
 
@@ -340,10 +377,14 @@ void Texture::Update(const Window& window, unsigned int x, unsigned int y)
 
     if (myTexture && window.SetActive(true))
     {
+        // Make sure that the current texture binding will be preserved
+        priv::TextureSaver save;
+
         // Copy pixels from the back-buffer to the texture
         GLCheck(glBindTexture(GL_TEXTURE_2D, myTexture));
         GLCheck(glCopyTexSubImage2D(GL_TEXTURE_2D, 0, x, y, 0, 0, window.GetWidth(), window.GetHeight()));
         myPixelsFlipped = true;
+        myCacheId = GetUniqueId();
     }
 }
 
@@ -380,6 +421,9 @@ void Texture::Bind(CoordinateType coordinateType) const
         // Load the matrix
         GLCheck(glMatrixMode(GL_TEXTURE));
         GLCheck(glLoadMatrixf(matrix));
+
+        // Go back to model-view mode (sf::RenderTarget relies on it)
+        GLCheck(glMatrixMode(GL_MODELVIEW));
     }
 }
 
@@ -395,6 +439,9 @@ void Texture::SetSmooth(bool smooth)
         {
             EnsureGlContext();
 
+            // Make sure that the current texture binding will be preserved
+            priv::TextureSaver save;
+
             GLCheck(glBindTexture(GL_TEXTURE_2D, myTexture));
             GLCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, myIsSmooth ? GL_LINEAR : GL_NEAREST));
             GLCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, myIsSmooth ? GL_LINEAR : GL_NEAREST));
@@ -421,6 +468,9 @@ void Texture::SetRepeated(bool repeated)
         {
             EnsureGlContext();
 
+            // Make sure that the current texture binding will be preserved
+            priv::TextureSaver save;
+
             GLCheck(glBindTexture(GL_TEXTURE_2D, myTexture));
             GLCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, myIsRepeated ? GL_REPEAT : GL_CLAMP_TO_EDGE));
             GLCheck(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, myIsRepeated ? GL_REPEAT : GL_CLAMP_TO_EDGE));
@@ -461,6 +511,7 @@ Texture& Texture::operator =(const Texture& right)
     std::swap(myIsSmooth,      temp.myIsSmooth);
     std::swap(myIsRepeated,    temp.myIsRepeated);
     std::swap(myPixelsFlipped, temp.myPixelsFlipped);
+    myCacheId = GetUniqueId();
 
     return *this;
 }
diff --git a/src/SFML/Graphics/TextureSaver.cpp b/src/SFML/Graphics/TextureSaver.cpp
new file mode 100644
index 00000000..e4bbecd3
--- /dev/null
+++ b/src/SFML/Graphics/TextureSaver.cpp
@@ -0,0 +1,50 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2009 Laurent Gomila (laurent.gom@gmail.com)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+//    you must not claim that you wrote the original software.
+//    If you use this software in a product, an acknowledgment
+//    in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+//    and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Graphics/TextureSaver.hpp>
+
+
+namespace sf
+{
+namespace priv
+{
+////////////////////////////////////////////////////////////
+TextureSaver::TextureSaver()
+{
+    GLCheck(glGetIntegerv(GL_TEXTURE_BINDING_2D, &myTextureBinding));
+}
+
+
+////////////////////////////////////////////////////////////
+TextureSaver::~TextureSaver()
+{
+    GLCheck(glBindTexture(GL_TEXTURE_2D, myTextureBinding));
+}
+
+} // namespace priv
+
+} // namespace sf
diff --git a/src/SFML/Graphics/TextureSaver.hpp b/src/SFML/Graphics/TextureSaver.hpp
new file mode 100644
index 00000000..845cffb7
--- /dev/null
+++ b/src/SFML/Graphics/TextureSaver.hpp
@@ -0,0 +1,75 @@
+////////////////////////////////////////////////////////////
+//
+// SFML - Simple and Fast Multimedia Library
+// Copyright (C) 2007-2009 Laurent Gomila (laurent.gom@gmail.com)
+//
+// This software is provided 'as-is', without any express or implied warranty.
+// In no event will the authors be held liable for any damages arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented;
+//    you must not claim that you wrote the original software.
+//    If you use this software in a product, an acknowledgment
+//    in the product documentation would be appreciated but is not required.
+//
+// 2. Altered source versions must be plainly marked as such,
+//    and must not be misrepresented as being the original software.
+//
+// 3. This notice may not be removed or altered from any source distribution.
+//
+////////////////////////////////////////////////////////////
+
+#ifndef SFML_TEXTURESAVER_HPP
+#define SFML_TEXTURESAVER_HPP
+
+////////////////////////////////////////////////////////////
+// Headers
+////////////////////////////////////////////////////////////
+#include <SFML/Graphics/GLCheck.hpp>
+
+
+namespace sf
+{
+namespace priv
+{
+////////////////////////////////////////////////////////////
+/// \brief Automatic wrapper for saving and restoring the current texture binding
+///
+////////////////////////////////////////////////////////////
+class TextureSaver
+{
+public :
+
+    ////////////////////////////////////////////////////////////
+    /// \brief Default constructor
+    ///
+    /// The current texture binding is saved.
+    ///
+    ////////////////////////////////////////////////////////////
+    TextureSaver();
+
+    ////////////////////////////////////////////////////////////
+    /// \brief Destructor
+    ///
+    /// The previous texture binding is restored.
+    ///
+    ////////////////////////////////////////////////////////////
+    ~TextureSaver();
+
+private :
+
+    ////////////////////////////////////////////////////////////
+    // Member data
+    ////////////////////////////////////////////////////////////
+    GLint myTextureBinding; ///< Texture binding to restore
+};
+
+} // namespace priv
+
+} // namespace sf
+
+
+#endif // SFML_TEXTURESAVER_HPP