Hello awesome SFML forum!
Today, I (We? Can't speak for the other team members..) approach all of you to ask for your opinion on the matter of:
SFML Context ManagementThis has been an issue that I have personally been involved in for a long time, and I can imagine many others, including Laurent himself of course, have expressed their concern in regards to this matter time and again.
With the
SFML 3 vision thread up in full swing for just over 5 months, there is no doubt that we are going to start work on SFML 3 sooner or later.
As you all know one of the biggest uses of SFML, is in the area of 2D graphics. To realize this, SFML builds on top of the cross-platform graphics API OpenGL. As with any hardware accelerated API, there needs to be some form handle to identify the accelerator device in question, whether it is really the physical device or a virtual device that is emulated by the driver. DirectX names their handles, devices, fittingly, and OpenGL decided to name their handles contexts. OpenGL has had a long history, much longer than DirectX, and needless to say, the concept of contexts as a container for GL state has existed since the beginnings of OpenGL.
For those familiar with OpenGL, you know that calls to the OpenGL API will only have an effect if a context is currently active, to keep it simple for now. Any state modifying calls will modify the state of the currently active context and no other. Since OpenGL used to do most of its work via bindings (i.e. state variables), understanding how contexts work is essential if you plan on using multiple of them, and that is where the difficulties arise.
There are a certain set of constraints that have to be met when dealing with contexts. Many of them are probably the result of operating system constraints from the time when the first specifications were written, but they haven't changed much since then. I will try to list most of the important ones here so that you can understand what I mention later better.
- Contexts are containers for OpenGL objects, identifiers to resources that live somewhere, either in the driver or on the GPU itself.
- Contexts are containers for OpenGL state. OpenGL can be seen as one huge state machine, and you manipulate this state to alter what you see on your screen.
- OpenGL operations operate either on state, or objects. If the container (i.e. context) for these does not exist, naturally, OpenGL operations will have no effect.
- Almost all OpenGL objects can be shared between contexts. This simply means that they share the same identifier space, meaning that an identifier generated in one context can be used in another. In previous versions of OpenGL, one did not even have to generate objects and could manage the identifiers without the help of the driver. This however has been deprecated and will cause an error in newer OpenGL. The most prominent examples of objects that cannot be shared between contexts are framebuffer objects, the underlying object of an sf::RenderTexture and vertex array objects, containers for vertex attribute binding state in modern OpenGL.
- Context resource sharing works on a peer-to-peer basis. Sharing is associative. Once contexts are shared with any context that is part of a chain of other contexts, it cannot be "unshared".
- Every context has its own default framebuffer, basically a surface on which all draw operations have an effect. This cannot be suppressed, even if one does not intend on ever drawing to that framebuffer.
- Contexts can be activated and deactivated. The currently active context is the one that OpenGL operations have an effect on.
- Contexts can only be activated in a single thread at a time. It is an error to activate a context in a different thread than one that it is currently already activated in. In order to switch the thread in which a context is active in, the previous thread has to deactivate it and only after that has been done can the new thread activate it.
- What follows from the previous points is that only a single context can be active in a thread at any given time. Activating another context than the one already active will automatically deactivate the currently active one first. Multiple contexts can simultaneously be active, but each in their respective threads. Unless you know that your application causes a lot of work in the driver (not on the GPU!) and that you have a multi-threaded driver, splitting OpenGL work up among multiple contexts in multiple threads will provide absolutely no gain in regards to OpenGL performance. Developers who know how to get a hold of that information will probably also understand how to exploit multi-threaded GL, but most of the time, this is simply not the case. With future OpenGL versions, reduction of driver overhead is also part of the plan, so multi-threaded OpenGL usage will keep losing significance as well.
SFML makes extensive use of OpenGL for drawing throughout its graphics module, and marginal use of OpenGL in its window module in order to provide context support for developers who aim to use OpenGL directly.
The technical details come now.
Due to historical reasons, Laurent chose to have a single context that serves no other purpose than to be shared with any context that is created after it. In SFML code, this context is referred to as the "shared context". The shared context is the first context to be created in an SFML application, due to the fact that any future contexts have to be shared with it. It is created as part of the global GL context initialization which happens as soon as the first sf::GlResource is created. It relies on reference counting in order to track how many objects there are that still rely on having an active context. Once the last sf::GlResource is destroyed, the global GL context cleanup is performed, which gets rid of the shared context as well. In addition to that, the cleanup function also gets rid of all "internal contexts".
SFML sets up a sort of "invariant" that states that as soon as a sf::GlResource has to perform
any operation within
any thread that thread will have to constantly have a context active at all times, even when not required. Maybe this was done for performance reasons? I do not know (only Laurent knows
), and the difference was barely measurable in my tests. As opposed to "standard practice" of deactivating contexts when required, SFML "deactivates" contexts on the current thread by activating a so called "internal context", a thread local context that makes sure that the invariant of having a context active at all times is upheld. These internal contexts are created the first time they are required (i.e. a context was activated and requested to deactivate) and due to the way the context management works, they cannot be destroyed until global cleanup, which typically happens when the application closes. This results in what is now known as the "context leak" that has been discussed on the forum and tracker often enough. Applications spawning temporary threads to perform sfml-graphics operations will notice a constant rise in memory usage due to this, and at some point, the driver will also give up in trying to manage the excessive number of contexts.
Are these internal contexts ever used when (and if) they are active? Not very often in typical code. If an application doesn't make use of sfml-graphics in secondary threads without an explicit context object and never explicitly calls .setActive(false) from within the main thread (the one that deals with the main window) or creates sfml-graphics objects before the main window is created, it will likely never be used. Nonetheless, it is created, all the time. If you are proficient in OpenGL debugging tools, you will notice that even the simplest SFML application, such as those found in the beginner tutorials will end up creating 3 OpenGL contexts, although the shared context is unnecessary (according to my tests) and the internal context is rarely ever used.
Additionally, because framebuffer objects cannot be shared between contexts as stated above, in order for SFML to guarantee that a sf::RenderTexture can be used in any thread after it is created, it has to contain its own context that is activated solely when something is to be done on the sf::RenderTexture and deactivated thereafter. This means that in a single-threaded application with a "large" number of sf::RenderTextures, the same number of additional contexts will have to be created, one for each sf::RenderTexture. The alternative implementation of sf::RenderTexture using a secondary context's default framebuffer on systems that don't support framebuffer objects is more justified, but the systems that cause this to be the case are getting less and less common.
The reasoning behind the current design stems from the fact that SFML tries to hide context management from its users. The assumption is that context management shouldn't be something that beginners have to struggle with when they already have so much to learn. This is true, and maybe it should be something that is kept for understanding at a later time, but I personally find that learning that a context has to be active in order to play with sfml-graphics is something that shouldn't be too hard to grasp. The problem is that I, Laurent and many others already have a lot of experience with graphics programming, so what might seem easy for us might seem like a nightmare for beginners. Conversely, what might seem like a boon to beginners is a nightmare to advanced users (e.g. threaded sfml-graphics).
SFML 3 will probably bring with it a lot of changes, and Laurent has already proposed his ideas on separating the sf::RenderTarget concept from the window and offscreen rendering surface objects (currently sf::RenderTexture). Conceptually, a sf::RenderTarget is nothing other than a context that is used to target a surface to render to, so in that respect a change in regards to context management wouldn't be as drastic as some might expect.
I would have provided mockup examples of what explicit context management might look like, but I decided against it since it is only one of many possible ways of exposing it through the API and I don't want to influence the outcome of the poll because of that.
We really need community feedback on this matter. Should SFML make context management more explicit? Or do you think that it should stay implicit since this is what makes SFML different from (and better than?) all other multimedia libraries?
Please try to be honest and give the poll a bit of thought before voting. In the case you retrospectively change your mind, please try to factor in the gut feeling you had when you voted previously. We want the API to feel good and easy to use, yet powerful at the same time, and there is no better way to judge the "feel" of the API than relying on your gut feeling. Also, since I hope to get a representative opinion on this matter, it is important to motivate as many of SFML's users to vote as possible, especially those that don't visit the forum very often. SFML will always try to be beginner friendly and thus their opinions are worth just as much (if not more) than those of us who have been using SFML for years, and yet they are probably the ones who are the hardest to get a hold of for these kinds of polls.
For those who are afraid/shy of exposing yourself, keep in mind that voting is anonymous (posting is not mandatory), and only those with database access (i.e. those who really don't care
) can check what you voted for, if they even invest the amount of effort required to do that.
I would be happy for your feedback on this matter
.