Skip to content

Commit

Permalink
Optimized RenderTexture performance when using the FBO implementation…
Browse files Browse the repository at this point in the history
… by removing unnecessary context switches and flushing.
  • Loading branch information
binary1248 authored and eXpl0it3r committed Apr 14, 2018
1 parent c706f11 commit 0adde24
Show file tree
Hide file tree
Showing 17 changed files with 700 additions and 192 deletions.
4 changes: 3 additions & 1 deletion include/SFML/Graphics/RenderTarget.hpp
Expand Up @@ -296,7 +296,7 @@ class SFML_GRAPHICS_API RenderTarget : NonCopyable
/// \return True if operation was successful, false otherwise
///
////////////////////////////////////////////////////////////
virtual bool setActive(bool active = true) = 0;
virtual bool setActive(bool active = true);

////////////////////////////////////////////////////////////
/// \brief Save the current OpenGL render states and matrices
Expand Down Expand Up @@ -458,6 +458,7 @@ class SFML_GRAPHICS_API RenderTarget : NonCopyable
{
enum {VertexCacheSize = 4};

bool enable; ///< Is the cache enabled?
bool glStatesSet; ///< Are our internal GL states set yet?
bool viewChanged; ///< Has the current view changed since last draw?
BlendMode lastBlendMode; ///< Cached blending mode
Expand All @@ -473,6 +474,7 @@ class SFML_GRAPHICS_API RenderTarget : NonCopyable
View m_defaultView; ///< Default view
View m_view; ///< Current view
StatesCache m_cache; ///< Render states cache
Uint64 m_id; ///< Unique number that identifies the RenderTarget
};

} // namespace sf
Expand Down
15 changes: 15 additions & 0 deletions include/SFML/Window/Context.hpp
Expand Up @@ -112,11 +112,26 @@ class SFML_WINDOW_API Context : GlResource, NonCopyable
////////////////////////////////////////////////////////////
/// \brief Get the currently active context
///
/// This function will only return sf::Context objects.
/// Contexts created e.g. by RenderTargets or for internal
/// use will not be returned by this function.
///
/// \return The currently active context or NULL if none is active
///
////////////////////////////////////////////////////////////
static const Context* getActiveContext();

////////////////////////////////////////////////////////////
/// \brief Get the currently active context's ID
///
/// The context ID is used to identify contexts when
/// managing unshareable OpenGL resources.
///
/// \return The active context's ID or 0 if no context is currently active
///
////////////////////////////////////////////////////////////
static Uint64 getActiveContextId();

////////////////////////////////////////////////////////////
/// \brief Construct a in-memory context
///
Expand Down
15 changes: 15 additions & 0 deletions include/SFML/Window/GlResource.hpp
Expand Up @@ -37,6 +37,8 @@ namespace sf

class Context;

typedef void(*ContextDestroyCallback)(void*);

////////////////////////////////////////////////////////////
/// \brief Base class for classes that require an OpenGL context
///
Expand All @@ -57,6 +59,19 @@ class SFML_WINDOW_API GlResource
////////////////////////////////////////////////////////////
~GlResource();

////////////////////////////////////////////////////////////
/// \brief Register a function to be called when a context is destroyed
///
/// This is used for internal purposes in order to properly
/// clean up OpenGL resources that cannot be shared between
/// contexts.
///
/// \param callback Function to be called when a context is destroyed
/// \param arg Argument to pass when calling the function
///
////////////////////////////////////////////////////////////
static void registerContextDestroyCallback(ContextDestroyCallback callback, void* arg);

////////////////////////////////////////////////////////////
/// \brief RAII helper class to temporarily lock an available context for use
///
Expand Down
122 changes: 107 additions & 15 deletions src/SFML/Graphics/RenderTarget.cpp
Expand Up @@ -32,10 +32,14 @@
#include <SFML/Graphics/VertexArray.hpp>
#include <SFML/Graphics/VertexBuffer.hpp>
#include <SFML/Graphics/GLCheck.hpp>
#include <SFML/Window/Context.hpp>
#include <SFML/System/Mutex.hpp>
#include <SFML/System/Lock.hpp>
#include <SFML/System/Err.hpp>
#include <cassert>
#include <iostream>
#include <algorithm>
#include <map>


// GL_QUADS is unavailable on OpenGL ES, thus we need to define GL_QUADS ourselves
Expand All @@ -48,6 +52,36 @@

