This is definitely a driver bug. I can bet with you that multi-context tests weren't part of their OpenGL test suite, because who even makes use of that feature anyway right?
Because I (thankfully) don't have the privilege to understand how their driver "works" (or doesn't), I can only guess at this point.
After you are done drawing the first sprite, the RenderTexture's texture is left bound in the window's context. Normally this shouldn't be a problem, since drawing to the RenderTexture doesn't make use of it anyway (if it did, the results would be undefined). In fact, any normal drawing still works in this case, it's just the clear that doesn't for whatever reason.
Because the effects of OpenGL commands don't need to be complete right after they are issued, all that is done by calling all those functions is a command queue is built up. OpenGL guarantees that the effects of commands are applied in the exact order in which they were issued. Since there is only a single thread here, this also shouldn't be an issue. Commands are issued to each context serially anyway.
The problems only pop up when one tries to be smart about this system and start optimizing things that should be able to be optimized. In order to do this, I can imagine a dependency tree is internally built up that tracks which commands depend on the results of previous commands and which commands can be executed in parallel to others. The driver might be incorrectly thinking that it can't clear because the contents of the FBO attachment might still be needed somewhere. What's even stranger is that it just skips the clear command all together instead of just postponing it. Drawing a quad with the clear colour to the RenderTexture works, even though it does pretty much the same as the clear command.
Something probably just went wrong with the inter-context dependency tracking. "Normal" OpenGL users don't notice this and this bug is therefore probably not widely known because in your typical OpenGL application only 1 context is ever used anyway and if multiple are used, it's not to draw but to stream data better or balance the state validation load. There is no benefit to drawing from multiple contexts, and in our case there are even concrete drawbacks.
The thing with SFML is that it doesn't "do stuff like all the other guys do". Here are a few things that SFML does that you don't find in a typical OpenGL application:
- Make very liberal use of multiple OpenGL contexts, for drawing as well.
- Maintain a lot of state between frames. SFML even expects this to work or it will just break.
- Never unbind FBOs. Many drivers interpret unbinding an FBO as "flushing" it (this is not part of the specification). So if SFML never unbinds, you could say it never gets flushed from the perspective of a sloppy driver.
It is a combination of these things and many others that make SFML "that special kid" that just never gets any attention. While perfectly legal according to the specification, driver developers would probably rather spend their paid time working on things that their "bigger customers" care about more. Maybe SFML might be able to get on that train one day when it starts to make use of Vulkan.
There are several workarounds to solve this problem:
1. Alternate between 2 RenderTextures. This way you will implicitly force the window to rebind its texture and "break the dependency chain".
#include <SFML/Graphics.hpp>
int main()
{
sf::RenderTexture renderTexture1;
renderTexture1.create(256, 256);
sf::Sprite renderSprite1(renderTexture1.getTexture());
sf::RenderTexture renderTexture2;
renderTexture2.create(256, 256);
sf::Sprite renderSprite2(renderTexture2.getTexture());
sf::RenderWindow window(sf::VideoMode(512, 512), "Re-using Render Textures Without QuickSprite", sf::Style::Default);
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed || event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)
window.close();
}
window.clear();
renderTexture1.clear(sf::Color::Red);
renderTexture1.display();
renderSprite1.setPosition(0, 0);
window.draw(renderSprite1);
renderTexture2.clear(sf::Color::Blue);
renderTexture2.display();
renderSprite2.setPosition(256, 256);
window.draw(renderSprite2);
renderTexture1.clear(sf::Color::Green);
renderTexture1.display();
renderSprite1.setPosition(256, 0);
window.draw(renderSprite1);
renderTexture2.clear(sf::Color::Yellow);
renderTexture2.display();
renderSprite2.setPosition(0, 256);
window.draw(renderSprite2);
window.display();
}
return EXIT_SUCCESS;
}
2. Manually break the dependency chain by unbinding and rebinding the RenderTexture's texture yourself. It is important to rebind it after unbinding or else the window's cached texture state will go out of sync and you will need to resetGLStates() to fix it.
#include <SFML/Graphics.hpp>
int main()
{
sf::RenderTexture renderTexture;
renderTexture.create(256, 256);
sf::Sprite renderSprite(renderTexture.getTexture());
sf::RenderWindow window(sf::VideoMode(512, 512), "Re-using Render Textures Without QuickSprite", sf::Style::Default);
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed || event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)
window.close();
}
window.clear();
renderTexture.clear(sf::Color::Red);
renderTexture.display();
renderSprite.setPosition(0, 0);
window.draw(renderSprite);
sf::Texture::bind(0);
sf::Texture::bind(&renderTexture.getTexture());
renderTexture.clear(sf::Color::Blue);
renderTexture.display();
renderSprite.setPosition(256, 256);
window.draw(renderSprite);
window.display();
}
return EXIT_SUCCESS;
}
3. Just draw a quad covering the entire RenderTexture to clear it. Draw operations seem to work even though clears do not.
#include <SFML/Graphics.hpp>
int main()
{
sf::RenderTexture renderTexture;
renderTexture.create(256, 256);
sf::Sprite renderSprite(renderTexture.getTexture());
sf::RectangleShape clearRect(sf::Vector2f(256, 256));
clearRect.setFillColor(sf::Color::Blue);
sf::RenderWindow window(sf::VideoMode(512, 512), "Re-using Render Textures Without QuickSprite", sf::Style::Default);
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed || event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)
window.close();
}
window.clear();
renderTexture.clear(sf::Color::Red);
renderTexture.display();
renderSprite.setPosition(0, 0);
window.draw(renderSprite);
renderTexture.draw(clearRect);
renderTexture.display();
renderSprite.setPosition(256, 256);
window.draw(renderSprite);
window.display();
}
return EXIT_SUCCESS;
}
I'd say option 2 has the lowest overhead followed by 1 then 3. Going through the whole draw process just to fill the framebuffer with a fixed value is really wasteful, and creating multiple FBO objects where unnecessary is also wasteful. If you were going to use multiple RenderTextures anyway, then option 1 isn't that bad.