First attempt to load images using threading...

BlitzMax Forums/BlitzMax Beginners Area/First attempt to load images using threading...

Arowx(Posted 2010) [#1]
Just had a play trying to see if I could get images to load via threading, but it just throws an exception and crashes where am I going wrong?

'What happens when I load grphics in via threads....?
SuperStrict

Graphics 800,600

Global timeTaken1:Long
Global timeTaken2:Long

Global startTime:Long = MilliSecs()
Global imageUrl:String[10]

imageUrl[0] = "D:\Dev\BlitzMax\cancerWars\Gfx\brainagonLogo.png"
imageUrl[1] = "D:\Dev\BlitzMax\cancerWars\Gfx\brainagonLogo2.png"
imageUrl[2] = "D:\Dev\BlitzMax\cancerWars\Gfx\brainagonTitle.png"
imageUrl[3] = "D:\Dev\BlitzMax\cancerWars\Gfx\characters.png"
imageUrl[4] = "D:\Dev\BlitzMax\cancerWars\Gfx\cwIcon.png"
imageUrl[5] = "D:\Dev\BlitzMax\cancerWars\Gfx\desktop.png"
imageUrl[6] = "D:\Dev\BlitzMax\cancerWars\Gfx\mainMenu.png"
imageUrl[7] = "D:\Dev\BlitzMax\cancerWars\Gfx\options.png"
imageUrl[8] = "D:\Dev\BlitzMax\cancerWars\Gfx\screenshot.png"
imageUrl[9] = "D:\Dev\BlitzMax\cancerWars\Gfx\logoRender1.png"

Const maxImage:Int = 10


Global imagesA:TImage[maxImage]
Global imagesB:TImage[maxImage]

'Test 1 load some graphics in and time it

For Local I:Int = 0 Until maxImage 
	imagesA[I] = LoadImage(imageUrl[I])
	Assert imagesA[I], "Failed to load image "+imageUrl[I]
	Print "A Loaded "+imageUrl[I]
Next

timeTaken1 = MilliSecs()-startTime

Print "Single Thread TimeTaken: "+timeTaken1

startTime = MilliSecs()

'Test 2 load some graphics in threads and time it

Global Threads:TThread[maxImage] 

For Local I:Int = 0 Until maxImage
	Threads[I] = CreateThread(LoadImageThreaded,imageUrl[I])
Next

Global notCompleted:Int = 0

Repeat
	
	notCompleted = 0
	
	For Local I:Int = 0 Until maxImage
		If Threads[I].Running()
			notCompleted:+1	
		EndIf
	Next

Until notCompleted = 0



For Local I:Int = 0 To maxImage

	imagesB[I] = LoadImage(TPixmap(Threads[I].Wait()))
	
	Assert imagesB[I], "Failed to load image "+imageUrl[I]
	
	Print "B Loaded "+imageUrl[I]

Next



timeTaken2 = MilliSecs()-startTime
Print "Threaded TimeTaken: "+timeTaken2



Function LoadImageThreaded:Object(data:Object)
	
	Local url:String = String(data)
	
	Local pix:TPixmap = LoadPixmap(url)
	
	Return pix
	
End Function 



Brucey(Posted 2010) [#2]
Well, you seem to have a hard loop which is waiting for your threads to finish..

then... you are waiting for them to finish - and to return the TPixmap.

But, since they've already finished by then, I'm not sure waiting will be very useful to you :-)

The code isn't really using threads as they were meant to be used. This is basically a procedural example that just so happens to launch the load in a new thread.


Arowx(Posted 2010) [#3]
Well the examples I've found just indicate that you launch a threaded funciton, pass in some data and get a returned object when it completes, are there any better examples of thread usage I could apply to loading images?


Brucey(Posted 2010) [#4]
But if you are going to background load... why would you want to hang around in your main thread waiting for them to finish?


Arowx(Posted 2010) [#5]
So I should just pass the list of files to load and return a list of images?

But I will still need the main thread to check to see when they are loaded?


ima747(Posted 2010) [#6]
Point 1: threading is a PITA.
Threading tends to be hard to debug, and opens the door for a lot of other nasty bugs to creep in. And generally you can't just turn it on and off so you have to accept the hassles up front if you want to use it. Further, lots of stuff (such as drawing graphics) is not (generally) safe in anything but the main thread, so you have to do some research before you start firing off threads. And lastly, as illustrated by the previous sentence. There are very few finite things in threading, there's usually a way around the a problem, it's just probably going to make you crazy. Just a little disclaimer really :0)

Point 2: threading doesn't work like normal linear execution.
The point of multi threading is that one thing can be happening on one thread (say loading a picture) while something else is happening on another thread (say drawing the pictures you've already loaded). As a result your program should optimally never wait for a thread (unless the thread HAS to be completed before you can progress... in which case you have to ask if it needed to be a thread in the first place... see paragraph one re: hassles...). You can't think of a thread as a function that you call, and wait for a response. But rather an assistant that you give a task to. You can periodically check if the thread is done, and see what the result was, but that leads you back to linear flow and you can probably spend that "are you done yet?" time better somewhere else.

Here's a little (UNTESTED!) example of a threaded load. Expect problems but hopefully it will give you an idea of what I'm on about...
SuperStrict


Global loadedPictures:TList = CreateList() ' a tlist of loaded pictures
Global pictureListLock:TMutex = CreateMutex() ' a mutex to prevent overlapping access of the picture list which causes bad things to happen...
Global picturePathsToLoad:String[] = ["pic1.png", "pic2.png", "pic3.png"] ' list of the pictures we'd like to load


Function LoadPictures:Object(Input:Object) ' threadable function that loads the pictures
	For Local onPic:String = EachIn picturePathsToLoad ' loop through the pictures we want to load
		Local newPic:TImage = LoadImage(onPic) ' load a picture
		LockMutex(pictureListLock) ' makes sure that nothing else is accessing our TList
		loadedPictures.AddLast(onpic) ' add the picture to our list since we're sure it's safe to play with the list
		UnlockMutex(pictureListLock) ' when you're done with something you have to put it back or else no one else can play with it...
	next
End Function



'####### Main code starts here

Graphics(1024,768) ' start the graphics etc etc,

Local loadingThread:TThread = CreateThread(LoadPictures, Null) ' Start the loading processin a child thread so that the main thread can keep executing


While(Not AppTerminate()) ' just cuz it's an example we loop forever... or until the app says it's time to go...
	Cls()
	LockMutex(pictureListLock) ' we're about to interact with the picture list, make sure it's not busy...
	Local currentlyLoaded:Int = loadedPictures.Count() ' get the number of currently loaded pictures
	UnlockMutex(pictureListLock) ' let go of the picture list since we're done with it
	
	DrawText("Loaded " + currentlyLoaded " out of " + Len(picturePathsToLoad) + " pictures so far...", 15, 15) ' draw the current progress
	' Note that we don't lock anything for the picturePathsToLoad array even though it's accessed in 2 threads at once potentially...
	' It is generally safe you READ from something in multiple places at once, but if you change it while it's being read...
	' or While you're trying to change it somewhere else...
	' or if you try to change while it's being read...
	' ... there be dragons. YE have been warned!
	
	Flip(1) ' flip with a VSync since we could use the extra time for loading, there's no rush
	Delay(10) ' there's no rush since we're just updating a progress indicator
	
	If(currentlyLoaded = Len(picturePathsToLoad)) ' looks like loading should be done... might be worth checking... this runs on the assumption my loads will never fail, not a real world example here...
		If(Not ThreadRunning(loadingThread)) ' ... if loading is done...
			Exit ' bust out of this loop since we've got nothing left to wait for
		End if
	End if
Wend

Print "Loading completed!"
EndGraphics()


You'll note that the child thread is launched with no input, and no output, it just does it's thing (you can do input and output, I'm just illustrating how autonomous a thread can be). Since we don't have to wait for it we can go about our business in the main thread... in this case notifying the user how things are progressing...

Also notice the mutex locks and unlocks, that's just a super simple taste of the extra thought you need regarding multiple access points to your data... since you don't know exactly what is happening "elsewhere" in your program at any given moment, whenever you do something potentially dangerous you are responsible for making it safe...

The up shot of all of this extra hassle is you can make more happen at once, which can mean either less waiting, more responsiveness, or just getting more done in the same amount of time (hardware willing :0)

Hope that helps...


Czar Flavius(Posted 2010) [#7]
My game has more than 50 pngs and it loads in about 1 second on a good computer and 4 seconds on a slow computer. Unless you're doing something really extreme (and I don't think 9 images is extreme!) then forget about threading, it will be more hassle than it's worth.

In my experience bitmaps load faster than pngs, but for 9 images it's nothing to lose sleep over.


Arowx(Posted 2010) [#8]
Hi ima747,

Thank you, nice it works as well, couple of typos for instance your loading the list with the strings not the pics, but once that's fixed it worked fine!

Maybe I can say goodby to loading bars, or at least hide them under transitions! ;o)


ImaginaryHuman(Posted 2010) [#9]
I think it's reasonable to use threading for loading since the PNG loader has to spend time doing decompression, you might be able to get some time savings. But more usefully, you could/should be loading resources `in the background` while the user has something useful/meaningful to do, so that they don't even notice any delay at all.

I also found RAW image data loading to be faster than PNG. I had to load maybe 60 fairly large images, took several second for PNG, a couple for RAW.


Czar Flavius(Posted 2010) [#10]
If you are loading several images from the same PNG file, it can save a LOT of time by loading it as a pixmap in memory and then loading your subimages from it. Otherwise each loadimage will decompress the png again and again. I posted some code for this in another thread a while ago.


ima747(Posted 2010) [#11]
Followup thoughts for someone stumbling on this thread while researching threading for themselves.

IMO the simplest example of the value of threading in games is to cover time sinks like loading screens. If you can blend resource management into the background you can make a much more immersive experience.

Another great use is an extension of the previous. Real time resource management becomes much easier when you don't have to write your own custom loading routines, just do your loading invisibly. This is the kind of thing that allows open world games and the like to function.

And then there's the yet more complex concept of threaded processing where you actually offload some of your processing tasks to other thread(s) (say your enemy AI, or physics calculations). Much harder to pull off but this lets you maximize modern hardware and leverage that extra processing power for more detail which in theory should translate through proper design to a better/more believable experience.

All of that said Czar and ImaginaryHuman have a very good point. Just because you can offload doesn't mean you shouldn't still optimize. Better use of your resources should always be a focus. Additionally multithreading even gives you a better return on that investment since you have even more you can do with that saved time.

And finally just a reminder, threading is a PITA! Balancing your development time and resources is crucial to any project. Before embarking on threads consider the added development and testing time. It might make more sense to stick to a single thread and just make a pretty loading screen, especially if it's at a logical break (between levels...) and doesn't take too long on a modern system. Also bear in mind each thread will compete with every other process/thread on it's core. Adding threads only adds processing cycles to your game if there are enough resources available to handle it. If you're running at 90% usage of a single core system already another thread is going to make things worse not better. If you're targeting multicore, or you have wiggle room above your current usage then you've got room to play.


Brucey(Posted 2010) [#12]
Here's a small Win32 demo showing that BlitzMax is quite capable of streaming data from the internet to a disk cache, and loading it for display from disk, while smoothing scrolling, and zooming.

osm_demo2.rar (1.1 MB, win32, mt release build)

Click and drag to scroll.
Mouse wheel to zoom in/out.

Expects to be able create a folder called "tiles" where it is run... to cache the images.

There are still some bugs in Max's thread code, as it can crash on occasion. ima747 is acutely aware of these, I'm sure :-)


Yan(Posted 2010) [#13]
Just out of interest while we're on the subject of threaded pixmap loading. Does anyone *know* which of the pixmap loaders are thread safe?

I'd assume the BMP loader is fine as it's pure bmx code, but what of the others?


ImaginaryHuman(Posted 2010) [#14]
I don't know, myself, sorry.. ... but I would think you'd need to check the documentation for the png/jpeg libaries which blitz is using. My guess is the parts which blitz does are thread safe if they're much the same for BMP as they are for other formats. Then it's up to the image libraries as to how they do stuff?


Dreamora(Posted 2010) [#15]
if the code is part of a bm module it should work fine as it will run in the thread its called.

if oyu added something through an external dll you will have to check that dlls docs if it does processing in an own thread. for image libraries or loaders in general that should not be the case though.