Threading - how to do this?
BlitzMax Forums/BlitzMax Programming/Threading - how to do this?
| ||
When my game sections are loading, the mouse pointer freezes and I don't want it to, so I figured there are two ways of solving the problem using threading. 1. Load all my game assets in a separate thread, and let the main loop draw the mouse pointer until the thread is done loading. 2. Load the assets in the main thread (like they are now), and have a small 'draw' loop in a thread to keep redrawing the mouse pointer. Close the thread when all the assets have been loaded. I'm leaning slightly towards option #2 for simplicities sake but I heard somebody mention, somewhere.... that you should do any drawing stuff in threads. Why not? Thoughts? |
| ||
I heard somebody mention, somewhere.... that you should do any drawing stuff in threads. Why not? I believe graphics contexts are not thread-safe / shared across threads in all instances. You might find #2 works for you, but might not work for all. I'd go with #1, and have the thread raise an event (of your own devising) when the media is ready, then your main loop can waitevent on this / timerticks. (I reckon it ought to be possible to add a LoadImageAsync and EVENT_IMAGELOADED to Max2d that does just this). Also by going with #1, if you are doing multiple things on load (pre-calcing data / unpacking data to memory / loading from multiple different sources e.g. incbin / file /web) then you can expand this by creating separate threads for each area where it would make sense (although no real point in doing 10 threads for loading e.g 10 files from hard disk). |
| ||
The easiest way is to use a single unsafe semaphore. Pseudocode should look like this: Global Loading:int = False Function LoadMedia() Loading = True RunThread2 While Loading DrawThings Flip Delay(1) Wend End Function Function RunThread2 LoadEverything Loading = False End Function If you read a wrong value in the Loading var, on the main thread, it means it has been loaded anyway, so it works ok as long as you don't do anything else with the Loading var, and you're not trying to return a value from the Tread2 to be read on the Thread1. |
| ||
Good tip. Thx! |
| ||
also, as the graphics context are not thread safe, I think you better load images as pixmaps and convert them to regular images on the main thread, and don't try to draw them while they're being loaded! Anyway, converting a pixmap to an image is way faster than loading an image from disk so, unless you're doing something very complicated, it shouldn't be a problem. |
| ||
When you load an image does it immediately put it into vram? I thought that was done automatically, when needed. |
| ||
@Plash: I'm not sure, maybe it's right that the images are just sent to the vram when needed by the rendering engine. I supose a look to the modules should make it clear. Has anybody took a look at it? |
| ||
It would seem that it does not do magic, for OpenGL at least. |
| ||
I've done a small silly test:Strict Global Image1:TImage Global Image2:TImage Global Image3:TImage Global Loading:Int = False SetGraphicsDriver(GLMax2DDriver()) Graphics(800, 600) SetBlend ALPHABLEND Load() While Not KeyHit(KEY_ESCAPE) Cls DrawImage(Image1, 0, 0) DrawImage(Image2, 200, 0) DrawImage(Image3, 300, 300) Flip WEnd Function Load() Loading = True Local T:TThread = CreateThread(LoadData, Null) While Loading Cls DrawText("loading " + millisecs(), 0, 0) Flip Delay(1) Wend End Function Function LoadData:Object(dat:Object) Image1 = LoadImage("InstallIcon.png") Delay(500) 'If it's too fast, where's the fun? Image2 = LoadImage("InstallIcon.png") Delay(500) 'If it's too fast, where's the fun? Image3 = LoadImage("InstallIcon.png") Delay(500) 'If it's too fast, where's the fun? Loading = False End Function and it seems there's no problem loading directly images in OpenGL and DirectX. So, it seems the images are sent to vram when drawn for the first time! Yay! EDIT: put a file called installicon.png or whatever in the same folder as the sample! |
| ||
You should find another example of threaded background image loading in the BlitzMax samples, under Threads (assuming latest version). and it seems there's no problem loading directly images in OpenGL and DirectX. I'm a little surprised by this, yet that certainly works here. Maybe it's because they're not being drawn until the thread is finished? I thought they would have to be loaded in the main thread, though, since my understanding is (or was!) that the DirectX/OpenGL 'context' that they belong to is only accessible to the main thread. |
| ||
I think the problem on DX is on concurrent draw operations, while on OpenGL is the complete context. Now sure when the images are being stored on the vram, given the fact that Max seems to survive greaphicsend and graphics to switch full-screen and windowed mode, I "think" images are stored also as pixmas and sent to vram when needed? |
| ||
I'm a little surprised by this, yet that certainly works here. Maybe it's because they're not being drawn until the thread is finished? I thought they would have to be loaded in the main thread, though, since my understanding is (or was!) that the DirectX/OpenGL 'context' that they belong to is only accessible to the main thread. I read somewhere that LoadImage does some jiggery-pokery with Pixmaps behind the scenes anyway.Also, its possible to change from DirectX to OpenGL mid-application without losing any graphics. So there's quite clearly something very clever - or possibly witchcraft - going on that none of us understand. [edit] ...or to put it another way - what Ziggy said. |
| ||
Since I wrote the Direct X 9 Driver I am pretty familiar with the internals of TImage. The LoadImage command loads a pixmap. When its time to draw them the BMAX2d driver requests and TImageFrame from the Image it is at that time that the images are loaded into VRAM. This is also why TImages survive driver switches. So you should be ok loading the Images I would say its probably safer to load Pixmaps in the background thread and use LoadImage after the thread is complete and not rely on the behavior of TImageFrame and on demand loading. I typically after loading all my media draw it once before going into the main loop so there are no stutters as the images get loading into VRAM for the first time. Doug Stastny |
| ||
Since I wrote the Direct X 9 Driver I am pretty familiar with the internals of TImage. The LoadImage command loads a pixmap. When its time to draw them the BMAX2d driver requests and TImageFrame from the Image it is at that time that the images are loaded into VRAM. This is also why TImages survive driver switches. Precisely what I thought! |
| ||
Hmm.... that seems like an awful lot of farting about. The very idea of loading media in a thread, is so that I can have an animated mouse pointer, loading screen or some such while all the assets are loaded. Is there really any benefit to doing this if I have to convert it all to TImages from Pixmaps, from the safety of the main thread anyway? And how about audio stuff? Is that thread-safe? [edit] For what its worth, I just tested Ziggy's example on two PCs: 1. Athlon64 X2 5000+, 2GB RAM, 512MB Geforce 8500GT, Vista Home Premium. 2. P3-733MHz, 256MB RAM, 64MB GeForce2 MX400, Windows XP. Works perfectly on both systems - even the cruddy old pentium. I've just posted this thread to get some more general feedback on this. |
| ||
Is there really any benefit to doing this if I have to convert it all to TImages from Pixmaps, from the safety of the main thread anyway? Yep, try the example I mentioned. Loading images from pixmaps is pretty much instantaneous -- it's only the loading from disk that's relatively slow. In my example, the pixmaps load in the background while allowing animation to take place on-screen. (The example converts several pixmaps to images at once without any trouble here, but you could just convert each one as it's loaded if you preferred.) That said, it sounds like LoadImage is fine. I'd just be slightly wary of the possibility that Mark might have to change things due to some unexpected threading issue later on. If you wrap the loading process up a little bit, though, you can at least make sure you would only have to change a small amount of code if that became necessary. |
| ||
Yep, try the example I mentioned. Loading images from pixmaps is pretty much instantaneous Ah. I really must learn to read. I did look for it earlier but I went in the hitoro folder. Didn't find anything. ;) |
| ||
If you wanted to play real safe you could always load the image into a bankStream and then LoadImage(myBankStream) when needed. I'm guessing this would also work for other media (sounds etc). |
| ||
I think you'll love this one:Strict Graphics 800, 600 'We create an MTImageLoader: Local Loader:MTLoader = New MTLoader 'We feed this loader with URLs to load images from. This is done using the 'ImageLoader object: For Local i:Int = 0 To 5 'We will load 6 times the same image, for testing only. Local IL:ImageLoader = New ImageLoader IL.Origin = "C:\Users\Manel\Pictures\camafeos-baja.jpg" REPLACE WITH YOUR OWN FILE Loader.AddImageLoader("Image" + i, IL) 'Arguments are NAME, IMAGELOADER Next 'Now the MTLoad has a list of the images we want to load in the background, 'we call the LoadImages method: Loader.LoadImages() 'Now, the ImageLoader IS working in the background, so we check for it to complete: While Loader.ImagesLoading() = True Cls DrawText("Loading!", 0, 0) DrawRect(Rand(0, 800), Rand(0, 600), 10, 10) 'Dummy draw to show activity Delay(10) Flip Wend 'Now images are loaded, we want to get an image and set it to a regular TImage object: Local IL:ImageLoader = Loader.GetImageLoaderbyName("Image0") Local Image:TImage = IL.Image 'That's it! We show the image on screen: While Not KeyHit(KEY_ESCAPE) Cls DrawImage(Image, 50, 50) Flip Wend End '--------------------------------------------------------------------------------------- Type MTLoader Field _Loading:Int = False Field _Images:TMap = New TMap Method LoadImages() If brl.threads.CurrentThread() <> brl.threads.MainThread() Then Throw "Image loader has to be called from Main Thread." End If If _Loading = True Then Throw "Already loading images" End If _Loading = True Local T:TThread = New TThread T.Create (Self._ProcessDelegate, Self) End Method Function _ProcessDelegate:Object(Val:Object) Local MTL:MTLoader = MTLoader(Val) MTL._Process() End Function Method _Process() For Local IL:ImageLoader = EachIn _Images.Values() Print "loading " + IL.Origin IL.Image = LoadImage(IL.Origin) Next Self._Loading = False End Method Method ImagesLoading:Int() Return _Loading End Method Method AddImageLoader(Name:String, IL:ImageLoader) If _Loading Then Throw "Can't add images to the loader while loading is being performed!" End If If _images.Contains(Name.ToLower()) = False Then _images.Insert(Name.ToLower(), IL) Else Throw "Duplicate ImageLoader name!" EndIf End Method Method GetImageLoaderByName:ImageLoader (Name:String) Local IL:ImageLoader = ImageLoader(_Images.ValueForKey(Name.ToLower())) Return IL End Method End Type Type ImageLoader Field Origin:String Field Image:TImage Method Create(url:String) Self.Origin = url End Method End Type Every MTLoader object creates an async image loader thread that can be controlled from main thread. You assing a list of images to the MTLoader object, with a key for each image for later referencing, and then call the LoadImages() method (this method will create a scondary lading thread in the background) You will be able to know when all the images in the object have been loaded by calling the method ImagesLoaded(). when ImagesLoaded() return TRUE, you can get a loaded image by its internal key name, and assign it to a regular TImage object. this also manages the pixmap need (in fact no-need) and everything discussed here. Hope you'll find it usefull! This is just a small sample, take into account you could have several MTLoader objects to take proffit of several cores on a machine, so images could be loaded using 2 or 3 MTLoader objects at the same time, as long as you check for Loader1.IsLoading() or Loader2.IsLoading() or Lader3.IsLoading(), etc... EDIT: Note that the NAME paramter on AddImageLoader and GetImageLoader is case-insensitive, so Image01 is the same as imAGE01. |
| ||
If you wanted to play real safe you could always load the image into a bankStream and then LoadImage(myBankStream) when needed. I'm guessing this would also work for other media (sounds etc). That's a pretty good idea, actually. With Pixmaps it's probably much the same (since they're just blocks of memory), but you could make a much more generic threaded media loader in the style of Ziggy's doing it with bank streams. (You'd just call LoadImage, LoadSound, etc, on each returned bank stream depending on a flag you set during the load request.) This is a simple modification of the image downloading example found in 'samples\threads' (which loads one image at a time, drawing all images as they're loaded) to load local images instead (just makes the demo less flashy). Again, it should be easy to modify to use bank streams for loading all sorts of media. For added simplicity, I'm pretty sure it'd be safe to just pass LoadPixmap directly to CreateThread, ie. CreateThread (LoadPixmap, Pic [index]). It certainly works, and LoadPixmap doesn't seem to depend on anything global, but wrapping it like this at least lets you fiddle with the pixmap/data before it's loaded/returned (as well as letting me add a delay for demo purposes)... Note that most of the above is demo-specific -- the main section of interest is highlighted below (ie. the loading screen loop). If you take the comments out, you'll see how simple it really is... Anyway, plenty of options. |
| ||
stupid post. forget it. |