Moved most variables in sfml-graphics, GlResource and GlContext out of namespace scope.

This commit is contained in:
binary1248 2023-04-01 02:44:05 +02:00 committed by Chris Thrasher
parent 6a3e44bc4d
commit b39be46db0
11 changed files with 523 additions and 477 deletions

View File

@ -29,6 +29,8 @@
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
#include <SFML/Window/Export.hpp> #include <SFML/Window/Export.hpp>
#include <memory>
namespace sf namespace sf
{ {
@ -48,23 +50,24 @@ protected:
GlResource(); GlResource();
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Destructor /// \brief Register an OpenGL object to be destroyed when its containing context is destroyed
///
////////////////////////////////////////////////////////////
~GlResource();
////////////////////////////////////////////////////////////
/// \brief Register a function to be called when a context is destroyed
/// ///
/// This is used for internal purposes in order to properly /// This is used for internal purposes in order to properly
/// clean up OpenGL resources that cannot be shared between /// clean up OpenGL resources that cannot be shared between
/// contexts. /// contexts.
/// ///
/// \param callback Function to be called when a context is destroyed /// \param object Object to be destroyed when its containing context is destroyed
/// \param arg Argument to pass when calling the function
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
static void registerContextDestroyCallback(ContextDestroyCallback callback, void* arg); static void registerUnsharedGlObject(std::shared_ptr<void> object);
////////////////////////////////////////////////////////////
/// \brief Unregister an OpenGL object from its containing context
///
/// \param object Object to be unregistered
///
////////////////////////////////////////////////////////////
static void unregisterUnsharedGlObject(std::shared_ptr<void> object);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief RAII helper class to temporarily lock an available context for use /// \brief RAII helper class to temporarily lock an available context for use
@ -97,6 +100,12 @@ protected:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
TransientContextLock& operator=(const TransientContextLock&) = delete; TransientContextLock& operator=(const TransientContextLock&) = delete;
}; };
private:
////////////////////////////////////////////////////////////
// Member data
////////////////////////////////////////////////////////////
std::shared_ptr<void> m_sharedContext; //!< Shared context used to link all contexts together for resource sharing
}; };
} // namespace sf } // namespace sf

View File

