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

Author Topic: RenderTexture::create crashes on call #287  (Read 4172 times)

0 Members and 1 Guest are viewing this topic.

Kipernal

  • Newbie
  • *
  • Posts: 29
    • View Profile
RenderTexture::create crashes on call #287
« on: April 24, 2015, 09:13:50 am »
Hey there,

I seem to be having a bit of trouble when creating RenderTextures (and occasionally normal Textures too).  I basically have four problems (that are hopefully all connected): 
  • The create call crashes after creating around 300 RenderTextures or so (but occasionally as low as 18, and sometimes just hanging forever on call #9) regardless of the size. Destroying the RenderTexture allows me to create more RenderTextures, so it's a limit of 300 or so at any given time, not 300 total.
  • If a separate thread calls create on a RenderTexture or normal Texture that was defined on the main thread, then destroying the RenderTexture or Texture will not allow me to create more than the ~300 max I mentioned above. 300 becomes a maximum for RenderTextures or Textures created like this.
  • The create call is measurably slow for RenderTextures, such that I can see the program freeze briefly upon creation and in the most extreme case of calling create once every frame my FPS drops from 60 to 20.
  • The memory usage for RendureTextures is bizarrely high.  Creating 283 1x1 RenderTextures uses almost 2 GB of memory.

Here is a minimal example for the first point:

#include <SFML/Graphics.hpp>
#include <iostreams>

int main(int argc, char* argv[])
{
        for (int i = 0; i < 400; i++)
        {
                (new sf::RenderTexture)->create(1, 1);
                std::cout << "Created texture #" << i + 1 << std::endl;
        }
}

On my system this I get the error "Unhandled exception at 0x0364DC72 (atioglxx.dll) in out.exe: 0xC0000005: Access violation writing location 0x00000000." trying to create texture #287.

In this case, if you were to replace the RenderTextures with normal Textures there would be no problem.
If you were to delete the RenderTexture allocated in the for loop or put it on the stack so it is destroyed automatically there would be no problem.
If you were to not call create there would be no problem.

The second point is strangely specific (I mean, y'know, threads. Of course things get weird).  This was the minimal example I came up with:

#include <SFML/Graphics.hpp>
#include <iostreams>
#include <thread>

int main(int argc, char* argv[])
{
        sf::RenderTexture renderTextures;       // For some reason we need at least one RenderTexture like this for the crash to occur.
                                                                // It doesn't even need to be created, it just needs to exist.
       
        for (int i = 0; i < 400; i++)
        {
                // Define the texture in one thread, but create it in another.
                sf::RenderTexture tex;
                std::thread thread([&]
                {
                        tex.create(1, 1);
                });
                thread.join();

                std::cout << "Created texture #" << i + 1 << std::endl;
               
                // Note that the RenderTextures are destroyed here, but we still crash.
        }
}

On the latest development snapshot of SFML compiling with Visual Studio 2015 CTP6 I get "Unhandled exception at 0x0401A610 (atioglxx.dll) in out.exe: 0xC0000005: Access violation writing location 0x00000000." trying to create texture #278.
On SFML 2.2 compiling with Visual Studio 2013 I get "Unhandled exception at 0x68956F8A (atigktxx.dll) in out.exe: 0xC0000005: Access violation reading location 0xFEEEFEEE." trying to create texture #159.

Here, if you replace the RenderTextures with Textures the problem persists.
If you remove the first RenderTexture (the one outside of the for loop) there is no problem.
If the RenderTextures are defined and created in the same thread, there is no problem.


So all of that in summary:
  • If I am using RenderTextures in one thread, I can only have ~300 created at one time before I need to destroy some. These are created very slowly and have a huge memory footprint.
  • If I am using Textures in one thread, I can have as many as I want, all created instantly, with no penalty.
  • If I am using RenderTextures defined in one thread and created in another thread, I can only have ~300 created in total, with the same slow creation time and memory usage.
  • If I am using Textures defined in one thread and created in another thread, the same problems occur (300 total, slow, lots of memory used)





My system is set up like the following:

OS: Windows 8.1, 64 bit
Graphics card: AMD Radeon R7 200 Series (2048 MB memory)
SFML versions tested: 2.2 and latest development snapshot as of April 23, 2015 (e0d27358fb9d62fcba96e1d14fa3185ce63668e9)
Compiler: Visual Studio 2013 for SFML 2.2, Visual Studio 2015 CTP6 for the latest GitHub version. Building for 32-bit.
Linkage: Static
Also made sure I'm using the latest drivers.  I updated them specifically for this test--before that the results were the same except the performance for creating RenderTextures was significantly worse and I could actually create a few more RenderTextures before crashing.  That being said at one point while running these tests my driver itself actually crashed, but I'm not sure if that's 100% relevant.

Full stack trace is here.  For reference, the function being called at WglContext::createContext is wglCreateContextAttribsARB.  This is the final call I can see in the debugger.

Code: [Select]
atioglxx.dll!0431dc72() Unknown
  [Frames below may be incorrect and/or missing, no symbols loaded for atioglxx.dll]
  atioglxx.dll!043f85eb() Unknown
  atioglxx.dll!043ccf9e() Unknown
  atioglxx.dll!043c3ae2() Unknown
  atioglxx.dll!043cd2ed() Unknown
  atioglxx.dll!043c3b51() Unknown
  atioglxx.dll!043fd613() Unknown
  atioglxx.dll!043cef6e() Unknown
  atioglxx.dll!0384be01() Unknown
  atioglxx.dll!04265e43() Unknown
  atioglxx.dll!04264cf9() Unknown
  atioglxx.dll!0426a47b() Unknown
  atioglxx.dll!03747c80() Unknown
  opengl32.dll!_CreateAnyContext@4() Unknown
  opengl32.dll!_wglCreateLayerContext@8() Unknown
  atioglxx.dll!0375a526() Unknown
> out.exe!sf::priv::WglContext::createContext(sf::priv::WglContext * shared, unsigned int bitsPerPixel, const sf::ContextSettings & settings) Line 420 C++
  out.exe!sf::priv::WglContext::WglContext(sf::priv::WglContext * shared) Line 88 C++
  out.exe!sf::priv::GlContext::create() Line 217 C++
  out.exe!sf::Context::Context() Line 37 C++
  out.exe!sf::priv::RenderTextureImplFBO::create(unsigned int width, unsigned int height, unsigned int textureId, bool depthBuffer) Line 87 C++
  out.exe!sf::RenderTexture::create(unsigned int width, unsigned int height, bool depthBuffer) Line 78 C++
  out.exe!main(int argc, char * * argv) Line 286 C++
  out.exe!invoke_main() Line 73 C++
  out.exe!__scrt_common_main_seh() Line 261 C++
  out.exe!__scrt_common_main() Line 304 C++
  out.exe!mainCRTStartup() Line 17 C++
  kernel32.dll!@BaseThreadInitThunk@12() Unknown
  ntdll.dll!__RtlUserThreadStart() Unknown
  ntdll.dll!__RtlUserThreadStart@8() Unknown

   
   
   
Occasionally I'll get a different stack trace, but this is more rare (note that this is for the threaded version of the problem, though I don't think it is unique to it):
Code: [Select]
atioglxx.dll!039edc72() Unknown
  [Frames below may be incorrect and/or missing, no symbols loaded for atioglxx.dll]
  atioglxx.dll!03ac85eb() Unknown
  atioglxx.dll!03a9cf9e() Unknown
  atioglxx.dll!03a93ae2() Unknown
  atioglxx.dll!03a9d2ed() Unknown
  atioglxx.dll!03a93b51() Unknown
  atioglxx.dll!03acd613() Unknown
  atioglxx.dll!03a9ef6e() Unknown
  atioglxx.dll!02f1bbb6() Unknown
  atioglxx.dll!03933aa2() Unknown
  atioglxx.dll!03933a0b() Unknown
  atioglxx.dll!039539d0() Unknown
  atioglxx.dll!03935222() Unknown
  atioglxx.dll!03938df9() Unknown
  atioglxx.dll!02e186df() Unknown
  opengl32.dll!_MakeAnyCurrent@16() Unknown
  opengl32.dll!_wglMakeCurrent@8() Unknown
> out.exe!sf::priv::WglContext::makeCurrent() Line 181 C++
  out.exe!sf::priv::GlContext::setActive(bool active) Line 301 C++
  out.exe!sf::priv::GlContext::initialize() Line 374 C++
  out.exe!sf::priv::GlContext::create() Line 220 C++
  out.exe!`anonymous namespace'::getInternalContext() Line 155 C++
  out.exe!sf::priv::GlContext::ensureContext() Line 207 C++
  out.exe!sf::GlResource::ensureGlContext() Line 83 C++
  out.exe!sf::Texture::getValidSize(unsigned int size) Line 610 C++
  out.exe!sf::Texture::create(unsigned int width, unsigned int height) Line 126 C++
  out.exe!sf::RenderTexture::create(unsigned int width, unsigned int height, bool depthBuffer) Line 55 C++
  out.exe!main::__l4::<lambda>() Line 292 C++
  out.exe!std::_Invoker_functor::_Call<void <lambda>(void) &>(main::__l4::void <lambda>(void) & _Obj) Line 1408 C++
  out.exe!std::invoke<void <lambda>(void) &>(main::__l4::void <lambda>(void) & _Obj) Line 1477 C++
  out.exe!std::_Invoke_ret<void <lambda>(void) &>(std::_Forced<std::_Unforced> __formal, main::__l4::void <lambda>(void) & <_Vals_0>) Line 1508 C++
  out.exe!std::_Call_binder<std::_Unforced,void <lambda>(void),std::tuple<>,std::tuple<> >(std::_Forced<std::_Unforced> _Fr, std::integer_sequence<unsigned int> __formal, main::__l4::void <lambda>(void) & _Obj, std::tuple<> & _Tpl, std::tuple<> && _Ut) Line 789 C++
  out.exe!std::_Binder<std::_Unforced,void <lambda>(void) >::operator()<>() Line 847 C++
  out.exe!std::_LaunchPad<std::_Binder<std::_Unforced,void <lambda>(void) > >::_Run(std::_LaunchPad<std::_Binder<std::_Unforced,void <lambda>(void) > > * _Ln) Line 234 C++
  out.exe!std::_LaunchPad<std::_Binder<std::_Unforced,void <lambda>(void) > >::_Go() Line 225 C++
  out.exe!std::_Pad::_Call_func(void * _Data) Line 202 C++
  out.exe!invoke_thread_procedure(unsigned int (void *) * const procedure, void * const context) Line 92 C++
  out.exe!thread_start<unsigned int (__stdcall*)(void *)>(void * const parameter) Line 115 C++
  kernel32.dll!@BaseThreadInitThunk@12() Unknown
  ntdll.dll!__RtlUserThreadStart() Unknown
  ntdll.dll!__RtlUserThreadStart@8() Unknown



Thanks in advance for any help you guys can give me!  I really know nothing about OpenGL so this was about all I could dig up without any sort of further guidance.  If anyone needs more information from me please just let me know.

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: RenderTexture::create crashes on call #287
« Reply #1 on: April 24, 2015, 11:00:07 am »
The create call crashes after creating around 300 RenderTextures or so (but occasionally as low as 18, and sometimes just hanging forever on call #9) regardless of the size. Destroying the RenderTexture allows me to create more RenderTextures, so it's a limit of 300 or so at any given time, not 300 total.
Operating system/hardware constraint and the way SFML context management currently works. Might get better in SFML 2.4, no promises.
If a separate thread calls create on a RenderTexture or normal Texture that was defined on the main thread, then destroying the RenderTexture or Texture will not allow me to create more than the ~300 max I mentioned above. 300 becomes a maximum for RenderTextures or Textures created like this.
Operating system constraint and the way SFML context management currently works. Might get better in SFML 3, no promises.
The create call is measurably slow for RenderTextures, such that I can see the program freeze briefly upon creation and in the most extreme case of calling create once every frame my FPS drops from 60 to 20.
Operating system constraint and the way SFML context management currently works. Might get better in SFML 2.4, no promises.
The memory usage for RendureTextures is bizarrely high.  Creating 283 1x1 RenderTextures uses almost 2 GB of memory.
Operating system constraint and the way SFML context management currently works. Might get better in SFML 2.4, no promises.

I think you get the picture. ;D

I'm curious, why do you need so many RenderTextures simultaneously anyway? An acceptable solution that has been suggested many times in the past is to simply reuse stale RenderTextures that are all allocated at the start of the application into a RenderTexture pool. Instead of using a separate RenderTexture as a working area for every little graphics operation, you should try to use them only when absolutely necessary. They are resource intensive objects after all, partly because SFML makes it worse than it already would be in an optimal implementation.

If you feel like a happy guinea pig today and don't mind building SFML from source, you can try out this branch. It is a small preview of the stuff that might come around in SFML 2.4. ;D It might fix/improve issues 1, 3 and 4 but definitely won't fix issue 2.
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::create crashes on call #287
« Reply #2 on: April 24, 2015, 07:59:04 pm »
I think you get the picture. ;D

It was kinda the answer I wasn't hoping for, but yeah, it makes sense.  :P

I'm curious, why do you need so many RenderTextures simultaneously anyway?

The best way to answer that would probably be with a timeline of how I found this problem:
  • A certain animation for an entity requires a RenderTexture.  I figure I'll save myself the trouble and do all rendering to that RenderTexture regardless of whether or not that entity will play that animation.
  • I hit the limit when creating too many of that entity and the program crashes (there is also a small pause whenever the entity is created). I try to work around that by only creating the RenderTexture when the animation that uses it starts playing.
  • Unfortunately the pause that occurred when the entity was created now occurs when the animation starts, which is almost worse.  I have 3 frames between when the animation starts and the RenderTexture is needed, so I try moving the creation to a new thread to give it some time to load.
  • RenderTexture creation succeeds on the new thread, but now if that animation is ever played 300 types the program crashes, which is the worst result yet.



An acceptable solution that has been suggested many times in the past is to simply reuse stale RenderTextures that are all allocated at the start of the application into a RenderTexture pool. Instead of using a separate RenderTexture as a working area for every little graphics operation, you should try to use them only when absolutely necessary. They are resource intensive objects after all, partly because SFML makes it worse than it already would be in an optimal implementation.

That's a pretty good idea and it's probably what I'll end up having to do if I can't get anything else working.  I knew that RenderTextures weren't exactly cheap, but I didn't expect them to be this slow to create and this memory intensive.

That being said I'm not sure how big the pool should be.  If this is a problem unique to my computer then it probably doesn't matter, but if other computers have a lower limit (like 50 textures or something), then it's a bit worse.

If you feel like a happy guinea pig today and don't mind building SFML from source, you can try out this branch. It is a small preview of the stuff that might come around in SFML 2.4. ;D It might fix/improve issues 1, 3 and 4 but definitely won't fix issue 2.

Went and tried that branch, but unfortunately the results were exactly the same.  :-\

Oh well.  Assuming that fixing 1, 3, and 4 are not possible, do you happen to have any suggestions for the threading problem?  If not I can always go for the pooling solution, but as long as the slow creation is the CPU's fault (as opposed to the GPU, since I don't think threads would help there?) I feel like loading in a separate thread might be the best workaround.

And failing that, do you think there is any way to have SFML detect the error and print to err() or something instead of crashing?  I'm not sure if there's any way to detect what's causing the crash in the first place (since it looks like it's taking place deep in some driver code), so I understand if it's not doable, but it's certainly preferable to crashing.

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: RenderTexture::create crashes on call #287
« Reply #3 on: April 24, 2015, 08:25:06 pm »
I've said it many times in the past and I'll say it again: Don't multi-thread OpenGL rendering. It doesn't solve the actual problem you are facing.

Contrary to what people think, issuing the draw commands takes absolutely no time at all. If one could somewhat reliably measure the time spent inside an SFML .draw() call, it will be close to nothing, even if the application runs at 10 frames per second. Where is all this time spent then? In the .display() call of your main window. The driver only ever blocks a call from returning when it absolutely has to, i.e. when it's command queue is full or the GPU is lagging too far behind the CPU in the case of high render load. Rendering doesn't happen when you call .draw(), it happens when you call .display() on the main window, but not RenderTextures.

This means that your approach of offloading creation of the RenderTextures into a secondary thread might reduce the amount of stuff that has to be done in your main thread, but it doesn't cut down on the OpenGL load at all. Like I said, RenderTextures are heavy objects. Creating and destroying them take time, manipulating them (changing size etc.) and performing other tasks needed to re-purpose them for rendering other unrelated things takes no time at all. I'm fairly certain that if you just set up a pool of them you won't even need to rely on threads any longer. With pools, threads won't provide any performance benefit, so in that sense they are no longer worth using.

I'm pretty sure that with pools all of your issues will just disappear, you might even be able to cut down on the total number of RenderTextures simultaneously in use by making sure they are released back into the pool once you've issued the final .draw() command that sources from their texture data. OpenGL is highly optimized for such use, so I wouldn't be surprised if this works well out of the box.

And failing that, do you think there is any way to have SFML detect the error and print to err() or something instead of crashing?  I'm not sure if there's any way to detect what's causing the crash in the first place (since it looks like it's taking place deep in some driver code), so I understand if it's not doable, but it's certainly preferable to crashing.
The problem here is that the operating system people and Khronos (the people who maintain the OpenGL specification) are constantly pointing fingers at each other. Context creation and management isn't governed by the OpenGL specification, and the OS people only do as much as necessary to keep the complaints from flooding their inboxes.

In your case, there simply isn't any way for the OS (which is in charge of context management) to report "out of resources" properly. The original WGL/GLX specifications were written more than 16 years ago, and almost nothing has changed since, so I think you get the idea. I guess they find it acceptable to just crash out on the user if that ever happens, oh well...
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::create crashes on call #287
« Reply #4 on: April 24, 2015, 09:49:23 pm »
This means that your approach of offloading creation of the RenderTextures into a secondary thread might reduce the amount of stuff that has to be done in your main thread, but it doesn't cut down on the OpenGL load at all. Like I said, RenderTextures are heavy objects. Creating and destroying them take time, manipulating them (changing size etc.) and performing other tasks needed to re-purpose them for rendering other unrelated things takes no time at all. I'm fairly certain that if you just set up a pool of them you won't even need to rely on threads any longer. With pools, threads won't provide any performance benefit, so in that sense they are no longer worth using.

Alright, that more or less makes sense.  I guess my last question then would be how to change the size of a RenderTexture without calling create, since if I need to keep a pool of RenderTextures it'd be nice to not have to lock them into a specific size (especially given how much memory they use).  Looking at SFML's source the biggest performance hit in create is its call to m_impl->create(width, height, m_texture.m_texture, depthBuffer), and that's called no matter what.  If I call create(1, 1) during a loading screen or whatever to create the texture, later calling create(64, 64) or something looks like it'll give me the same hit at a time when I can no longer hide it.

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: RenderTexture::create crashes on call #287
« Reply #5 on: April 24, 2015, 10:02:47 pm »
Hmm... yeah I just noticed that the current implementation is... suboptimal in that respect :P. I might improve it at some point.

For now, I guess you can just allocate a fixed number of RenderTextures of fixed sizes (e.g. 1x 1024x1024, 4x 512x512, 32x 64x64 etc.). It doesn't matter if they are bigger than you need them to be, just use the portion that is "valid" when .draw()ing using their texture by specifying a texture sub-rectangle. This is a really cheap operation (all done on GPU) and the only drawback is potentially "wasting" a bit of GPU memory on the areas that you aren't actually using for an operation. But if you make the pool really intelligent and provide you with a texture that is the closest to the size you currently need, it is even better than my previously proposed solution of constantly re-creating them. If you pull this off, you will have some really awesome frame times. ;)
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::create crashes on call #287
« Reply #6 on: April 24, 2015, 11:24:07 pm »
Sounds good.  I'll do that for now, and if I see RenderTexture ever getting a resize function or something similar I'll switch to that.

Thanks for your help!  Though I've gotta admit--I'm half relieved that it was such a simple problem and half disappointed that there's not really anything I can do to stop the fact that if I accidentally create too many RenderTextures my program will just kinda die on the spot.  :P