Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: RenderTexture reuse  (Read 10423 times)

0 Members and 1 Guest are viewing this topic.

Rosme

  • Full Member
  • ***
  • Posts: 169
  • Proud member of the shoe club
    • View Profile
    • Code-Concept
Re: RenderTexture reuse
« Reply #15 on: June 23, 2015, 09:57:51 pm »
@Nexus: yes, Hapax one without Quicksprite.

I re-tried it, Debug and Release, x86 and x64, Static and Dynamic. Two red squares, always.

Edit: Hapax, this is what I get with your second code:

(click to show/hide)
« Last Edit: June 23, 2015, 10:00:33 pm by Rosme »
GitHub
Code Concept
Twitter
Rosme on IRC/Discord

shadowmouse

  • Sr. Member
  • ****
  • Posts: 302
    • View Profile
Re: RenderTexture reuse
« Reply #16 on: June 23, 2015, 10:02:35 pm »
Just copied Hapax's first code in his latest post and got red and blue on Windows 7 with sfml 2.1 static.

Kipernal

  • Newbie
  • *
  • Posts: 29
    • View Profile
Re: RenderTexture reuse
« Reply #17 on: June 23, 2015, 10:28:46 pm »
Since it seemed to be only the people who are statically linking who are getting the wrong result, I re-tested it statically. It still worked and, in a similar direction of thought to you, Kipernal, I'm providing my built executable for you to test. Actually, I'm providing two as a check to see if it's just the render texture's clear() that's failing (or clearing with the wrong colour). The links to the executables are directly below the source code.

Just tried both executables, and I get the exact same results as Rosme on my home computer (AMD Radeon R7 200) and my laptop (Intel integrated).  That is, two red rectangles on the first test, and two red rectangles with two green circles inscribed in them, and blue artifacts around the second circle on the second test.



EDIT: wording
« Last Edit: June 23, 2015, 10:36:00 pm by Kipernal »

kitteh-warrior

  • Guest
Re: RenderTexture reuse
« Reply #18 on: June 24, 2015, 01:32:08 am »
I tried the second sample code that Hapax posted.
To clarify, this:
The second one draws a circle on the render texture after each clear (to see if it's clearing it and if it is, it's clearing with the wrong colour):
(click to show/hide)

I tested it on my main system:
OS: Windows 8.1 Pro
GPU: AMD Radeon R4 Graphics
Linkage: Dynamic
Compiler: MinGW64 (5.0.2) [Used the same binaries for the other tests]
Results:
(click to show/hide)

I tested it on my secondary system:
OS: Windows 8.1 Home
GPU: Intel HD Graphics (this is a custom driver, to be more specific: Acer Aspire E1-510P-4402)
Linkage: Dynamic
Compiler: Used the previous binaries from main system*
Results:
(click to show/hide)

I tested it on my virtual machine (within my main system):
OS: Windows 7 Professional
GPU: VMWare's Internal
Linkage: Dynamic
Compiler: Used the previous binaries from main system*
Results:
(click to show/hide)

Upon all of the other posts, it could be Windows 8.1 that is causing a problem.

Hapax

  • Hero Member
  • *****
  • Posts: 3351
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: RenderTexture reuse
« Reply #19 on: June 24, 2015, 01:49:11 am »
Well, the circle test certainly brought out some information but what that information means will be best interpreted by someone who can guess what is going on.
I'd recommend trying to remove display() or double call display() for the render texture (for the first block, the second block, or both - try them all!).

Upon all of the other posts, it could be Windows 8.1 that is causing a problem.
Windows 8.1 seems involved. Or, at least, something that is common in Windows 8.1.
It can't just be Windows 8.1 though as Nexus got the correct result. Nexus, what are you doing right?  ;D

Can someone - who is getting the error - try running the executable in compatibility mode for Windows 7?
« Last Edit: June 24, 2015, 01:53:17 am by Hapax »
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

kitteh-warrior

  • Guest
Re: RenderTexture reuse
« Reply #20 on: June 24, 2015, 02:02:32 am »
Well, the circle test certainly brought out some information but what that information means will be best interpreted by someone who can guess what is going on.
I'd recommend trying to remove display() or double call display() for the render texture (for the first block, the second block, or both - try them all!).

Playing around with the code:
(click to show/hide)
This code outputs both image files to be correct. (possibly due to copying them from VRAM to RAM)

Also, removing the .display() calls did not solve the problem.
(click to show/hide)
This yields the exact same result as before.

Quote
Windows 8.1 seems involved. Or, at least, something that is common in Windows 8.1.
It can't just be Windows 8.1 though as Nexus got the correct result.
Oh, I guess I missed this post. :p

Quote
Can someone - who is getting the error - try running the executable in compatibility mode for Windows 7?
This yields the exact same result for me. I also tried running the executable as Administrator, yet no change.
« Last Edit: June 24, 2015, 02:06:39 am by kitteh-warrior »