@ -54,30 +54,35 @@ namespace
namespace RenderTargetImpl namespace RenderTargetImpl
{ {
// Mutex to protect ID generation and our context-RenderTarget-map // Mutex to protect ID generation and our context-RenderTarget-map
std::recursive_mutex mutex; std::recursive_mutex& getMutex()
{
static std::recursive_mutex mutex;
return mutex;
}
// Unique identifier, used for identifying RenderTargets when // Unique identifier, used for identifying RenderTargets when
// tracking the currently active RenderTarget within a given context // tracking the currently active RenderTarget within a given context
std::uint64_t getUniqueId() std::uint64_t getUniqueId()
{ {
const std::lock_guard lock(mutex); const std::lock_guard lock(getMutex());
static std::uint64_t id = 1; // start at 1, zero is "no RenderTarget" static std::uint64_t id = 1; // start at 1, zero is "no RenderTarget"
return id++; return id++;
} }
// Map to help us detect whether a different RenderTarget // Map to help us detect whether a different RenderTarget
// has been activated within a single context // has been activated within a single context
using ContextRenderTargetMap = std::unordered_map<std::uint64_t, std::uint64_t>; using ContextRenderTargetMap = std::unordered_map<std::uint64_t, std::uint64_t>;
ContextRenderTargetMap contextRenderTargetMap; ContextRenderTargetMap& getContextRenderTargetMap()
{
static ContextRenderTargetMap contextRenderTargetMap;
return contextRenderTargetMap;
}
// Check if a RenderTarget with the given ID is active in the current context // Check if a RenderTarget with the given ID is active in the current context
bool isActive(std::uint64_t id) bool isActive(std::uint64_t id)
{ {
const auto it = contextRenderTargetMap.find(sf::Context::getActiveContextId()); const auto it = getContextRenderTargetMap().find(sf::Context::getActiveContextId());
return (it != getContextRenderTargetMap().end()) && (it->second == id);
return (it != contextRenderTargetMap.end()) && (it->second == id);
} }
// Convert an sf::BlendMode::Factor constant to the corresponding OpenGL constant. // Convert an sf::BlendMode::Factor constant to the corresponding OpenGL constant.
@ -393,12 +398,13 @@ bool RenderTarget::setActive(bool active)
{ {
// Mark this RenderTarget as active or no longer active in the tracking map // Mark this RenderTarget as active or no longer active in the tracking map
{ {
const std::lock_guard lock(RenderTargetImpl::mutex); const std::lock_guard lock(RenderTargetImpl::getMutex());
const std::uint64_t contextId = Context::getActiveContextId(); const std::uint64_t contextId = Context::getActiveContextId();
using RenderTargetImpl::contextRenderTargetMap; using RenderTargetImpl::getContextRenderTargetMap;
const auto it = contextRenderTargetMap.find(contextId); auto& contextRenderTargetMap = getContextRenderTargetMap();
auto it = contextRenderTargetMap.find(contextId);
if (active) if (active)
{ {

View File

@ -36,111 +36,38 @@
#include <mutex> #include <mutex>
#include <ostream> #include <ostream>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
namespace
{
// Set to track all active FBO mappings
// This is used to free active FBOs while their owning
// RenderTextureImplFBO is still alive
std::unordered_set<std::unordered_map<std::uint64_t, unsigned int>*> frameBuffers;
// Set to track all stale FBOs
// This is used to free stale FBOs after their owning
// RenderTextureImplFBO has already been destroyed
// An FBO cannot be destroyed until it's containing context
// becomes active, so the destruction of the RenderTextureImplFBO
// has to be decoupled from the destruction of the FBOs themselves
std::set<std::pair<std::uint64_t, unsigned int>> staleFrameBuffers;
// Mutex to protect both active and stale frame buffer sets
std::recursive_mutex mutex;
// This function is called either when a RenderTextureImplFBO is
// destroyed or via contextDestroyCallback when context destruction
// might trigger deletion of its contained stale FBOs
void destroyStaleFBOs()
{
const std::uint64_t contextId = sf::Context::getActiveContextId();
for (auto it = staleFrameBuffers.begin(); it != staleFrameBuffers.end();)
{
if (it->first == contextId)
{
const auto frameBuffer = static_cast<GLuint>(it->second);
glCheck(GLEXT_glDeleteFramebuffers(1, &frameBuffer));
staleFrameBuffers.erase(it++);
}
else
{
++it;
}
}
}
// Callback that is called every time a context is destroyed
void contextDestroyCallback(void* /*arg*/)
{
const std::lock_guard lock(mutex);
const std::uint64_t contextId = sf::Context::getActiveContextId();
// Destroy active frame buffer objects
for (auto* frameBuffer : frameBuffers)
{
for (auto it = frameBuffer->begin(); it != frameBuffer->end(); ++it)
{
if (it->first == contextId)
{
const GLuint frameBufferId = it->second;
glCheck(GLEXT_glDeleteFramebuffers(1, &frameBufferId));
// Erase the entry from the RenderTextureImplFBO's map
frameBuffer->erase(it);
break;
}
}
}
// Destroy stale frame buffer objects
destroyStaleFBOs();
}
} // namespace
namespace sf::priv namespace sf::priv
{ {
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
RenderTextureImplFBO::RenderTextureImplFBO() struct RenderTextureImplFBO::FrameBufferObject
{ {
const std::lock_guard lock(mutex); FrameBufferObject()
{
// Register the context destruction callback // Create the framebuffer object
registerContextDestroyCallback(contextDestroyCallback, nullptr); glCheck(GLEXT_glGenFramebuffers(1, &object));
// Insert the new frame buffer mapping into the set of all active mappings
frameBuffers.insert(&m_frameBuffers);
frameBuffers.insert(&m_multisampleFrameBuffers);
} }
~FrameBufferObject()
{
if (object)
glCheck(GLEXT_glDeleteFramebuffers(1, &object));
}
GLuint object{};
};
////////////////////////////////////////////////////////////
RenderTextureImplFBO::RenderTextureImplFBO() = default;
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
RenderTextureImplFBO::~RenderTextureImplFBO() RenderTextureImplFBO::~RenderTextureImplFBO()
{ {
const TransientContextLock contextLock; const TransientContextLock contextLock;
const std::lock_guard lock(mutex);
// Remove the frame buffer mapping from the set of all active mappings
frameBuffers.erase(&m_frameBuffers);
frameBuffers.erase(&m_multisampleFrameBuffers);
// Destroy the color buffer // Destroy the color buffer
if (m_colorBuffer) if (m_colorBuffer)
{ {
@ -155,15 +82,22 @@ RenderTextureImplFBO::~RenderTextureImplFBO()
glCheck(GLEXT_glDeleteRenderbuffers(1, &depthStencilBuffer)); glCheck(GLEXT_glDeleteRenderbuffers(1, &depthStencilBuffer));
} }
// Move all frame buffer objects to stale set // Unregister FBOs with the contexts if they haven't already been destroyed
for (auto& [contextId, frameBufferId] : m_frameBuffers) for (auto& entry : m_frameBuffers)
staleFrameBuffers.emplace(contextId, frameBufferId); {
auto frameBuffer = entry.second.lock();
for (auto& [contextId, multisampleFrameBufferId] : m_multisampleFrameBuffers) if (frameBuffer)
staleFrameBuffers.emplace(contextId, multisampleFrameBufferId); unregisterUnsharedGlObject(std::move(frameBuffer));
}
// Clean up FBOs for (auto& entry : m_multisampleFrameBuffers)
destroyStaleFBOs(); {
auto frameBuffer = entry.second.lock();
if (frameBuffer)
unregisterUnsharedGlObject(std::move(frameBuffer));
}
} }
@ -422,15 +356,14 @@ bool RenderTextureImplFBO::create(const Vector2u& size, unsigned int textureId,
bool RenderTextureImplFBO::createFrameBuffer() bool RenderTextureImplFBO::createFrameBuffer()
{ {
// Create the framebuffer object // Create the framebuffer object
GLuint frameBuffer = 0; auto frameBuffer = std::make_shared<FrameBufferObject>();
glCheck(GLEXT_glGenFramebuffers(1, &frameBuffer));
if (!frameBuffer) if (!frameBuffer->object)
{ {
err() << "Impossible to create render texture (failed to create the frame buffer object)" << std::endl; err() << "Impossible to create render texture (failed to create the frame buffer object)" << std::endl;
return false; return false;
} }
glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_FRAMEBUFFER, frameBuffer)); glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_FRAMEBUFFER, frameBuffer->object));
// Link the depth/stencil renderbuffer to the frame buffer // Link the depth/stencil renderbuffer to the frame buffer
if (!m_multisample && m_depthStencilBuffer) if (!m_multisample && m_depthStencilBuffer)
@ -462,33 +395,30 @@ bool RenderTextureImplFBO::createFrameBuffer()
if (status != GLEXT_GL_FRAMEBUFFER_COMPLETE) if (status != GLEXT_GL_FRAMEBUFFER_COMPLETE)
{ {
glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_FRAMEBUFFER, 0)); glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_FRAMEBUFFER, 0));
glCheck(GLEXT_glDeleteFramebuffers(1, &frameBuffer));
err() << "Impossible to create render texture (failed to link the target texture to the frame buffer)" << std::endl; err() << "Impossible to create render texture (failed to link the target texture to the frame buffer)" << std::endl;
return false; return false;
} }
{
const std::lock_guard lock(mutex);
// Insert the FBO into our map // Insert the FBO into our map
m_frameBuffers.emplace(Context::getActiveContextId(), frameBuffer); m_frameBuffers.emplace(Context::getActiveContextId(), frameBuffer);
}
// Register the object with the current context so it is automatically destroyed
registerUnsharedGlObject(std::move(frameBuffer));
#ifndef SFML_OPENGL_ES #ifndef SFML_OPENGL_ES
if (m_multisample) if (m_multisample)
{ {
// Create the multisample framebuffer object // Create the multisample framebuffer object
GLuint multisampleFrameBuffer = 0; auto multisampleFrameBuffer = std::make_shared<FrameBufferObject>();
glCheck(GLEXT_glGenFramebuffers(1, &multisampleFrameBuffer));
if (!multisampleFrameBuffer) if (!multisampleFrameBuffer->object)
{ {
err() << "Impossible to create render texture (failed to create the multisample frame buffer object)" err() << "Impossible to create render texture (failed to create the multisample frame buffer object)"
<< std::endl; << std::endl;
return false; return false;
} }
glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_FRAMEBUFFER, multisampleFrameBuffer)); glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_FRAMEBUFFER, multisampleFrameBuffer->object));
// Link the multisample color buffer to the frame buffer // Link the multisample color buffer to the frame buffer
glCheck(GLEXT_glBindRenderbuffer(GLEXT_GL_RENDERBUFFER, m_colorBuffer)); glCheck(GLEXT_glBindRenderbuffer(GLEXT_GL_RENDERBUFFER, m_colorBuffer));
@ -517,19 +447,17 @@ bool RenderTextureImplFBO::createFrameBuffer()
if (status != GLEXT_GL_FRAMEBUFFER_COMPLETE) if (status != GLEXT_GL_FRAMEBUFFER_COMPLETE)
{ {
glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_FRAMEBUFFER, 0)); glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_FRAMEBUFFER, 0));
glCheck(GLEXT_glDeleteFramebuffers(1, &multisampleFrameBuffer));
err() << "Impossible to create render texture (failed to link the render buffers to the multisample frame " err() << "Impossible to create render texture (failed to link the render buffers to the multisample frame "
"buffer)" "buffer)"
<< std::endl; << std::endl;
return false; return false;
} }
{
const std::lock_guard lock(mutex);
// Insert the FBO into our map // Insert the FBO into our map
m_multisampleFrameBuffers.emplace(Context::getActiveContextId(), multisampleFrameBuffer); m_multisampleFrameBuffers.emplace(Context::getActiveContextId(), multisampleFrameBuffer);
}
// Register the object with the current context so it is automatically destroyed
registerUnsharedGlObject(std::move(multisampleFrameBuffer));
} }
#endif #endif
@ -575,29 +503,33 @@ bool RenderTextureImplFBO::activate(bool active)
// Lookup the FBO corresponding to the currently active context // Lookup the FBO corresponding to the currently active context
// If none is found, there is no FBO corresponding to the // If none is found, there is no FBO corresponding to the
// currently active context so we will have to create a new FBO // currently active context so we will have to create a new FBO
{
const std::lock_guard lock(mutex);
std::unordered_map<std::uint64_t, unsigned int>::iterator it;
if (m_multisample) if (m_multisample)
{ {
it = m_multisampleFrameBuffers.find(contextId); const auto it = m_multisampleFrameBuffers.find(contextId);
if (it != m_multisampleFrameBuffers.end()) if (it != m_multisampleFrameBuffers.end())
{ {
glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_FRAMEBUFFER, it->second)); auto frameBuffer = it->second.lock();
if (frameBuffer)
{
glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_FRAMEBUFFER, frameBuffer->object));
return true; return true;
} }
} }
}
else else
{ {
it = m_frameBuffers.find(contextId); const auto it = m_frameBuffers.find(contextId);
if (it != m_frameBuffers.end()) if (it != m_frameBuffers.end())
{ {
glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_FRAMEBUFFER, it->second)); auto frameBuffer = it->second.lock();
if (frameBuffer)
{
glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_FRAMEBUFFER, frameBuffer->object));
return true; return true;
} }
@ -630,15 +562,18 @@ void RenderTextureImplFBO::updateTexture(unsigned int)
{ {
const std::uint64_t contextId = Context::getActiveContextId(); const std::uint64_t contextId = Context::getActiveContextId();
const std::lock_guard lock(mutex);
const auto frameBufferIt = m_frameBuffers.find(contextId); const auto frameBufferIt = m_frameBuffers.find(contextId);
const auto multisampleIt = m_multisampleFrameBuffers.find(contextId); const auto multisampleIt = m_multisampleFrameBuffers.find(contextId);
if ((frameBufferIt != m_frameBuffers.end()) && (multisampleIt != m_multisampleFrameBuffers.end())) if ((frameBufferIt != m_frameBuffers.end()) && (multisampleIt != m_multisampleFrameBuffers.end()))
{
auto frameBuffer = frameBufferIt->second.lock();
auto multiSampleFrameBuffer = multisampleIt->second.lock();
if (frameBuffer && multiSampleFrameBuffer)
{ {
// Set up the blit target (draw framebuffer) and blit (from the read framebuffer, our multisample FBO) // Set up the blit target (draw framebuffer) and blit (from the read framebuffer, our multisample FBO)
glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_DRAW_FRAMEBUFFER, frameBufferIt->second)); glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_DRAW_FRAMEBUFFER, frameBuffer->object));
glCheck(GLEXT_glBlitFramebuffer(0, glCheck(GLEXT_glBlitFramebuffer(0,
0, 0,
static_cast<GLint>(m_size.x), static_cast<GLint>(m_size.x),
@ -649,7 +584,8 @@ void RenderTextureImplFBO::updateTexture(unsigned int)
static_cast<GLint>(m_size.y), static_cast<GLint>(m_size.y),
GL_COLOR_BUFFER_BIT, GL_COLOR_BUFFER_BIT,
GL_NEAREST)); GL_NEAREST));
glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_DRAW_FRAMEBUFFER, multisampleIt->second)); glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_DRAW_FRAMEBUFFER, multiSampleFrameBuffer->object));
}
} }
} }

