From b39be46db028a825f321fa676ca8417fd9ab60d9 Mon Sep 17 00:00:00 2001 From: binary1248 Date: Sat, 1 Apr 2023 02:44:05 +0200 Subject: [PATCH] Moved most variables in sfml-graphics, GlResource and GlContext out of namespace scope. --- include/SFML/Window/GlResource.hpp | 29 +- src/SFML/Graphics/RenderTarget.cpp | 30 +- src/SFML/Graphics/RenderTextureImplFBO.cpp | 224 +++----- src/SFML/Graphics/RenderTextureImplFBO.hpp | 8 +- src/SFML/Graphics/Shader.cpp | 48 +- src/SFML/Graphics/Texture.cpp | 1 + src/SFML/Graphics/VertexBuffer.cpp | 5 +- src/SFML/Window/GlContext.cpp | 600 ++++++++++++--------- src/SFML/Window/GlContext.hpp | 40 +- src/SFML/Window/GlResource.cpp | 11 +- test/Window/GlResource.test.cpp | 4 +- 11 files changed, 523 insertions(+), 477 deletions(-) diff --git a/include/SFML/Window/GlResource.hpp b/include/SFML/Window/GlResource.hpp index cc432925f..21b66b628 100644 --- a/include/SFML/Window/GlResource.hpp +++ b/include/SFML/Window/GlResource.hpp @@ -29,6 +29,8 @@ //////////////////////////////////////////////////////////// #include +#include + namespace sf { @@ -48,23 +50,24 @@ protected: GlResource(); //////////////////////////////////////////////////////////// - /// \brief Destructor - /// - //////////////////////////////////////////////////////////// - ~GlResource(); - - //////////////////////////////////////////////////////////// - /// \brief Register a function to be called when a context is destroyed + /// \brief Register an OpenGL object to be destroyed when its containing 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 + /// \param object Object to be destroyed when its containing context is destroyed /// //////////////////////////////////////////////////////////// - static void registerContextDestroyCallback(ContextDestroyCallback callback, void* arg); + static void registerUnsharedGlObject(std::shared_ptr object); + + //////////////////////////////////////////////////////////// + /// \brief Unregister an OpenGL object from its containing context + /// + /// \param object Object to be unregistered + /// + //////////////////////////////////////////////////////////// + static void unregisterUnsharedGlObject(std::shared_ptr object); //////////////////////////////////////////////////////////// /// \brief RAII helper class to temporarily lock an available context for use @@ -97,6 +100,12 @@ protected: //////////////////////////////////////////////////////////// TransientContextLock& operator=(const TransientContextLock&) = delete; }; + +private: + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + std::shared_ptr m_sharedContext; //!< Shared context used to link all contexts together for resource sharing }; } // namespace sf diff --git a/src/SFML/Graphics/RenderTarget.cpp b/src/SFML/Graphics/RenderTarget.cpp index dccfc1377..726f6e3bb 100644 --- a/src/SFML/Graphics/RenderTarget.cpp +++ b/src/SFML/Graphics/RenderTarget.cpp @@ -54,30 +54,35 @@ namespace namespace RenderTargetImpl { // 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 // tracking the currently active RenderTarget within a given context std::uint64_t getUniqueId() { - const std::lock_guard lock(mutex); - - static std::uint64_t id = 1; // start at 1, zero is "no RenderTarget" - + const std::lock_guard lock(getMutex()); + static std::uint64_t 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 using ContextRenderTargetMap = std::unordered_map; -ContextRenderTargetMap contextRenderTargetMap; +ContextRenderTargetMap& getContextRenderTargetMap() +{ + static ContextRenderTargetMap contextRenderTargetMap; + return contextRenderTargetMap; +} // Check if a RenderTarget with the given ID is active in the current context bool isActive(std::uint64_t id) { - const auto it = contextRenderTargetMap.find(sf::Context::getActiveContextId()); - - return (it != contextRenderTargetMap.end()) && (it->second == id); + const auto it = getContextRenderTargetMap().find(sf::Context::getActiveContextId()); + return (it != getContextRenderTargetMap().end()) && (it->second == id); } // 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 { - const std::lock_guard lock(RenderTargetImpl::mutex); + const std::lock_guard lock(RenderTargetImpl::getMutex()); const std::uint64_t contextId = Context::getActiveContextId(); - using RenderTargetImpl::contextRenderTargetMap; - const auto it = contextRenderTargetMap.find(contextId); + using RenderTargetImpl::getContextRenderTargetMap; + auto& contextRenderTargetMap = getContextRenderTargetMap(); + auto it = contextRenderTargetMap.find(contextId); if (active) { diff --git a/src/SFML/Graphics/RenderTextureImplFBO.cpp b/src/SFML/Graphics/RenderTextureImplFBO.cpp index f4ceec3cf..e035f30ed 100644 --- a/src/SFML/Graphics/RenderTextureImplFBO.cpp +++ b/src/SFML/Graphics/RenderTextureImplFBO.cpp @@ -36,98 +36,31 @@ #include #include -#include -#include -#include -#include - - -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*> 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> 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(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 { //////////////////////////////////////////////////////////// -RenderTextureImplFBO::RenderTextureImplFBO() +struct RenderTextureImplFBO::FrameBufferObject { - const std::lock_guard lock(mutex); + FrameBufferObject() + { + // Create the framebuffer object + glCheck(GLEXT_glGenFramebuffers(1, &object)); + } - // Register the context destruction callback - registerContextDestroyCallback(contextDestroyCallback, nullptr); + ~FrameBufferObject() + { + if (object) + glCheck(GLEXT_glDeleteFramebuffers(1, &object)); + } - // Insert the new frame buffer mapping into the set of all active mappings - frameBuffers.insert(&m_frameBuffers); - frameBuffers.insert(&m_multisampleFrameBuffers); -} + GLuint object{}; +}; + + +//////////////////////////////////////////////////////////// +RenderTextureImplFBO::RenderTextureImplFBO() = default; //////////////////////////////////////////////////////////// @@ -135,12 +68,6 @@ RenderTextureImplFBO::~RenderTextureImplFBO() { 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 if (m_colorBuffer) { @@ -155,15 +82,22 @@ RenderTextureImplFBO::~RenderTextureImplFBO() glCheck(GLEXT_glDeleteRenderbuffers(1, &depthStencilBuffer)); } - // Move all frame buffer objects to stale set - for (auto& [contextId, frameBufferId] : m_frameBuffers) - staleFrameBuffers.emplace(contextId, frameBufferId); + // Unregister FBOs with the contexts if they haven't already been destroyed + for (auto& entry : m_frameBuffers) + { + auto frameBuffer = entry.second.lock(); - for (auto& [contextId, multisampleFrameBufferId] : m_multisampleFrameBuffers) - staleFrameBuffers.emplace(contextId, multisampleFrameBufferId); + if (frameBuffer) + unregisterUnsharedGlObject(std::move(frameBuffer)); + } - // Clean up FBOs - destroyStaleFBOs(); + for (auto& entry : m_multisampleFrameBuffers) + { + 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() { // Create the framebuffer object - GLuint frameBuffer = 0; - glCheck(GLEXT_glGenFramebuffers(1, &frameBuffer)); + auto frameBuffer = std::make_shared(); - if (!frameBuffer) + if (!frameBuffer->object) { err() << "Impossible to create render texture (failed to create the frame buffer object)" << std::endl; 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 if (!m_multisample && m_depthStencilBuffer) @@ -462,33 +395,30 @@ bool RenderTextureImplFBO::createFrameBuffer() if (status != GLEXT_GL_FRAMEBUFFER_COMPLETE) { 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; return false; } - { - const std::lock_guard lock(mutex); + // Insert the FBO into our map + m_frameBuffers.emplace(Context::getActiveContextId(), frameBuffer); - // Insert the FBO into our map - 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 if (m_multisample) { // Create the multisample framebuffer object - GLuint multisampleFrameBuffer = 0; - glCheck(GLEXT_glGenFramebuffers(1, &multisampleFrameBuffer)); + auto multisampleFrameBuffer = std::make_shared(); - if (!multisampleFrameBuffer) + if (!multisampleFrameBuffer->object) { err() << "Impossible to create render texture (failed to create the multisample frame buffer object)" << std::endl; 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 glCheck(GLEXT_glBindRenderbuffer(GLEXT_GL_RENDERBUFFER, m_colorBuffer)); @@ -517,19 +447,17 @@ bool RenderTextureImplFBO::createFrameBuffer() if (status != GLEXT_GL_FRAMEBUFFER_COMPLETE) { 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 " "buffer)" << std::endl; return false; } - { - const std::lock_guard lock(mutex); + // Insert the FBO into our map + m_multisampleFrameBuffers.emplace(Context::getActiveContextId(), multisampleFrameBuffer); - // Insert the FBO into our map - m_multisampleFrameBuffers.emplace(Context::getActiveContextId(), multisampleFrameBuffer); - } + // Register the object with the current context so it is automatically destroyed + registerUnsharedGlObject(std::move(multisampleFrameBuffer)); } #endif @@ -575,29 +503,33 @@ bool RenderTextureImplFBO::activate(bool active) // Lookup the FBO corresponding to the currently active context // If none is found, there is no FBO corresponding to the // currently active context so we will have to create a new FBO + if (m_multisample) { - const std::lock_guard lock(mutex); + const auto it = m_multisampleFrameBuffers.find(contextId); - std::unordered_map::iterator it; - - if (m_multisample) + if (it != m_multisampleFrameBuffers.end()) { - it = m_multisampleFrameBuffers.find(contextId); + auto frameBuffer = it->second.lock(); - if (it != m_multisampleFrameBuffers.end()) + if (frameBuffer) { - glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_FRAMEBUFFER, it->second)); + glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_FRAMEBUFFER, frameBuffer->object)); return true; } } - else - { - it = m_frameBuffers.find(contextId); + } + else + { + const auto it = m_frameBuffers.find(contextId); - if (it != m_frameBuffers.end()) + if (it != m_frameBuffers.end()) + { + auto frameBuffer = it->second.lock(); + + if (frameBuffer) { - glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_FRAMEBUFFER, it->second)); + glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_FRAMEBUFFER, frameBuffer->object)); return true; } @@ -630,26 +562,30 @@ void RenderTextureImplFBO::updateTexture(unsigned int) { const std::uint64_t contextId = Context::getActiveContextId(); - const std::lock_guard lock(mutex); - const auto frameBufferIt = m_frameBuffers.find(contextId); const auto multisampleIt = m_multisampleFrameBuffers.find(contextId); if ((frameBufferIt != m_frameBuffers.end()) && (multisampleIt != m_multisampleFrameBuffers.end())) { - // 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_glBlitFramebuffer(0, - 0, - static_cast(m_size.x), - static_cast(m_size.y), - 0, - 0, - static_cast(m_size.x), - static_cast(m_size.y), - GL_COLOR_BUFFER_BIT, - GL_NEAREST)); - glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_DRAW_FRAMEBUFFER, multisampleIt->second)); + 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) + glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_DRAW_FRAMEBUFFER, frameBuffer->object)); + glCheck(GLEXT_glBlitFramebuffer(0, + 0, + static_cast(m_size.x), + static_cast(m_size.y), + 0, + 0, + static_cast(m_size.x), + static_cast(m_size.y), + GL_COLOR_BUFFER_BIT, + GL_NEAREST)); + glCheck(GLEXT_glBindFramebuffer(GLEXT_GL_DRAW_FRAMEBUFFER, multiSampleFrameBuffer->object)); + } } } diff --git a/src/SFML/Graphics/RenderTextureImplFBO.hpp b/src/SFML/Graphics/RenderTextureImplFBO.hpp index faa4f5168..d7d991fe4 100644 --- a/src/SFML/Graphics/RenderTextureImplFBO.hpp +++ b/src/SFML/Graphics/RenderTextureImplFBO.hpp @@ -137,8 +137,12 @@ private: //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// - std::unordered_map m_frameBuffers; //!< OpenGL frame buffer objects per context - std::unordered_map m_multisampleFrameBuffers; //!< Optional per-context OpenGL frame buffer objects with multisample attachments + struct FrameBufferObject; + + using FrameBufferObjectMap = std::unordered_map>; + + 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_colorBuffer{}; //!< Optional multisample color buffer attached to the frame buffer Vector2u m_size; //!< Width and height of the attachments diff --git a/src/SFML/Graphics/Shader.cpp b/src/SFML/Graphics/Shader.cpp index eb442c499..2c6ee88e8 100644 --- a/src/SFML/Graphics/Shader.cpp +++ b/src/SFML/Graphics/Shader.cpp @@ -40,7 +40,6 @@ #include #include -#include #include #include #include @@ -61,20 +60,17 @@ 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 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(maxUnits); } @@ -747,23 +743,16 @@ void Shader::bind(const Shader* shader) //////////////////////////////////////////////////////////// bool Shader::isAvailable() { - const std::lock_guard lock(isAvailableMutex); - - static bool checked = false; - static bool available = false; - - if (!checked) + static const bool available = []() { - checked = true; - const TransientContextLock contextLock; // Make sure that extensions are initialized sf::priv::ensureExtensionsInit(); - available = GLEXT_multitexture && GLEXT_shading_language_100 && GLEXT_shader_objects && GLEXT_vertex_shader && - GLEXT_fragment_shader; - } + return GLEXT_multitexture && GLEXT_shading_language_100 && GLEXT_shader_objects && GLEXT_vertex_shader && + GLEXT_fragment_shader; + }(); return available; } @@ -772,22 +761,15 @@ bool Shader::isAvailable() //////////////////////////////////////////////////////////// bool Shader::isGeometryAvailable() { - const std::lock_guard lock(isAvailableMutex); - - static bool checked = false; - static bool available = false; - - if (!checked) + static const bool available = []() { - checked = true; - const TransientContextLock contextLock; // Make sure that extensions are initialized 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; } diff --git a/src/SFML/Graphics/Texture.cpp b/src/SFML/Graphics/Texture.cpp index 86e95db27..7c6df2e5a 100644 --- a/src/SFML/Graphics/Texture.cpp +++ b/src/SFML/Graphics/Texture.cpp @@ -71,6 +71,7 @@ Texture::Texture() : m_cacheId(TextureImpl::getUniqueId()) //////////////////////////////////////////////////////////// Texture::Texture(const Texture& copy) : +GlResource(copy), m_isSmooth(copy.m_isSmooth), m_sRgb(copy.m_sRgb), m_isRepeated(copy.m_isRepeated), diff --git a/src/SFML/Graphics/VertexBuffer.cpp b/src/SFML/Graphics/VertexBuffer.cpp index 4c743e193..e8ea0908b 100644 --- a/src/SFML/Graphics/VertexBuffer.cpp +++ b/src/SFML/Graphics/VertexBuffer.cpp @@ -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) { diff --git a/src/SFML/Window/GlContext.cpp b/src/SFML/Window/GlContext.cpp index e44fa5dbc..7d2469ff5 100644 --- a/src/SFML/Window/GlContext.cpp +++ b/src/SFML/Window/GlContext.cpp @@ -167,17 +167,6 @@ namespace // A nested named namespace is used here to allow unity builds of SFML. 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 // track current context information for each thread struct CurrentContext @@ -185,27 +174,156 @@ struct CurrentContext std::uint64_t id{}; sf::priv::GlContext* ptr{}; unsigned int transientCount{}; + + // This per-thread variable holds the current context information for each thread + static CurrentContext& get() + { + thread_local CurrentContext currentContext; + return currentContext; + } + +private: + // Private constructor to prevent CurrentContext from being constructed outside of get() + CurrentContext() = default; +}; +} // namespace GlContextImpl +} // namespace + + +namespace sf::priv +{ +// This structure contains all the state necessary to +// track SharedContext usage +struct GlContext::SharedContext +{ + //////////////////////////////////////////////////////////// + /// \brief Constructor + /// + //////////////////////////////////////////////////////////// + SharedContext() + { + const std::lock_guard lock(mutex); + + context.emplace(nullptr); + context->initialize(sf::ContextSettings()); + + loadExtensions(); + + context->setActive(false); + } + + //////////////////////////////////////////////////////////// + /// \brief Get weak_ptr + /// + /// \return weak_ptr to the SharedContext + /// + //////////////////////////////////////////////////////////// + static std::weak_ptr& getWeakPtr() + { + static std::weak_ptr weakSharedContext; + return weakSharedContext; + } + + //////////////////////////////////////////////////////////// + /// \brief Get shared_ptr to the shared context + /// + /// Create new object if one doesn't already exist. + /// + /// \return shared_ptr to the shared context + /// + //////////////////////////////////////////////////////////// + static std::shared_ptr get() + { + auto& weakSharedContext = getWeakPtr(); + auto sharedContext = weakSharedContext.lock(); + + if (!sharedContext) + { + sharedContext = std::make_shared(); + weakSharedContext = sharedContext; + } + + return sharedContext; + } + + //////////////////////////////////////////////////////////// + /// \brief Load our extensions vector with the supported extensions + /// + //////////////////////////////////////////////////////////// + void loadExtensions() + { + auto glGetErrorFunc = reinterpret_cast(getFunction("glGetError")); + auto glGetIntegervFunc = reinterpret_cast(getFunction("glGetIntegerv")); + auto glGetStringFunc = reinterpret_cast(getFunction("glGetString")); + + if (!glGetErrorFunc || !glGetIntegervFunc || !glGetStringFunc) + return; + + // Check whether a >= 3.0 context is available + int majorVersion = 0; + glGetIntegervFunc(GL_MAJOR_VERSION, &majorVersion); + + auto glGetStringiFunc = reinterpret_cast(getFunction("glGetStringi")); + + if (glGetErrorFunc() == GL_INVALID_ENUM || !majorVersion || !glGetStringiFunc) + { + // Try to load the < 3.0 way + const char* extensionString = reinterpret_cast(glGetStringFunc(GL_EXTENSIONS)); + + if (extensionString) + { + extensions.clear(); + + do + { + const char* extension = extensionString; + + while (*extensionString && (*extensionString != ' ')) + ++extensionString; + + extensions.emplace_back(extension, extensionString); + } while (*extensionString++); + } + } + else + { + // Try to load the >= 3.0 way + int numExtensions = 0; + glGetIntegervFunc(GL_NUM_EXTENSIONS, &numExtensions); + + if (numExtensions) + { + extensions.clear(); + + for (unsigned int i = 0; i < static_cast(numExtensions); ++i) + { + const char* extensionString = reinterpret_cast(glGetStringiFunc(GL_EXTENSIONS, i)); + + if (extensionString) + extensions.emplace_back(extensionString); + } + } + } + } + + // 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 + std::recursive_mutex mutex; + + // Supported OpenGL extensions + std::vector extensions; + + // The hidden, inactive context that will be shared with all other contexts + std::optional context; }; -// This per-thread variable holds the current context information for each thread -thread_local CurrentContext currentContext; - -// The hidden, inactive context that will be shared with all other contexts -std::unique_ptr sharedContext; - -// Unique identifier, used for identifying contexts when managing unshareable OpenGL resources -std::atomic 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; -ContextDestroyCallbacks contextDestroyCallbacks; // This structure contains all the state necessary to // track TransientContext usage -struct TransientContext +struct GlContext::TransientContext { //////////////////////////////////////////////////////////// /// \brief Constructor @@ -215,15 +333,13 @@ struct TransientContext { // TransientContext should never be created if there is // already a context active on the current thread - assert(!currentContext.id); + assert(!GlContextImpl::CurrentContext::get().id); - std::unique_lock lock(mutex); + // Lock ourselves so we don't create a new object if one doesn't already exist + sharedContext = SharedContext::getWeakPtr().lock(); - if (resourceCount == 0) + if (!sharedContext) { - // No GlResources, no shared context yet - assert(!sharedContext); - // Create a Context object for temporary use context.emplace(); } @@ -233,8 +349,8 @@ struct TransientContext assert(sharedContext); // Lock the shared context for temporary use - sharedContextLock = std::move(lock); - sharedContext->setActive(true); + sharedContextLock = std::unique_lock(sharedContext->mutex); + sharedContext->context->setActive(true); } } @@ -245,7 +361,7 @@ struct TransientContext ~TransientContext() { if (sharedContextLock) - sharedContext->setActive(false); + sharedContext->context->setActive(false); } //////////////////////////////////////////////////////////// @@ -260,176 +376,141 @@ struct TransientContext //////////////////////////////////////////////////////////// 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& get() + { + thread_local std::optional transientContext; + return transientContext; + } + /////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// std::optional context; std::unique_lock sharedContextLock; + std::shared_ptr sharedContext; }; -// This per-thread variable tracks if and how a transient -// context is currently being used on the current thread -thread_local std::optional transientContext; -// Supported OpenGL extensions -std::vector extensions; - -// Load our extensions vector with the supported extensions -void loadExtensions() +// This structure contains all the implementation data we +// don't want to expose through the visible interface +struct GlContext::Impl { - auto glGetErrorFunc = reinterpret_cast(sf::priv::GlContext::getFunction("glGetError")); - auto glGetIntegervFunc = reinterpret_cast(sf::priv::GlContext::getFunction("glGetIntegerv")); - auto glGetStringFunc = reinterpret_cast(sf::priv::GlContext::getFunction("glGetString")); - - if (!glGetErrorFunc || !glGetIntegervFunc || !glGetStringFunc) - return; - - // Check whether a >= 3.0 context is available - int majorVersion = 0; - glGetIntegervFunc(GL_MAJOR_VERSION, &majorVersion); - - auto glGetStringiFunc = reinterpret_cast(sf::priv::GlContext::getFunction("glGetStringi")); - - if (glGetErrorFunc() == GL_INVALID_ENUM || !majorVersion || !glGetStringiFunc) - { - // Try to load the < 3.0 way - const char* extensionString = reinterpret_cast(glGetStringFunc(GL_EXTENSIONS)); - - if (extensionString) + //////////////////////////////////////////////////////////// + /// \brief Constructor + /// + //////////////////////////////////////////////////////////// + Impl() : + m_id( + []() { - extensions.clear(); + static std::atomic id(1); // start at 1, zero is "no context" + return id.fetch_add(1); + }()) + { + auto& weakUnsharedGlObjects = getWeakUnsharedGlObjects(); + unsharedGlObjects = weakUnsharedGlObjects.lock(); - do - { - const char* extension = extensionString; + if (!unsharedGlObjects) + { + unsharedGlObjects = std::make_shared(); - while (*extensionString && (*extensionString != ' ')) - ++extensionString; - - extensions.emplace_back(extension, extensionString); - } while (*extensionString++); + weakUnsharedGlObjects = unsharedGlObjects; } } - else + + // Structure to track which unshared object belongs to which context + struct UnsharedGlObject { - // Try to load the >= 3.0 way - int numExtensions = 0; - glGetIntegervFunc(GL_NUM_EXTENSIONS, &numExtensions); + std::uint64_t contextId; + std::shared_ptr object; + }; - if (numExtensions) - { - extensions.clear(); + using UnsharedGlObjects = std::vector; - for (unsigned int i = 0; i < static_cast(numExtensions); ++i) - { - const char* extensionString = reinterpret_cast(glGetStringiFunc(GL_EXTENSIONS, i)); - - if (extensionString) - extensions.emplace_back(extensionString); - } - } - } -} - -// Helper to parse OpenGL version strings -bool parseVersionString(const char* version, const char* prefix, unsigned int& major, unsigned int& minor) -{ - const std::size_t prefixLength = std::strlen(prefix); - - if ((std::strlen(version) >= (prefixLength + 3)) && (std::strncmp(version, prefix, prefixLength) == 0) && - std::isdigit(version[prefixLength]) && (version[prefixLength + 1] == '.') && - std::isdigit(version[prefixLength + 2])) + //////////////////////////////////////////////////////////// + /// \brief Get weak_ptr to unshared objects + /// + /// \return weak_ptr to unshared objects + /// + //////////////////////////////////////////////////////////// + static std::weak_ptr& getWeakUnsharedGlObjects() { - major = static_cast(version[prefixLength] - '0'); - minor = static_cast(version[prefixLength + 2] - '0'); + static std::weak_ptr weakUnsharedGlObjects; - return true; + return weakUnsharedGlObjects; } - return false; -} -} // namespace GlContextImpl -} // namespace + //////////////////////////////////////////////////////////// + /// \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; //!< 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 +}; -namespace sf::priv -{ //////////////////////////////////////////////////////////// -void GlContext::initResource() +std::shared_ptr GlContext::getSharedContext() { - 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(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; + return GlContext::SharedContext::get(); } //////////////////////////////////////////////////////////// -void GlContext::cleanupResource() +void GlContext::registerUnsharedGlObject(std::shared_ptr object) { - using GlContextImpl::mutex; - using GlContextImpl::resourceCount; - using GlContextImpl::sharedContext; - - // Protect from concurrent access - const std::lock_guard lock(mutex); - - // Decrement the resources counter - --resourceCount; - - // If there's no more resource alive, we can trigger the global context cleanup - if (resourceCount == 0) - { - if (!sharedContext) - return; - - // Destroy the shared context - sharedContext.reset(); - } + 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::registerContextDestroyCallback(ContextDestroyCallback callback, void* arg) +void GlContext::unregisterUnsharedGlObject(std::shared_ptr object) { - GlContextImpl::contextDestroyCallbacks.emplace(callback, arg); + 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() { - using GlContextImpl::currentContext; - using GlContextImpl::TransientContext; - using GlContextImpl::transientContext; + auto& currentContext = GlContextImpl::CurrentContext::get(); // Fast path if we already have a context active on this thread if (currentContext.id) @@ -443,7 +524,7 @@ void GlContext::acquireTransientContext() // If currentContextId is not set, this must be the first // TransientContextLock on this thread, construct the state object - transientContext.emplace(); + TransientContext::get().emplace(); // Make sure a context is active at this point assert(currentContext.id); @@ -453,8 +534,7 @@ void GlContext::acquireTransientContext() //////////////////////////////////////////////////////////// void GlContext::releaseTransientContext() { - using GlContextImpl::currentContext; - using GlContextImpl::transientContext; + auto& currentContext = GlContextImpl::CurrentContext::get(); // Make sure a context was left active after acquireTransientContext() was called assert(currentContext.id); @@ -468,34 +548,29 @@ void GlContext::releaseTransientContext() // If currentContextId is set and currentContextTransientCount is 0, // this is the last TransientContextLock that is released, destroy the state object - transientContext.reset(); + TransientContext::get().reset(); } //////////////////////////////////////////////////////////// std::unique_ptr 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) - assert(sharedContext != nullptr); + auto sharedContext = SharedContext::get(); - const std::lock_guard lock(mutex); + const std::lock_guard lock(sharedContext->mutex); std::unique_ptr context; // We don't use acquireTransientContext here since we have // to ensure we have exclusive access to the shared context // in order to make sure it is not active during context creation - { - sharedContext->setActive(true); + sharedContext->context->setActive(true); - // Create the context - context = std::make_unique(sharedContext.get()); + // Create the context + context = std::make_unique(&sharedContext->context.value()); - sharedContext->setActive(false); - } + sharedContext->context->setActive(false); context->initialize(ContextSettings()); @@ -506,31 +581,26 @@ std::unique_ptr GlContext::create() //////////////////////////////////////////////////////////// std::unique_ptr 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) - 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 // Check if we need to convert our shared context into a core context - if ((resourceCount == 1) && (settings.attributeFlags & ContextSettings::Core) && - !(sharedContext->m_settings.attributeFlags & ContextSettings::Core)) + if ((sharedContext.use_count() == 2) && (settings.attributeFlags & ContextSettings::Core) && + !(sharedContext->context->m_settings.attributeFlags & ContextSettings::Core)) { // Re-create our shared context as a core context const ContextSettings sharedSettings(0, 0, 0, settings.majorVersion, settings.minorVersion, settings.attributeFlags); - sharedContext = std::make_unique(nullptr, sharedSettings, Vector2u(1, 1)); - sharedContext->initialize(sharedSettings); + sharedContext->context.emplace(nullptr, sharedSettings, Vector2u(1, 1)); + sharedContext->context->initialize(sharedSettings); // Reload our extensions vector - loadExtensions(); + sharedContext->loadExtensions(); } std::unique_ptr context; @@ -538,14 +608,12 @@ std::unique_ptr GlContext::create(const ContextSettings& settings, co // We don't use acquireTransientContext here since we have // to ensure we have exclusive access to the shared context // in order to make sure it is not active during context creation - { - sharedContext->setActive(true); + sharedContext->context->setActive(true); - // Create the context - context = std::make_unique(sharedContext.get(), settings, owner, bitsPerPixel); + // Create the context + context = std::make_unique(&sharedContext->context.value(), settings, owner, bitsPerPixel); - sharedContext->setActive(false); - } + sharedContext->context->setActive(false); context->initialize(settings); context->checkSettings(settings); @@ -557,31 +625,26 @@ std::unique_ptr GlContext::create(const ContextSettings& settings, co //////////////////////////////////////////////////////////// std::unique_ptr 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) - 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 // Check if we need to convert our shared context into a core context - if ((resourceCount == 1) && (settings.attributeFlags & ContextSettings::Core) && - !(sharedContext->m_settings.attributeFlags & ContextSettings::Core)) + if ((sharedContext.use_count() == 2) && (settings.attributeFlags & ContextSettings::Core) && + !(sharedContext->context->m_settings.attributeFlags & ContextSettings::Core)) { // Re-create our shared context as a core context const ContextSettings sharedSettings(0, 0, 0, settings.majorVersion, settings.minorVersion, settings.attributeFlags); - sharedContext = std::make_unique(nullptr, sharedSettings, Vector2u(1, 1)); - sharedContext->initialize(sharedSettings); + sharedContext->context.emplace(nullptr, sharedSettings, Vector2u(1, 1)); + sharedContext->context->initialize(sharedSettings); // Reload our extensions vector - loadExtensions(); + sharedContext->loadExtensions(); } std::unique_ptr context; @@ -589,14 +652,12 @@ std::unique_ptr GlContext::create(const ContextSettings& settings, co // We don't use acquireTransientContext here since we have // to ensure we have exclusive access to the shared context // in order to make sure it is not active during context creation - { - sharedContext->setActive(true); + sharedContext->context->setActive(true); - // Create the context - context = std::make_unique(sharedContext.get(), settings, size); + // Create the context + context = std::make_unique(&sharedContext->context.value(), settings, size); - sharedContext->setActive(false); - } + sharedContext->context->setActive(false); context->initialize(settings); context->checkSettings(settings); @@ -608,15 +669,27 @@ std::unique_ptr GlContext::create(const ContextSettings& settings, co //////////////////////////////////////////////////////////// bool GlContext::isExtensionAvailable(const char* name) { - using GlContextImpl::extensions; - return std::find(extensions.begin(), extensions.end(), name) != extensions.end(); + // If this function is called before any context is available, + // 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) { - 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 lock; + + if (sharedContext) + lock = std::unique_lock(sharedContext->mutex); return ContextType::getFunction(name); } @@ -625,27 +698,23 @@ GlFunctionPointer GlContext::getFunction(const char* name) //////////////////////////////////////////////////////////// const GlContext* GlContext::getActiveContext() { - using GlContextImpl::currentContext; - - return currentContext.ptr; + return GlContextImpl::CurrentContext::get().ptr; } //////////////////////////////////////////////////////////// std::uint64_t GlContext::getActiveContextId() { - using GlContextImpl::currentContext; - - return currentContext.id; + return GlContextImpl::CurrentContext::get().id; } //////////////////////////////////////////////////////////// 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.ptr = nullptr; @@ -663,20 +732,27 @@ const ContextSettings& GlContext::getSettings() const //////////////////////////////////////////////////////////// bool GlContext::setActive(bool active) { - using GlContextImpl::currentContext; - using GlContextImpl::mutex; + auto& currentContext = GlContextImpl::CurrentContext::get(); + + // 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 (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 lock; + + if (sharedContext) + lock = std::unique_lock(sharedContext->mutex); // Activate the context if (makeCurrent(true)) { // Set it as the new current context for this thread - currentContext.id = m_id; + currentContext.id = m_impl->m_id; currentContext.ptr = this; return true; } @@ -693,9 +769,13 @@ bool GlContext::setActive(bool active) } 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 lock; + + if (sharedContext) + lock = std::unique_lock(sharedContext->mutex); // Deactivate the context 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()) { // Nothing to do } @@ -765,23 +845,34 @@ int GlContext::evaluateFormat( //////////////////////////////////////////////////////////// void GlContext::cleanupUnsharedResources() { - using GlContextImpl::ContextDestroyCallbacks; - using GlContextImpl::contextDestroyCallbacks; - using GlContextImpl::currentContext; + const auto& currentContext = GlContextImpl::CurrentContext::get(); // Save the current context so we can restore it later GlContext* contextToRestore = currentContext.ptr; // 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; // Make this context active so resources can be freed setActive(true); - // Call the registered destruction callbacks - for (auto& [callback, ptr] : contextDestroyCallbacks) - callback(ptr); + { + const std::lock_guard lock(Impl::getUnsharedGlObjectsMutex()); + + // 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 if (contextToRestore) @@ -815,7 +906,7 @@ void GlContext::initialize(const ContextSettings& requestedSettings) glGetIntegervFunc(GL_MAJOR_VERSION, &majorVersion); glGetIntegervFunc(GL_MINOR_VERSION, &minorVersion); - if ((glGetErrorFunc() != GL_INVALID_ENUM) && (majorVersion != 0)) + if (glGetErrorFunc() != GL_INVALID_ENUM) { m_settings.majorVersion = static_cast(majorVersion); m_settings.minorVersion = static_cast(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" // 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(versionString[prefixLength] - '0'); + minor = static_cast(versionString[prefixLength + 2] - '0'); + + return true; + } + + return false; + }; if (!parseVersionString(version, "OpenGL ES-CL ", m_settings.majorVersion, m_settings.minorVersion) && !parseVersionString(version, "OpenGL ES-CM ", m_settings.majorVersion, m_settings.minorVersion) && diff --git a/src/SFML/Window/GlContext.hpp b/src/SFML/Window/GlContext.hpp index a42587495..74c786cc8 100644 --- a/src/SFML/Window/GlContext.hpp +++ b/src/SFML/Window/GlContext.hpp @@ -50,38 +50,32 @@ class GlContext { public: //////////////////////////////////////////////////////////// - /// \brief Perform resource initialization + /// \brief Get a shared_ptr to the shared context /// - /// This function is called every time an OpenGL resource is - /// created. When the first resource is initialized, it makes - /// sure that everything is ready for contexts to work properly. + /// \return shared_ptr to the shared context /// //////////////////////////////////////////////////////////// - static void initResource(); + static std::shared_ptr getSharedContext(); //////////////////////////////////////////////////////////// - /// \brief Perform resource cleanup - /// - /// 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 + /// \brief Register an OpenGL object to be destroyed when its containing context is destroyed /// /// This is used for internal purposes in order to properly /// clean up OpenGL resources that cannot be shared bwteen /// contexts. /// - /// \param callback Function to be called when a context is destroyed - /// \param arg Argument to pass when calling the function + /// \param object Object to be destroyed when its containing context is destroyed /// //////////////////////////////////////////////////////////// - static void registerContextDestroyCallback(ContextDestroyCallback callback, void* arg); + static void registerUnsharedGlObject(std::shared_ptr object); + + //////////////////////////////////////////////////////////// + /// \brief Unregister an OpenGL object from its containing context + /// + /// \param object Object to be unregister + /// + //////////////////////////////////////////////////////////// + static void unregisterUnsharedGlObject(std::shared_ptr object); //////////////////////////////////////////////////////////// /// \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 private: + struct TransientContext; + struct SharedContext; + struct Impl; + //////////////////////////////////////////////////////////// /// \brief Perform various initializations after the context construction /// \param requestedSettings Requested settings during context creation @@ -317,7 +315,7 @@ private: //////////////////////////////////////////////////////////// // Member data //////////////////////////////////////////////////////////// - const std::uint64_t m_id; //!< Unique number that identifies the context + const std::unique_ptr m_impl; //!< Implementation details }; } // namespace sf::priv diff --git a/src/SFML/Window/GlResource.cpp b/src/SFML/Window/GlResource.cpp index 50ee3082b..e758c0bd2 100644 --- a/src/SFML/Window/GlResource.cpp +++ b/src/SFML/Window/GlResource.cpp @@ -32,23 +32,22 @@ namespace sf { //////////////////////////////////////////////////////////// -GlResource::GlResource() +GlResource::GlResource() : m_sharedContext(priv::GlContext::getSharedContext()) { - priv::GlContext::initResource(); } //////////////////////////////////////////////////////////// -GlResource::~GlResource() +void GlResource::registerUnsharedGlObject(std::shared_ptr object) { - priv::GlContext::cleanupResource(); + priv::GlContext::registerUnsharedGlObject(std::move(object)); } //////////////////////////////////////////////////////////// -void GlResource::registerContextDestroyCallback(ContextDestroyCallback callback, void* arg) +void GlResource::unregisterUnsharedGlObject(std::shared_ptr object) { - priv::GlContext::registerContextDestroyCallback(callback, arg); + priv::GlContext::unregisterUnsharedGlObject(std::move(object)); } diff --git a/test/Window/GlResource.test.cpp b/test/Window/GlResource.test.cpp index d0a0872da..31aafa726 100644 --- a/test/Window/GlResource.test.cpp +++ b/test/Window/GlResource.test.cpp @@ -3,7 +3,7 @@ #include static_assert(!std::is_constructible_v); -static_assert(!std::is_copy_constructible_v); +static_assert(std::is_copy_constructible_v); static_assert(std::is_copy_assignable_v); -static_assert(!std::is_nothrow_move_constructible_v); +static_assert(std::is_nothrow_move_constructible_v); static_assert(std::is_nothrow_move_assignable_v);