Kipernal

  • Newbie
  • *
  • Posts: 29
    • View Profile
Re: RenderTexture reuse
« Reply #21 on: June 24, 2015, 02:13:28 am »
@Hapax Tried a few things, but all of them still drew the same artifact-y circle:

  • Windows compatibility modes (7, XP SP3, 98/ME)
  • Removing the calls to renderTexture.display()
  • Calling renderTexture.display() twice, one immediately after another
  • Calling renderTexture.display() once before drawing the circle and once after

This code outputs both image files to be correct. (possibly due to copying them from VRAM to RAM)

I mentioned this before, but certain function calls result in the correct image being shown on-screen.  copyToImage being called on the RenderTexture's texture is one.  Calling applyTexture(NULL) on the RenderWindow (after making it public) is the other.

EDIT: spelling
« Last Edit: June 24, 2015, 02:20:51 am by Kipernal »

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: RenderTexture reuse
« Reply #22 on: June 24, 2015, 12:34:52 pm »
It is hardly ever the OS version that's causing the problem. And for graphical differences, it's almost never the compiler. So don't waste too much time exploring these design spaces. Much more likely are bugs or at least subtle behavior differences in graphics driver implementations. I hope you updated yours immediately before testing ;)

It looks like some OpenGL calls are cached for too long -- the glClear() seems to be only executed in certain scenarios, namely those where the buffer is prepared for download (RenderTexture::copyToImage()). Such calls usually flush the OpenGL pipeline. However, RenderTexture::display() explicitly calls glFlush() -- at least in the FBO implementation -- but that function's implementation may vary between drivers.

Those who have the problem:
  • Can you check which RenderTexture backend is used -- RenderTextureImplDefault or RenderTextureImplFBO?
  • Can you try if an OpenGL call glFinish() in place of/additional to RenderTexture::display() changes something?
« Last Edit: June 24, 2015, 12:55:54 pm by Nexus »
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Kipernal

  • Newbie
  • *
  • Posts: 29
    • View Profile
Re: RenderTexture reuse
« Reply #23 on: June 24, 2015, 05:21:00 pm »
Can you check which RenderTexture backend is used -- RenderTextureImplDefault or RenderTextureImplFBO?

RenderTextureImplFBO is used. If I force priv::RenderTextureImplFBO::isAvailable() to always return false, causing SFML to use RenderTextureImplDefault instead, everything works correctly, drawing a red and blue square.

Can you try if an OpenGL call glFinish() in place of/additional to RenderTexture::display() changes something?

I tried putting glFinish() before, after, and in place of both calls to renderTexture.display(), but there was no change (based on the circle test).

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: RenderTexture reuse
« Reply #24 on: June 25, 2015, 05:16:06 am »
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.
SFGUI # SFNUL # GLS # Wyrm <- Why do I waste my time on such a useless project? Because I am awesome (first meaning).

Kipernal

  • Newbie
  • *
  • Posts: 29
    • View Profile
Re: RenderTexture reuse
« Reply #25 on: June 25, 2015, 01:50:30 pm »
Can confirm that all three of those work.

Would it be worth it adding #2 into SFML?  It is technically a driver bug but this seems like the kind of thing SFML would handle to make everything "just work," and the fix seems to be incredibly easy.  I just tried adding a flag to sf::Texture that marked it as belonging to a RenderTexture and then, at the end of RenderTarget::draw, added the following code:

if (states.texture && states.texture->m_ownedByRenderTexture)
{
    Texture::bind(0);
    m_cache.lastTextureId = 0;
}

Seemed to make everything work alright.

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: RenderTexture reuse
« Reply #26 on: June 26, 2015, 12:48:36 am »
You might not know this, but the first half of this issue was already fixed more than a year ago (as discussed here, commit). The "fix" is still present in the current RenderTarget code with the accompanying comment.

Now that it is obvious to me that it was only half of a fix, I guess your method would provide the only full solution to this problem. It isn't even as complicated as you think. sf::Texture already has a member called m_pixelsFlipped which is only set when it is an FBO attachment, so that can be checked for in the draw() method.
SFGUI # SFNUL # GLS # Wyrm <- Why do I waste my time on such a useless project? Because I am awesome (first meaning).

Kipernal

  • Newbie
  • *
  • Posts: 29
    • View Profile
Re: RenderTexture reuse
« Reply #27 on: June 26, 2015, 01:36:31 pm »
Went ahead and made a pull request.  Thank you for the help, everyone!

It isn't even as complicated as you think. sf::Texture already has a member called m_pixelsFlipped which is only set when it is an FBO attachment, so that can be checked for in the draw() method.

I noticed that, but unfortunately m_pixelsFlipped is also set to true in Texture::update(sf::Window, ...), which looks like it would result in the texture being rebound every call to draw if it was based on the window.