View File

@ -137,8 +137,12 @@ private:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Member data // Member data
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
std::unordered_map<std::uint64_t, unsigned int> m_frameBuffers; //!< OpenGL frame buffer objects per context struct FrameBufferObject;
std::unordered_map<std::uint64_t, unsigned int> m_multisampleFrameBuffers; //!< Optional per-context OpenGL frame buffer objects with multisample attachments
using FrameBufferObjectMap = std::unordered_map<std::uint64_t, std::weak_ptr<FrameBufferObject>>;
FrameBufferObjectMap m_frameBuffers; //!< OpenGL frame buffer objects per context
FrameBufferObjectMap m_multisampleFrameBuffers; //!< Optional per-context OpenGL frame buffer objects with multisample attachments
unsigned int m_depthStencilBuffer{}; //!< Optional depth/stencil buffer attached to the frame buffer unsigned int m_depthStencilBuffer{}; //!< Optional depth/stencil buffer attached to the frame buffer
unsigned int m_colorBuffer{}; //!< Optional multisample color buffer attached to the frame buffer unsigned int m_colorBuffer{}; //!< Optional multisample color buffer attached to the frame buffer
Vector2u m_size; //!< Width and height of the attachments Vector2u m_size; //!< Width and height of the attachments

View File

@ -40,7 +40,6 @@
#include <fstream> #include <fstream>
#include <iomanip> #include <iomanip>
#include <mutex>
#include <ostream> #include <ostream>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -61,20 +60,17 @@
namespace namespace
{ {
std::recursive_mutex isAvailableMutex;
GLint checkMaxTextureUnits()
{
GLint maxUnits = 0;
glCheck(glGetIntegerv(GLEXT_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxUnits));
return maxUnits;
}
// Retrieve the maximum number of texture units available // Retrieve the maximum number of texture units available
std::size_t getMaxTextureUnits() std::size_t getMaxTextureUnits()
{ {
static const GLint maxUnits = checkMaxTextureUnits(); static const GLint maxUnits = []()
{
GLint value = 0;
glCheck(glGetIntegerv(GLEXT_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &value));
return value;
}();
return static_cast<std::size_t>(maxUnits); return static_cast<std::size_t>(maxUnits);
} }
@ -747,23 +743,16 @@ void Shader::bind(const Shader* shader)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool Shader::isAvailable() bool Shader::isAvailable()
{ {
const std::lock_guard lock(isAvailableMutex); static const bool available = []()
static bool checked = false;
static bool available = false;
if (!checked)
{ {
checked = true;
const TransientContextLock contextLock; const TransientContextLock contextLock;
// Make sure that extensions are initialized // Make sure that extensions are initialized
sf::priv::ensureExtensionsInit(); sf::priv::ensureExtensionsInit();
available = GLEXT_multitexture && GLEXT_shading_language_100 && GLEXT_shader_objects && GLEXT_vertex_shader && return GLEXT_multitexture && GLEXT_shading_language_100 && GLEXT_shader_objects && GLEXT_vertex_shader &&
GLEXT_fragment_shader; GLEXT_fragment_shader;
} }();
return available; return available;
} }
@ -772,22 +761,15 @@ bool Shader::isAvailable()
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool Shader::isGeometryAvailable() bool Shader::isGeometryAvailable()
{ {
const std::lock_guard lock(isAvailableMutex); static const bool available = []()
static bool checked = false;
static bool available = false;
if (!checked)
{ {
checked = true;
const TransientContextLock contextLock; const TransientContextLock contextLock;
// Make sure that extensions are initialized // Make sure that extensions are initialized
sf::priv::ensureExtensionsInit(); sf::priv::ensureExtensionsInit();
available = isAvailable() && (GLEXT_geometry_shader4 || GLEXT_GL_VERSION_3_2); return isAvailable() && (GLEXT_geometry_shader4 || GLEXT_GL_VERSION_3_2);
} }();
return available; return available;
} }

View File

@ -71,6 +71,7 @@ Texture::Texture() : m_cacheId(TextureImpl::getUniqueId())
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Texture::Texture(const Texture& copy) : Texture::Texture(const Texture& copy) :
GlResource(copy),
m_isSmooth(copy.m_isSmooth), m_isSmooth(copy.m_isSmooth),
m_sRgb(copy.m_sRgb), m_sRgb(copy.m_sRgb),
m_isRepeated(copy.m_isRepeated), m_isRepeated(copy.m_isRepeated),

View File

@ -84,7 +84,10 @@ VertexBuffer::VertexBuffer(PrimitiveType type, Usage usage) : m_primitiveType(ty
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
VertexBuffer::VertexBuffer(const VertexBuffer& copy) : m_primitiveType(copy.m_primitiveType), m_usage(copy.m_usage) VertexBuffer::VertexBuffer(const VertexBuffer& copy) :
GlResource(copy),
m_primitiveType(copy.m_primitiveType),
m_usage(copy.m_usage)
{ {
if (copy.m_buffer && copy.m_size) if (copy.m_buffer && copy.m_size)
{ {

View File

@ -167,17 +167,6 @@ namespace
// A nested named namespace is used here to allow unity builds of SFML. // A nested named namespace is used here to allow unity builds of SFML.
namespace GlContextImpl namespace GlContextImpl
{ {
// AMD drivers have issues with internal synchronization
// We need to make sure that no operating system context
// or pixel format operations are performed simultaneously
// This mutex is also used to protect the shared context
// from being locked on multiple threads and for managing
// the resource count
std::recursive_mutex mutex;
// OpenGL resources counter
unsigned int resourceCount = 0;
// This structure contains all the state necessary to // This structure contains all the state necessary to
// track current context information for each thread // track current context information for each thread
struct CurrentContext struct CurrentContext
@ -185,101 +174,87 @@ struct CurrentContext
std::uint64_t id{}; std::uint64_t id{};
sf::priv::GlContext* ptr{}; sf::priv::GlContext* ptr{};
unsigned int transientCount{}; unsigned int transientCount{};
};
// This per-thread variable holds the current context information for each thread // This per-thread variable holds the current context information for each thread
static CurrentContext& get()
{
thread_local CurrentContext currentContext; thread_local CurrentContext currentContext;
return currentContext;
}
// The hidden, inactive context that will be shared with all other contexts private:
std::unique_ptr<ContextType> sharedContext; // Private constructor to prevent CurrentContext from being constructed outside of get()
CurrentContext() = default;
};
} // namespace GlContextImpl
} // namespace
// Unique identifier, used for identifying contexts when managing unshareable OpenGL resources
std::atomic<std::uint64_t> nextContextId(1); // start at 1, zero is "no context"
// Set containing callback functions to be called whenever a
// context is going to be destroyed
// Unshareable OpenGL resources rely on this to clean up properly
// whenever a context containing them is destroyed
using ContextDestroyCallbacks = std::unordered_map<sf::ContextDestroyCallback, void*>;
ContextDestroyCallbacks contextDestroyCallbacks;
namespace sf::priv
{
// This structure contains all the state necessary to // This structure contains all the state necessary to
// track TransientContext usage // track SharedContext usage
struct TransientContext struct GlContext::SharedContext
{ {
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Constructor /// \brief Constructor
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
TransientContext() SharedContext()
{ {
// TransientContext should never be created if there is const std::lock_guard lock(mutex);
// already a context active on the current thread
assert(!currentContext.id);
std::unique_lock lock(mutex); context.emplace(nullptr);
context->initialize(sf::ContextSettings());
if (resourceCount == 0) loadExtensions();
{
// No GlResources, no shared context yet
assert(!sharedContext);
// Create a Context object for temporary use context->setActive(false);
context.emplace();
}
else
{
// GlResources exist, currentContextId not yet set
assert(sharedContext);
// Lock the shared context for temporary use
sharedContextLock = std::move(lock);
sharedContext->setActive(true);
}
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Destructor /// \brief Get weak_ptr
///
/// \return weak_ptr to the SharedContext
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
~TransientContext() static std::weak_ptr<SharedContext>& getWeakPtr()
{ {
if (sharedContextLock) static std::weak_ptr<SharedContext> weakSharedContext;
sharedContext->setActive(false); return weakSharedContext;
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Deleted copy constructor /// \brief Get shared_ptr to the shared context
///
/// Create new object if one doesn't already exist.
///
/// \return shared_ptr to the shared context
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
TransientContext(const TransientContext&) = delete; static std::shared_ptr<SharedContext> get()
{
auto& weakSharedContext = getWeakPtr();
auto sharedContext = weakSharedContext.lock();
if (!sharedContext)
{
sharedContext = std::make_shared<GlContext::SharedContext>();
weakSharedContext = sharedContext;
}
return sharedContext;
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Deleted copy assignment /// \brief Load our extensions vector with the supported extensions
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
TransientContext& operator=(const TransientContext&) = delete;
///////////////////////////////////////////////////////////
// Member data
////////////////////////////////////////////////////////////
std::optional<sf::Context> context;
std::unique_lock<std::recursive_mutex> sharedContextLock;
};
// This per-thread variable tracks if and how a transient
// context is currently being used on the current thread
thread_local std::optional<TransientContext> transientContext;
// Supported OpenGL extensions
std::vector<std::string> extensions;
// Load our extensions vector with the supported extensions
void loadExtensions() void loadExtensions()
{ {
auto glGetErrorFunc = reinterpret_cast<glGetErrorFuncType>(sf::priv::GlContext::getFunction("glGetError")); auto glGetErrorFunc = reinterpret_cast<glGetErrorFuncType>(getFunction("glGetError"));
auto glGetIntegervFunc = reinterpret_cast<glGetIntegervFuncType>(sf::priv::GlContext::getFunction("glGetIntegerv")); auto glGetIntegervFunc = reinterpret_cast<glGetIntegervFuncType>(getFunction("glGetIntegerv"));
auto glGetStringFunc = reinterpret_cast<glGetStringFuncType>(sf::priv::GlContext::getFunction("glGetString")); auto glGetStringFunc = reinterpret_cast<glGetStringFuncType>(getFunction("glGetString"));
if (!glGetErrorFunc || !glGetIntegervFunc || !glGetStringFunc) if (!glGetErrorFunc || !glGetIntegervFunc || !glGetStringFunc)
return; return;
@ -288,7 +263,7 @@ void loadExtensions()
int majorVersion = 0; int majorVersion = 0;
glGetIntegervFunc(GL_MAJOR_VERSION, &majorVersion); glGetIntegervFunc(GL_MAJOR_VERSION, &majorVersion);
auto glGetStringiFunc = reinterpret_cast<glGetStringiFuncType>(sf::priv::GlContext::getFunction("glGetStringi")); auto glGetStringiFunc = reinterpret_cast<glGetStringiFuncType>(getFunction("glGetStringi"));
if (glGetErrorFunc() == GL_INVALID_ENUM || !majorVersion || !glGetStringiFunc) if (glGetErrorFunc() == GL_INVALID_ENUM || !majorVersion || !glGetStringiFunc)
{ {
@ -331,105 +306,211 @@ void loadExtensions()
} }
} }
// Helper to parse OpenGL version strings // AMD drivers have issues with internal synchronization
bool parseVersionString(const char* version, const char* prefix, unsigned int& major, unsigned int& minor) // We need to make sure that no operating system context
{ // or pixel format operations are performed simultaneously
const std::size_t prefixLength = std::strlen(prefix); // This mutex is also used to protect the shared context
// from being locked on multiple threads
std::recursive_mutex mutex;
if ((std::strlen(version) >= (prefixLength + 3)) && (std::strncmp(version, prefix, prefixLength) == 0) && // Supported OpenGL extensions
std::isdigit(version[prefixLength]) && (version[prefixLength + 1] == '.') && std::vector<std::string> extensions;
std::isdigit(version[prefixLength + 2]))
{
major = static_cast<unsigned int>(version[prefixLength] - '0');
minor = static_cast<unsigned int>(version[prefixLength + 2] - '0');
return true; // The hidden, inactive context that will be shared with all other contexts
} std::optional<ContextType> context;
};
return false;
}
} // namespace GlContextImpl
} // namespace
namespace sf::priv // This structure contains all the state necessary to
// track TransientContext usage
struct GlContext::TransientContext
{ {
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void GlContext::initResource() /// \brief Constructor
{ ///
using GlContextImpl::loadExtensions;
using GlContextImpl::mutex;
using GlContextImpl::resourceCount;
using GlContextImpl::sharedContext;
// Protect from concurrent access
const std::lock_guard lock(mutex);
// If this is the very first resource, trigger the global context initialization
if (resourceCount == 0)
{
if (sharedContext)
{
// Increment the resources counter
++resourceCount;
return;
}
// Create the shared context
sharedContext = std::make_unique<ContextType>(nullptr);
sharedContext->initialize(ContextSettings());
// Load our extensions vector
loadExtensions();
// Deactivate the shared context so that others can activate it when necessary
sharedContext->setActive(false);
}
// Increment the resources counter
++resourceCount;
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void GlContext::cleanupResource() TransientContext()
{ {
using GlContextImpl::mutex; // TransientContext should never be created if there is
using GlContextImpl::resourceCount; // already a context active on the current thread
using GlContextImpl::sharedContext; assert(!GlContextImpl::CurrentContext::get().id);
// Protect from concurrent access // Lock ourselves so we don't create a new object if one doesn't already exist
const std::lock_guard lock(mutex); sharedContext = SharedContext::getWeakPtr().lock();
// Decrement the resources counter
--resourceCount;
// If there's no more resource alive, we can trigger the global context cleanup
if (resourceCount == 0)
{
if (!sharedContext) if (!sharedContext)
return; {
// Create a Context object for temporary use
// Destroy the shared context context.emplace();
sharedContext.reset();
} }
else
{
// GlResources exist, currentContextId not yet set
assert(sharedContext);
// Lock the shared context for temporary use
sharedContextLock = std::unique_lock(sharedContext->mutex);
sharedContext->context->setActive(true);
}
}
////////////////////////////////////////////////////////////
/// \brief Destructor
///
////////////////////////////////////////////////////////////
~TransientContext()
{
if (sharedContextLock)
sharedContext->context->setActive(false);
}
////////////////////////////////////////////////////////////
/// \brief Deleted copy constructor
///
////////////////////////////////////////////////////////////
TransientContext(const TransientContext&) = delete;
////////////////////////////////////////////////////////////
/// \brief Deleted copy assignment
///
////////////////////////////////////////////////////////////
TransientContext& operator=(const TransientContext&) = delete;
////////////////////////////////////////////////////////////
/// \brief Get the thread local TransientContext
///
/// This per-thread variable tracks if and how a transient
/// context is currently being used on the current thread
///
/// \return The thread local TransientContext
///
////////////////////////////////////////////////////////////
static std::optional<TransientContext>& get()
{
thread_local std::optional<TransientContext> transientContext;
return transientContext;
}
///////////////////////////////////////////////////////////
// Member data
////////////////////////////////////////////////////////////
std::optional<sf::Context> context;
std::unique_lock<std::recursive_mutex> sharedContextLock;
std::shared_ptr<SharedContext> sharedContext;
};
// This structure contains all the implementation data we
// don't want to expose through the visible interface
struct GlContext::Impl
{
////////////////////////////////////////////////////////////
/// \brief Constructor
///
////////////////////////////////////////////////////////////
Impl() :
m_id(
[]()
{
static std::atomic<std::uint64_t> id(1); // start at 1, zero is "no context"
return id.fetch_add(1);
}())
{
auto& weakUnsharedGlObjects = getWeakUnsharedGlObjects();
unsharedGlObjects = weakUnsharedGlObjects.lock();
if (!unsharedGlObjects)
{
unsharedGlObjects = std::make_shared<UnsharedGlObjects>();
weakUnsharedGlObjects = unsharedGlObjects;
}
}
// Structure to track which unshared object belongs to which context
struct UnsharedGlObject
{
std::uint64_t contextId;
std::shared_ptr<void> object;
};
using UnsharedGlObjects = std::vector<UnsharedGlObject>;
////////////////////////////////////////////////////////////
/// \brief Get weak_ptr to unshared objects
///
/// \return weak_ptr to unshared objects
///
////////////////////////////////////////////////////////////
static std::weak_ptr<UnsharedGlObjects>& getWeakUnsharedGlObjects()
{
static std::weak_ptr<UnsharedGlObjects> weakUnsharedGlObjects;
return weakUnsharedGlObjects;
}
////////////////////////////////////////////////////////////
/// \brief Get mutex protecting unshared objects
///
/// \return Mutex protecting unshared objects
///
////////////////////////////////////////////////////////////
static std::mutex& getUnsharedGlObjectsMutex()
{
static std::mutex mutex;
return mutex;
}
///////////////////////////////////////////////////////////
// Member data
////////////////////////////////////////////////////////////
std::shared_ptr<UnsharedGlObjects> unsharedGlObjects; //!< The current object's handle to unshared objects
const std::uint64_t m_id; //!< Unique identifier, used for identifying contexts when managing unshareable OpenGL resources
};
////////////////////////////////////////////////////////////
std::shared_ptr<void> GlContext::getSharedContext()
{
return GlContext::SharedContext::get();
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void GlContext::registerContextDestroyCallback(ContextDestroyCallback callback, void* arg) void GlContext::registerUnsharedGlObject(std::shared_ptr<void> object)
{ {
GlContextImpl::contextDestroyCallbacks.emplace(callback, arg); if (const std::lock_guard lock(Impl::getUnsharedGlObjectsMutex());
auto unsharedGlObjects = Impl::getWeakUnsharedGlObjects().lock())
unsharedGlObjects->emplace_back(Impl::UnsharedGlObject{GlContextImpl::CurrentContext::get().id, std::move(object)});
}
////////////////////////////////////////////////////////////
void GlContext::unregisterUnsharedGlObject(std::shared_ptr<void> object)
{
if (const std::lock_guard lock(Impl::getUnsharedGlObjectsMutex());
auto unsharedGlObjects = Impl::getWeakUnsharedGlObjects().lock())
{
// Find the object in unshared objects and remove it if its associated context is currently active
// This will trigger the destructor of the object since shared_ptr
// in unshared objects should be the only one existing
auto iter = std::find_if(unsharedGlObjects->begin(),
unsharedGlObjects->end(),
[&](const Impl::UnsharedGlObject& obj) {
return (obj.object == object) &&
(obj.contextId == GlContextImpl::CurrentContext::get().id);
});
if (iter != unsharedGlObjects->end())
unsharedGlObjects->erase(iter);
}
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void GlContext::acquireTransientContext() void GlContext::acquireTransientContext()
{ {
using GlContextImpl::currentContext; auto& currentContext = GlContextImpl::CurrentContext::get();
using GlContextImpl::TransientContext;
using GlContextImpl::transientContext;
// Fast path if we already have a context active on this thread // Fast path if we already have a context active on this thread
if (currentContext.id) if (currentContext.id)
@ -443,7 +524,7 @@ void GlContext::acquireTransientContext()
// If currentContextId is not set, this must be the first // If currentContextId is not set, this must be the first
// TransientContextLock on this thread, construct the state object // TransientContextLock on this thread, construct the state object
transientContext.emplace(); TransientContext::get().emplace();
// Make sure a context is active at this point // Make sure a context is active at this point
assert(currentContext.id); assert(currentContext.id);
@ -453,8 +534,7 @@ void GlContext::acquireTransientContext()
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void GlContext::releaseTransientContext() void GlContext::releaseTransientContext()
{ {
using GlContextImpl::currentContext; auto& currentContext = GlContextImpl::CurrentContext::get();
using GlContextImpl::transientContext;
// Make sure a context was left active after acquireTransientContext() was called // Make sure a context was left active after acquireTransientContext() was called
assert(currentContext.id); assert(currentContext.id);
@ -468,34 +548,29 @@ void GlContext::releaseTransientContext()
// If currentContextId is set and currentContextTransientCount is 0, // If currentContextId is set and currentContextTransientCount is 0,
// this is the last TransientContextLock that is released, destroy the state object // this is the last TransientContextLock that is released, destroy the state object
transientContext.reset(); TransientContext::get().reset();
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
std::unique_ptr<GlContext> GlContext::create() std::unique_ptr<GlContext> GlContext::create()
{ {
using GlContextImpl::mutex;
using GlContextImpl::sharedContext;
// Make sure that there's an active context (context creation may need extensions, and thus a valid context) // Make sure that there's an active context (context creation may need extensions, and thus a valid context)
assert(sharedContext != nullptr); auto sharedContext = SharedContext::get();
const std::lock_guard lock(mutex); const std::lock_guard lock(sharedContext->mutex);
std::unique_ptr<GlContext> context; std::unique_ptr<GlContext> context;
// We don't use acquireTransientContext here since we have // We don't use acquireTransientContext here since we have
// to ensure we have exclusive access to the shared context // to ensure we have exclusive access to the shared context
// in order to make sure it is not active during context creation // in order to make sure it is not active during context creation
{ sharedContext->context->setActive(true);
sharedContext->setActive(true);
// Create the context // Create the context
context = std::make_unique<ContextType>(sharedContext.get()); context = std::make_unique<ContextType>(&sharedContext->context.value());
sharedContext->setActive(false); sharedContext->context->setActive(false);
}
context->initialize(ContextSettings()); context->initialize(ContextSettings());
@ -506,31 +581,26 @@ std::unique_ptr<GlContext> GlContext::create()
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
std::unique_ptr<GlContext> GlContext::create(const ContextSettings& settings, const WindowImpl& owner, unsigned int bitsPerPixel) std::unique_ptr<GlContext> GlContext::create(const ContextSettings& settings, const WindowImpl& owner, unsigned int bitsPerPixel)
{ {
using GlContextImpl::loadExtensions;
using GlContextImpl::mutex;
using GlContextImpl::resourceCount;
using GlContextImpl::sharedContext;
// Make sure that there's an active context (context creation may need extensions, and thus a valid context) // Make sure that there's an active context (context creation may need extensions, and thus a valid context)
assert(sharedContext != nullptr); auto sharedContext = SharedContext::get();
const std::lock_guard lock(mutex); const std::lock_guard lock(sharedContext->mutex);
// If resourceCount is 1 we know that we are inside sf::Context or sf::Window // If use_count is 2 (GlResource + sharedContext) we know that we are inside sf::Context or sf::Window
// Only in this situation we allow the user to indirectly re-create the shared context as a core context // Only in this situation we allow the user to indirectly re-create the shared context as a core context
// Check if we need to convert our shared context into a core context // Check if we need to convert our shared context into a core context
if ((resourceCount == 1) && (settings.attributeFlags & ContextSettings::Core) && if ((sharedContext.use_count() == 2) && (settings.attributeFlags & ContextSettings::Core) &&
!(sharedContext->m_settings.attributeFlags & ContextSettings::Core)) !(sharedContext->context->m_settings.attributeFlags & ContextSettings::Core))
{ {
// Re-create our shared context as a core context // Re-create our shared context as a core context
const ContextSettings sharedSettings(0, 0, 0, settings.majorVersion, settings.minorVersion, settings.attributeFlags); const ContextSettings sharedSettings(0, 0, 0, settings.majorVersion, settings.minorVersion, settings.attributeFlags);
sharedContext = std::make_unique<ContextType>(nullptr, sharedSettings, Vector2u(1, 1)); sharedContext->context.emplace(nullptr, sharedSettings, Vector2u(1, 1));
sharedContext->initialize(sharedSettings); sharedContext->context->initialize(sharedSettings);
// Reload our extensions vector // Reload our extensions vector
loadExtensions(); sharedContext->loadExtensions();
} }
std::unique_ptr<GlContext> context; std::unique_ptr<GlContext> context;
@ -538,14 +608,12 @@ std::unique_ptr<GlContext> GlContext::create(const ContextSettings& settings, co
// We don't use acquireTransientContext here since we have // We don't use acquireTransientContext here since we have
// to ensure we have exclusive access to the shared context // to ensure we have exclusive access to the shared context
// in order to make sure it is not active during context creation // in order to make sure it is not active during context creation
{ sharedContext->context->setActive(true);
sharedContext->setActive(true);
// Create the context // Create the context
context = std::make_unique<ContextType>(sharedContext.get(), settings, owner, bitsPerPixel); context = std::make_unique<ContextType>(&sharedContext->context.value(), settings, owner, bitsPerPixel);
sharedContext->setActive(false); sharedContext->context->setActive(false);
}
context->initialize(settings); context->initialize(settings);
context->checkSettings(settings); context->checkSettings(settings);
@ -557,31 +625,26 @@ std::unique_ptr<GlContext> GlContext::create(const ContextSettings& settings, co
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
std::unique_ptr<GlContext> GlContext::create(const ContextSettings& settings, const Vector2u& size) std::unique_ptr<GlContext> GlContext::create(const ContextSettings& settings, const Vector2u& size)
{ {
using GlContextImpl::loadExtensions;
using GlContextImpl::mutex;
using GlContextImpl::resourceCount;
using GlContextImpl::sharedContext;
// Make sure that there's an active context (context creation may need extensions, and thus a valid context) // Make sure that there's an active context (context creation may need extensions, and thus a valid context)
assert(sharedContext != nullptr); auto sharedContext = SharedContext::get();
const std::lock_guard lock(mutex); const std::lock_guard lock(sharedContext->mutex);
// If resourceCount is 1 we know that we are inside sf::Context or sf::Window // If use_count is 2 (GlResource + sharedContext) we know that we are inside sf::Context or sf::Window
// Only in this situation we allow the user to indirectly re-create the shared context as a core context // Only in this situation we allow the user to indirectly re-create the shared context as a core context
// Check if we need to convert our shared context into a core context // Check if we need to convert our shared context into a core context
if ((resourceCount == 1) && (settings.attributeFlags & ContextSettings::Core) && if ((sharedContext.use_count() == 2) && (settings.attributeFlags & ContextSettings::Core) &&
!(sharedContext->m_settings.attributeFlags & ContextSettings::Core)) !(sharedContext->context->m_settings.attributeFlags & ContextSettings::Core))
{ {
// Re-create our shared context as a core context // Re-create our shared context as a core context
const ContextSettings sharedSettings(0, 0, 0, settings.majorVersion, settings.minorVersion, settings.attributeFlags); const ContextSettings sharedSettings(0, 0, 0, settings.majorVersion, settings.minorVersion, settings.attributeFlags);
sharedContext = std::make_unique<ContextType>(nullptr, sharedSettings, Vector2u(1, 1)); sharedContext->context.emplace(nullptr, sharedSettings, Vector2u(1, 1));
sharedContext->initialize(sharedSettings); sharedContext->context->initialize(sharedSettings);
// Reload our extensions vector // Reload our extensions vector
loadExtensions(); sharedContext->loadExtensions();
} }
std::unique_ptr<GlContext> context; std::unique_ptr<GlContext> context;
@ -589,14 +652,12 @@ std::unique_ptr<GlContext> GlContext::create(const ContextSettings& settings, co
// We don't use acquireTransientContext here since we have // We don't use acquireTransientContext here since we have
// to ensure we have exclusive access to the shared context // to ensure we have exclusive access to the shared context
// in order to make sure it is not active during context creation // in order to make sure it is not active during context creation
{ sharedContext->context->setActive(true);
sharedContext->setActive(true);
// Create the context // Create the context
context = std::make_unique<ContextType>(sharedContext.get(), settings, size); context = std::make_unique<ContextType>(&sharedContext->context.value(), settings, size);
sharedContext->setActive(false); sharedContext->context->setActive(false);
}
context->initialize(settings); context->initialize(settings);
context->checkSettings(settings); context->checkSettings(settings);
@ -608,15 +669,27 @@ std::unique_ptr<GlContext> GlContext::create(const ContextSettings& settings, co
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool GlContext::isExtensionAvailable(const char* name) bool GlContext::isExtensionAvailable(const char* name)
{ {
using GlContextImpl::extensions; // If this function is called before any context is available,
return std::find(extensions.begin(), extensions.end(), name) != extensions.end(); // the shared context will be created for the duration of this call
const auto sharedContext = SharedContext::get();
return std::find(sharedContext->extensions.begin(), sharedContext->extensions.end(), name) !=
sharedContext->extensions.end();
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
GlFunctionPointer GlContext::getFunction(const char* name) GlFunctionPointer GlContext::getFunction(const char* name)
{ {
const std::lock_guard lock(GlContextImpl::mutex); // Make sure we don't try to create the shared context here since
// setActive can be called during construction and lead to infinite recursion
auto* sharedContext = SharedContext::getWeakPtr().lock().get();
// We can't and don't need to lock when we are currently creating the shared context
std::unique_lock<std::recursive_mutex> lock;
if (sharedContext)
lock = std::unique_lock(sharedContext->mutex);
return ContextType::getFunction(name); return ContextType::getFunction(name);
} }
@ -625,27 +698,23 @@ GlFunctionPointer GlContext::getFunction(const char* name)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
const GlContext* GlContext::getActiveContext() const GlContext* GlContext::getActiveContext()
{ {
using GlContextImpl::currentContext; return GlContextImpl::CurrentContext::get().ptr;
return currentContext.ptr;
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
std::uint64_t GlContext::getActiveContextId() std::uint64_t GlContext::getActiveContextId()
{ {
using GlContextImpl::currentContext; return GlContextImpl::CurrentContext::get().id;
return currentContext.id;
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
GlContext::~GlContext() GlContext::~GlContext()
{ {
using GlContextImpl::currentContext; auto& currentContext = GlContextImpl::CurrentContext::get();
if (m_id == currentContext.id) if (m_impl->m_id == currentContext.id)
{ {
currentContext.id = 0; currentContext.id = 0;
currentContext.ptr = nullptr; currentContext.ptr = nullptr;
@ -663,20 +732,27 @@ const ContextSettings& GlContext::getSettings() const
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool GlContext::setActive(bool active) bool GlContext::setActive(bool active)
{ {
using GlContextImpl::currentContext; auto& currentContext = GlContextImpl::CurrentContext::get();
using GlContextImpl::mutex;
// Make sure we don't try to create the shared context here since
// setActive can be called during construction and lead to infinite recursion
auto* sharedContext = SharedContext::getWeakPtr().lock().get();
if (active) if (active)
{ {
if (m_id != currentContext.id) if (m_impl->m_id != currentContext.id)
{ {
const std::lock_guard lock(mutex); // We can't and don't need to lock when we are currently creating the shared context
std::unique_lock<std::recursive_mutex> lock;
if (sharedContext)
lock = std::unique_lock(sharedContext->mutex);
// Activate the context // Activate the context
if (makeCurrent(true)) if (makeCurrent(true))
{ {
// Set it as the new current context for this thread // Set it as the new current context for this thread
currentContext.id = m_id; currentContext.id = m_impl->m_id;
currentContext.ptr = this; currentContext.ptr = this;
return true; return true;
} }
@ -693,9 +769,13 @@ bool GlContext::setActive(bool active)
} }
else else
{ {
if (m_id == currentContext.id) if (m_impl->m_id == currentContext.id)
{ {
const std::lock_guard lock(mutex); // We can't and don't need to lock when we are currently creating the shared context
std::unique_lock<std::recursive_mutex> lock;
if (sharedContext)
lock = std::unique_lock(sharedContext->mutex);
// Deactivate the context // Deactivate the context
if (makeCurrent(false)) if (makeCurrent(false))
@ -719,7 +799,7 @@ bool GlContext::setActive(bool active)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
GlContext::GlContext() : m_id(GlContextImpl::nextContextId.fetch_add(1)) GlContext::GlContext() : m_impl(std::make_unique<Impl>())
{ {
// Nothing to do // Nothing to do
} }
@ -765,23 +845,34 @@ int GlContext::evaluateFormat(
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void GlContext::cleanupUnsharedResources() void GlContext::cleanupUnsharedResources()
{ {
using GlContextImpl::ContextDestroyCallbacks; const auto& currentContext = GlContextImpl::CurrentContext::get();
using GlContextImpl::contextDestroyCallbacks;
using GlContextImpl::currentContext;
// Save the current context so we can restore it later // Save the current context so we can restore it later
GlContext* contextToRestore = currentContext.ptr; GlContext* contextToRestore = currentContext.ptr;
// If this context is already active there is no need to save it // If this context is already active there is no need to save it
if (m_id == currentContext.id) if (m_impl->m_id == currentContext.id)
contextToRestore = nullptr; contextToRestore = nullptr;
// Make this context active so resources can be freed // Make this context active so resources can be freed
setActive(true); setActive(true);
// Call the registered destruction callbacks {
for (auto& [callback, ptr] : contextDestroyCallbacks) const std::lock_guard lock(Impl::getUnsharedGlObjectsMutex());
callback(ptr);
// Destroy the unshared objects contained in this context
for (auto iter = m_impl->unsharedGlObjects->begin(); iter != m_impl->unsharedGlObjects->end();)
{
if (iter->contextId == m_impl->m_id)
{
iter = m_impl->unsharedGlObjects->erase(iter);
}
else
{
++iter;
}
}
}
// Make the originally active context active again // Make the originally active context active again
if (contextToRestore) if (contextToRestore)
@ -815,7 +906,7 @@ void GlContext::initialize(const ContextSettings& requestedSettings)
glGetIntegervFunc(GL_MAJOR_VERSION, &majorVersion); glGetIntegervFunc(GL_MAJOR_VERSION, &majorVersion);
glGetIntegervFunc(GL_MINOR_VERSION, &minorVersion); glGetIntegervFunc(GL_MINOR_VERSION, &minorVersion);
if ((glGetErrorFunc() != GL_INVALID_ENUM) && (majorVersion != 0)) if (glGetErrorFunc() != GL_INVALID_ENUM)
{ {
m_settings.majorVersion = static_cast<unsigned int>(majorVersion); m_settings.majorVersion = static_cast<unsigned int>(majorVersion);
m_settings.minorVersion = static_cast<unsigned int>(minorVersion); m_settings.minorVersion = static_cast<unsigned int>(minorVersion);
@ -836,7 +927,24 @@ void GlContext::initialize(const ContextSettings& requestedSettings)
// OpenGL ES Full profile: The beginning of the returned string is "OpenGL ES major.minor" // OpenGL ES Full profile: The beginning of the returned string is "OpenGL ES major.minor"
// Desktop OpenGL: The beginning of the returned string is "major.minor" // Desktop OpenGL: The beginning of the returned string is "major.minor"
using GlContextImpl::parseVersionString; // Helper to parse OpenGL version strings
static const auto parseVersionString =
[](const char* versionString, const char* prefix, unsigned int& major, unsigned int& minor)
{
const std::size_t prefixLength = std::strlen(prefix);
if ((std::strlen(versionString) >= (prefixLength + 3)) &&
(std::strncmp(versionString, prefix, prefixLength) == 0) && std::isdigit(versionString[prefixLength]) &&
(versionString[prefixLength + 1] == '.') && std::isdigit(versionString[prefixLength + 2]))
{
major = static_cast<unsigned int>(versionString[prefixLength] - '0');
minor = static_cast<unsigned int>(versionString[prefixLength + 2] - '0');
return true;
}
return false;
};
if (!parseVersionString(version, "OpenGL ES-CL ", m_settings.majorVersion, m_settings.minorVersion) && if (!parseVersionString(version, "OpenGL ES-CL ", m_settings.majorVersion, m_settings.minorVersion) &&
!parseVersionString(version, "OpenGL ES-CM ", m_settings.majorVersion, m_settings.minorVersion) && !parseVersionString(version, "OpenGL ES-CM ", m_settings.majorVersion, m_settings.minorVersion) &&

View File

@ -50,38 +50,32 @@ class GlContext
{ {
public: public:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Perform resource initialization /// \brief Get a shared_ptr to the shared context
/// ///
/// This function is called every time an OpenGL resource is /// \return shared_ptr to the shared context
/// created. When the first resource is initialized, it makes
/// sure that everything is ready for contexts to work properly.
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
static void initResource(); static std::shared_ptr<void> getSharedContext();
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Perform resource cleanup /// \brief Register an OpenGL object to be destroyed when its containing context is destroyed
///
/// This function is called every time an OpenGL resource is
/// destroyed. When the last resource is destroyed, it makes
/// sure that everything that was created by initResource()
/// is properly released.
///
////////////////////////////////////////////////////////////
static void cleanupResource();
////////////////////////////////////////////////////////////
/// \brief Register a function to be called when a context is destroyed
/// ///
/// This is used for internal purposes in order to properly /// This is used for internal purposes in order to properly
/// clean up OpenGL resources that cannot be shared bwteen /// clean up OpenGL resources that cannot be shared bwteen
/// contexts. /// contexts.
/// ///
/// \param callback Function to be called when a context is destroyed /// \param object Object to be destroyed when its containing context is destroyed
/// \param arg Argument to pass when calling the function
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
static void registerContextDestroyCallback(ContextDestroyCallback callback, void* arg); static void registerUnsharedGlObject(std::shared_ptr<void> object);
////////////////////////////////////////////////////////////
/// \brief Unregister an OpenGL object from its containing context
///
/// \param object Object to be unregister
///
////////////////////////////////////////////////////////////
static void unregisterUnsharedGlObject(std::shared_ptr<void> object);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Acquires a context for short-term use on the current thread /// \brief Acquires a context for short-term use on the current thread
@ -300,6 +294,10 @@ protected:
ContextSettings m_settings; //!< Creation settings of the context ContextSettings m_settings; //!< Creation settings of the context
private: private:
struct TransientContext;
struct SharedContext;
struct Impl;
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Perform various initializations after the context construction /// \brief Perform various initializations after the context construction
/// \param requestedSettings Requested settings during context creation /// \param requestedSettings Requested settings during context creation
@ -317,7 +315,7 @@ private:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Member data // Member data
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
const std::uint64_t m_id; //!< Unique number that identifies the context const std::unique_ptr<Impl> m_impl; //!< Implementation details
}; };
} // namespace sf::priv } // namespace sf::priv

View File

@ -32,23 +32,22 @@
namespace sf namespace sf
{ {
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
GlResource::GlResource() GlResource::GlResource() : m_sharedContext(priv::GlContext::getSharedContext())
{ {
priv::GlContext::initResource();
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
GlResource::~GlResource() void GlResource::registerUnsharedGlObject(std::shared_ptr<void> object)
{ {
priv::GlContext::cleanupResource(); priv::GlContext::registerUnsharedGlObject(std::move(object));
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void GlResource::registerContextDestroyCallback(ContextDestroyCallback callback, void* arg) void GlResource::unregisterUnsharedGlObject(std::shared_ptr<void> object)
{ {
priv::GlContext::registerContextDestroyCallback(callback, arg); priv::GlContext::unregisterUnsharedGlObject(std::move(object));
} }

View File

@ -3,7 +3,7 @@
#include <type_traits> #include <type_traits>
static_assert(!std::is_constructible_v<sf::GlResource>); static_assert(!std::is_constructible_v<sf::GlResource>);
static_assert(!std::is_copy_constructible_v<sf::GlResource>); static_assert(std::is_copy_constructible_v<sf::GlResource>);
static_assert(std::is_copy_assignable_v<sf::GlResource>); static_assert(std::is_copy_assignable_v<sf::GlResource>);
static_assert(!std::is_nothrow_move_constructible_v<sf::GlResource>); static_assert(std::is_nothrow_move_constructible_v<sf::GlResource>);
static_assert(std::is_nothrow_move_assignable_v<sf::GlResource>); static_assert(std::is_nothrow_move_assignable_v<sf::GlResource>);