namespace
{
// Mutex to protect ID generation and our context-RenderTarget-map
sf::Mutex mutex;

// Unique identifier, used for identifying RenderTargets when
// tracking the currently active RenderTarget within a given context
sf::Uint64 getUniqueId()
{
sf::Lock lock(mutex);

static sf::Uint64 id = 1; // start at 1, zero is "no RenderTarget"

return id++;
}

// Map to help us detect whether a different RenderTarget
// has been activated within a single context
typedef std::map<sf::Uint64, sf::Uint64> ContextRenderTargetMap;
ContextRenderTargetMap contextRenderTargetMap;

// Check if a RenderTarget with the given ID is active in the current context
bool isActive(sf::Uint64 id)
{
ContextRenderTargetMap::iterator iter = contextRenderTargetMap.find(sf::Context::getActiveContextId());

if ((iter == contextRenderTargetMap.end()) || (iter->second != id))
return false;

return true;
}

// Convert an sf::BlendMode::Factor constant to the corresponding OpenGL constant.
sf::Uint32 factorToGlConstant(sf::BlendMode::Factor blendFactor)
{
Expand Down Expand Up @@ -94,7 +128,8 @@ namespace sf
RenderTarget::RenderTarget() :
m_defaultView(),
m_view (),
m_cache ()
m_cache (),
m_id (getUniqueId())
{
m_cache.glStatesSet = false;
}
Expand All @@ -109,7 +144,7 @@ RenderTarget::~RenderTarget()
////////////////////////////////////////////////////////////
void RenderTarget::clear(const Color& color)
{
if (setActive(true))
if (isActive(m_id) || setActive(true))
{
// Unbind texture to fix RenderTexture preventing clear
applyTexture(NULL);
Expand Down Expand Up @@ -224,7 +259,7 @@ void RenderTarget::draw(const Vertex* vertices, std::size_t vertexCount,
}
#endif

if (setActive(true))
if (isActive(m_id) || setActive(true))
{
// Check if the vertex count is low enough so that we can pre-transform them
bool useVertexCache = (vertexCount <= StatesCache::VertexCacheSize);
Expand All @@ -245,7 +280,7 @@ void RenderTarget::draw(const Vertex* vertices, std::size_t vertexCount,

// Check if texture coordinates array is needed, and update client state accordingly
bool enableTexCoordsArray = (states.texture || states.shader);
if (enableTexCoordsArray != m_cache.texCoordsArrayEnabled)
if (!m_cache.enable || (enableTexCoordsArray != m_cache.texCoordsArrayEnabled))
{
if (enableTexCoordsArray)
glCheck(glEnableClientState(GL_TEXTURE_COORD_ARRAY));
Expand All @@ -255,7 +290,7 @@ void RenderTarget::draw(const Vertex* vertices, std::size_t vertexCount,

// If we switch between non-cache and cache mode or enable texture
// coordinates we need to set up the pointers to the vertices' components
if (!useVertexCache || !m_cache.useVertexCache)
if (!m_cache.enable || !useVertexCache || !m_cache.useVertexCache)
{
const char* data = reinterpret_cast<const char*>(vertices);

Expand Down Expand Up @@ -324,15 +359,15 @@ void RenderTarget::draw(const VertexBuffer& vertexBuffer, std::size_t firstVerte
}
#endif

if (setActive(true))
if (isActive(m_id) || setActive(true))
{
setupDraw(false, states);

// Bind vertex buffer
VertexBuffer::bind(&vertexBuffer);

// Always enable texture coordinates
if (!m_cache.texCoordsArrayEnabled)
if (!m_cache.enable || !m_cache.texCoordsArrayEnabled)
glCheck(glEnableClientState(GL_TEXTURE_COORD_ARRAY));

glCheck(glVertexPointer(2, GL_FLOAT, sizeof(Vertex), reinterpret_cast<const void*>(0)));
Expand All @@ -353,10 +388,49 @@ void RenderTarget::draw(const VertexBuffer& vertexBuffer, std::size_t firstVerte
}


////////////////////////////////////////////////////////////
bool RenderTarget::setActive(bool active)
{
// Mark this RenderTarget as active or no longer active in the tracking map
{
sf::Lock lock(mutex);

Uint64 contextId = Context::getActiveContextId();

ContextRenderTargetMap::iterator iter = contextRenderTargetMap.find(contextId);

if (active)
{
if (iter == contextRenderTargetMap.end())
{
contextRenderTargetMap[contextId] = m_id;

m_cache.enable = false;
}
else if (iter->second != m_id)
{
iter->second = m_id;

m_cache.enable = false;
}
}
else
{
if (iter != contextRenderTargetMap.end())
contextRenderTargetMap.erase(iter);

m_cache.enable = false;
}
}

return true;
}


////////////////////////////////////////////////////////////
void RenderTarget::pushGLStates()
{
if (setActive(true))
if (isActive(m_id) || setActive(true))
{
#ifdef SFML_DEBUG
// make sure that the user didn't leave an unchecked OpenGL error
Expand Down Expand Up @@ -388,7 +462,7 @@ void RenderTarget::pushGLStates()
////////////////////////////////////////////////////////////
void RenderTarget::popGLStates()
{
if (setActive(true))
if (isActive(m_id) || setActive(true))
{
glCheck(glMatrixMode(GL_PROJECTION));
glCheck(glPopMatrix());
Expand Down Expand Up @@ -417,7 +491,7 @@ void RenderTarget::resetGLStates()
setActive(false);
#endif

if (setActive(true))
if (isActive(m_id) || setActive(true))
{
// Make sure that extensions are initialized
priv::ensureExtensionsInit();
Expand Down Expand Up @@ -458,6 +532,8 @@ void RenderTarget::resetGLStates()

// Set the default view
setView(getView());

m_cache.enable = true;
}
}

Expand Down Expand Up @@ -579,7 +655,7 @@ void RenderTarget::setupDraw(bool useVertexCache, const RenderStates& states)
if (useVertexCache)
{
// Since vertices are transformed, we must use an identity transform to render them
if (!m_cache.useVertexCache)
if (!m_cache.enable || !m_cache.useVertexCache)
glCheck(glLoadIdentity());
}
else
Expand All @@ -588,17 +664,30 @@ void RenderTarget::setupDraw(bool useVertexCache, const RenderStates& states)
}

// Apply the view
if (m_cache.viewChanged)
if (!m_cache.enable || m_cache.viewChanged)
applyCurrentView();

// Apply the blend mode
if (states.blendMode != m_cache.lastBlendMode)
if (!m_cache.enable || (states.blendMode != m_cache.lastBlendMode))
applyBlendMode(states.blendMode);

// Apply the texture
Uint64 textureId = states.texture ? states.texture->m_cacheId : 0;
if (textureId != m_cache.lastTextureId)
if (!m_cache.enable || (states.texture && states.texture->m_fboAttachment))
{
// If the texture is an FBO attachment, always rebind it
// in order to inform the OpenGL driver that we want changes
// made to it in other contexts to be visible here as well
// This saves us from having to call glFlush() in
// RenderTextureImplFBO which can be quite costly
// See: https://www.khronos.org/opengl/wiki/Memory_Model
applyTexture(states.texture);
}
else
{
Uint64 textureId = states.texture ? states.texture->m_cacheId : 0;
if (textureId != m_cache.lastTextureId)
applyTexture(states.texture);
}

// Apply the shader
if (states.shader)
Expand Down Expand Up @@ -630,6 +719,9 @@ void RenderTarget::cleanupDraw(const RenderStates& states)
// This prevents a bug where some drivers do not clear RenderTextures properly.
if (states.texture && states.texture->m_fboAttachment)
applyTexture(NULL);

// Re-enable the cache at the end of the draw if it was disabled
m_cache.enable = true;
}

} // namespace sf
Expand Down
10 changes: 8 additions & 2 deletions src/SFML/Graphics/RenderTexture.cpp
Expand Up @@ -147,15 +147,21 @@ bool RenderTexture::generateMipmap()
////////////////////////////////////////////////////////////
bool RenderTexture::setActive(bool active)
{
return m_impl && m_impl->activate(active);
bool result = m_impl && m_impl->activate(active);

// Update RenderTarget tracking
if (result)
RenderTarget::setActive(active);

return result;
}


////////////////////////////////////////////////////////////
void RenderTexture::display()
{
// Update the target texture
if (setActive(true))
if (priv::RenderTextureImplFBO::isAvailable() || setActive(true))
{
m_impl->updateTexture(m_texture.m_texture);
m_texture.m_pixelsFlipped = true;
Expand Down

0 comments on commit 0adde24

Please sign in to comment.