The thread-thread! Questions regarding threading
BlitzMax Forums/BlitzMax Programming/The thread-thread! Questions regarding threading
| ||
In general, I understand what threading is, what it does, and how it works in general. For my current project, it would be a very massive advantage to have multi-threading, for one part of the code in particular. I got threading to work with very simple things. However, my scenario is a bit more complex. First of all, I have an OpenGL context, inside which I draw, obviously. Now I understand from searches in the forum that rendering with OpenGL and threading is a very delicate thing. In simple terms, I have models of different "resolution", and depending on camera distance I want to swap those models for lower or higher resolution. In general, this is working perfectly fine. However, when I create and detach a thread that basically just looks after "model swapping" (it really isn't, but just to keep it simple), all kinds of weird things happen. My models are in a VBO. The part of the code basically destroys the VBO, reads model data, and uploads (the now lesser data) into the graphic card, with a new VBO. In single thread, it works as expected. In multi-thread, it only partially works. You can see it's doing something - however even though the VBO is destroyed, data seems to remain. Colors go completely sideways as well as the information for normals of the polygons. So the question for me, really, becomes: how does one make an OpenGL application (multi-) thread safe? Is it an idea to maybe put the rendering itself in a separate thread? Thanks for reading this far, and thanks for any insightful answers. If anything is unclear, tell me :) |
| ||
I'm sure I've read repeatedly around here that all graphics stuff must only be done in the main thread. Perhaps you can use child threads to prepare raw data (vertices, normals etc) but you can only have the main thread upload it to OpenGL. |
| ||
Mark's initial threading release post is here.. http://www.blitzbasic.com/Community/posts.php?topic=80344 in which he states OpenGL contexts are 'per-thread'. But again, I'm sure there are still issues beyond that. |
| ||
I tried using multi-threading as well to test this feature. I didn't use OpenGL, but I use the Xors3D engine. In the main thread I was rendering the entire scene, while another thread spawns extra spheres every 100ms. This posed some problems because while the scene was being rendered by the main thread, the separate thread created a new sphere at the same time and the entire program crashed. Using only 1 thread never crashed. As I investigated some more, I found numerous topics about graphics stuff being done in the main thread only. Doing graphics stuff in separate threads caused nothing but problems that cannot be easily solved. You could still have a flashscreen in the main thread and an animating cursor, while the separate thread loads new models I guess. But in such case, you should only draws the flashscreen and the cursor, stay away from rendering commands that render the scene while it's being loaded. I guess you could make your own LOD system, where you want to fade-in objects that are about to enter your camera-range. Those objects woiuld still be outside the range of the camera and won't be rendered anyway. In this case, I think you could stream your distant objects (load them when needed) using your separate thread before they come into range. But I haven't tested this yet. It could still be a problem if the engine checks the distance of every polygon in all your models to see if they have come in range of the camera. |
| ||
OpenGL available through BMax is limited to main thread access only. If you are trying to do anything that in any way communicates with the GPU or openGL it must be on the main thread. A little background that might help clear up some confusion regarding threading and OpenGL etc. New versions of OpenGL can me multithreaded in various ways. BMax doesn't use or init this. Older versions are technically single thread, meaning they can only be safely accessed form the thread that inited them, but that thread doesn't *have* to be the main thread. However from my experience and understanding BMax does it's own thing again and trying to create and manage a context within the scope of a child thread will result in tears. So you must confine graphics related commands to the main thread under bmax (unless you want to write your own opengl driver and not use any bmax graphics related commands). Engines (Xors3D for example) may support higher versions of OpenGL, but unless they're totally disconnected from bmax's graphics management (that includes memory management behind the sceens, I'm not personally familiar with the engine, or how it manages data, etc. just going on hypothetical here) you will be bound by lowest common denominator, being that something is going to try to poke the graphics from the main thread at some point and that means the graphics have to live exclusively there or all hell could break loose. So you might wonder what good is multithreading if I can't do any graphics work including loading. You can, you just have to be VERY careful with what you do. You have free reign over OS (not GPU) level resources. You can load from disc into RAM and do any pre-processing you like, so long as you don't talk to the video card until it's back on the main thread. If your engine handles all of that and isn't fully thread safe then you're likely to run into problems. Multi threading is hard for the following reasons: 1) It's just hard to properly manage things safely without stepping on your own toes. If it works 100 times it might just mean you aren't getting the timing wrong enough to see a problem that does indeed exist. You have to be VERY thorough. 2) There's a lot that isn't thread safe. If you step over the line anything could happen, including nothing... you have to be VERY aware of everything going in behind the scene's if you're not using things that have been vetted as totally thread safe. 3) It's less stable. This is more to do with BMax than threading in general, but bmax has a couple gremlins deep in the bowels of the multithreading systems, the most notable is with the garbage collector. It doesn't behave like the normal GC at all, and it's definitely not as reliable or trustworthy. All that said you can certainly make multithreaded things in BMax, but as outlined above you have to be obsessive about attention to detail and knowing what you're doing on a very low level in spite of the abstraction or you're going to run into some snags. And even if it's perfect you might still hit some snags due to the GC or other factors. Re: SystemError51 specifically if you're trying to swap models from a child thread you're accessing the GPU from a child thread and that's not going to go well at all. I use a texture LOD manager in one of my 3D projects under minib3d. The way I handle it is use a child thread to load the texture to a pixmap, and then trim to the resolution I want, do any other processing it needs (squaring etc.) to get totally prepped for 3D (note that all the processing is done entirely on the CPU, I use an image manipulation lib from brucy which isn't thread safe but is purely CPU based and since I only access it from one thread it is safe to use in that context). it then has to safely hand the texture to the main thread for it to be loaded to the GPU as a usable texture. It's complicated but it's the only safe way to do it. Last edited 2012 |
| ||
matibee - Very interesting thread regarding that subject, read into it. Also bookmarked. PowerPC603 - While I didn't experience crashes at all, I did experience things going sideways when threading. Color or normal information getting lost, or things not loading at all. One thread - everything scrolls like butter. ima747 - Now this is some very thorough and useful information. In the end, it seems that ultimately I'm stuck to one main thread... and I have to find other ways to make things work. In general my engine does what it's supposed to - threading would have been nice. Guess you can't have it both ways. For now, anyways. |
| ||
The simplest way to shoehorn some form of threading into things I find is to do *something* safe, and just make the best of it. i.e. perhaps you can load the mesh from disc into a memory buffer, and then pass that to the main thread to do the rest of the apparently unsafe loading? All depends on what and how you're loading. And since bringing in multithreading brings in the multithreaded GC which I touched on previously as being not so great (it tried to eat my dog once I swear! and this is only a little paranoid compared to what it can do to you...) it probably isn't worth it. Running good is good. running better is better, but not when it kinda-runs-a-bit-better-but-then-sometimes-doesn't-work-at-all (patent pending). Good luck! |
| ||
From my experience the only real way to do multi-threaded in a 3D environment is to move over to an MCV architecture. The key here is that you separate your control classes from your view classes, and this way you can keep your view classes on the main thread, but control classes can be on any thread. For instance, I wrote a fractal landscape generator recently, and because fractals take an age I wanted to view the landscape whilst it was creating, which allowed me to test stuff in a minute or so without the 90 minute wait for the landscape to generate. To do this I made the generator a control class which worked on the data class( in this case an object containing a memory bank ). The view class then read the data class to draw the landscape in whatever state it was in and ran from the programs main thread. MCV is a popular architecture out in the professional world too, so it's a good skill to learn. |
| ||
MCV in Multithreading still has to suffer from only one thread being able to access opengl the blitzmax way. The one you can multithread is "control" - and this transforms to "cpu calculations". MCV pattern wont help us Blitzmaxers to do multithreaded opengl processings. Also "games" should avoid MCV as the benefits dont work out the time needed to implement. Why? "Views" are there to handle different ACL-depending views on a subject, to abstract Logic from Presentation etc. It is just overkill for the main purpose of BlitzMax (exception is your personal favor :D). bye Ron |
| ||
I take the view that abstraction leads to obfuscation, in a 1 developer game there is definitely a limit to how far you want to abstract. The problem with doing graphics on a sub-thread is not Blitz specific, or even OpenGL specific. It is an unfortunate side effect of the current technology. I'll certainly take your side in admitting that this is problematical, and there is no good solution as a consequence. For me taking an MCV approach allows me to get much more "stuff" into threads than I could do previously - but you are right that the limitation on only doing graphics on the main thread isn't solved. What it does is get much more of the rest of the program off the main thread, freeing it up for the graphics. Of course, in a simple game almost everything you do is handling the graphics - but one could argue that in this instance there is no benefit to using multi-threading anyway. Where multi-threading really helps is when games have a lot more logic going on. |
| ||
I only use multithreading to process networking while something stops the window from updating (drag a windowed app around in Windows - processing freezes). On Applications running with multiple user levels (business apps) I use the MVC pattern (or its extensions) but for games I just split between "Physics" and "Graphics" (logic, presentation). So I don't use the model-part to abstract the inheritance of the used resources. Ok that is kind of a lie as I use a AssetManager and XML-files to make it mod-able for externals - but this is not common for "normal blitzmax games". Nethertheless I would not like it to call that thing "MVC"-approach. What I've experienced using my way: it makes the code less "overview-able" as there are much more functions included (each thingy is splitted into "update" and "draw", instead of "myship.draw(xxx)" I use 'Assets.GetSprite("myship").draw(xxx)' etc.) Sure that way I can control if a specific game state is able to get/use a certain sprite but it ends in writing more code than normally needed. If one starts with programming in general I do not advice to use such patterns. Use them if using in Teams or if you want to open it to the public (so one can continue your development more easily). Same goes for "over oop-ing" everything (using types for each small thing you use more than once) - but using it, makes it easier to integrade threading (object mutex). Hmpf... at the end it seems we should mix it together in a way which fits our style the best. bye Ron |