|
BlitImageThreadMode | |||||||||||
BlitImageThreadMode Mode | |||||||||||
Parameters: Mode = The Threading mode to enable for th blitimage commands |
|||||||||||
Returns: NONE | |||||||||||
BlitImageThreadMode controls the threading mode the BlitImage commands use. The blitimage command set are by design, big job commands, meaning they do a lot of data processing making them ideal for threading. Threading is the ability to execute a 'function' upon a different CPU core than your main program. So when we thread a blitimage function, PlayBASIC passes this task from our program onto some other CPU/Core. In systems that have more than one core, or physical cpu, we're thus executing two things at once. Systems with a single core cpu can still use threading, but the jobs are executed by the system in a 'one at a time' fashion. Depending upon the size of the threaded job, this can still be of benefit to a single core system. PlayBASIC currently supports two different types of threading. We have an immediate single job mode an a queue mode. We contol these using the BlitImageThreadMode MODE command. Threading Mode Values 0 = Threading OFF, (default) In this mode all blitimage calls are drawn by the program immediately 1 = Push Single Job, When this mode is enabled, the next blitimage function called will be pushed onto a second core and executed now. 2 = Start Queue. This setting makes the blitimage function calls stack up. They're not executed until you set the Mode to Render QUEUE mode 3 = Render Queue. Fires up the thread that renders the previously queued list of blitimage render tasks. I N D E X:
Threading Mode #1: Single Task The single task mode is the easiest threading available but also a fairly limited. However once you wrap your mind around this mode understanding the queue method will be much easier. Threading can seem fairly strange initially, so we'll tackle this using a human example. Let's imagine if we want to dig a pair of large holes in the ground. If there's only one person "Bill" , the work load falls completely on his shovel, so if it takes an hour to dig one hole, then it'll take two hours for both. Now what if Bill asked a friend "Joe" to help dig one hole with him, then they could immediately half the time that complete task will take. Now we're assuming here that both Bill & Joe are at the location where the works needs to be done. What if Joe can't make it for an hour ? Then the benefit of the pair working together isn't as efficient. This is what mode #1 is all about, is about sharing a job between a pair of cpu's in your computer, which would normally be performed on a single cpu running your program. So while this could theoretically double the speed of the operation on a multi core system, we're at the mercy of how quickly the operating system can start our threaded job. It may start immediately, but the system might also be busy and it might not start our task for a number of milliseconds. When that occurs, the efficiency is decreased. In this example we're going to split up a full screen blitimage render to simulate two workers sharing the load.
On my test systems this approach works, but it doesn't really return much benefit, since the screen size is fairly small and BlitImage function we're using are reasonably efficient to begin with. We might see some gain on larger screen or using a less efficient render function. There is another approach we can take, which is to give out worker threads two unrelated tasks to perform in unison. So if we can overlap the tasks equally, we potentially double our performance when the program is under stress. This is one of the difficulties of threading computer programs, working out where what jobs are performed by what thread and trying to balance the load. Which is easier said than done. In this example we're going to push the BlitImage function onto our second CPU core, then while this taking place we'll render our bunch of circles. So the key difference here is that we're no longer rendering to the same version of the screen, rather we're now manually doubling buffering. So the blitimage function is drawing (and modifying in this case) the previous frame, while we draw to a different version of the screen. This is necessary so both thread aren't trying to write to the same image together, which would cause all matter of unwanted visible artifacts.
This is generally the best way to make use threading in our programs, but it can be difficult to for new programmers to identity situations where some task could be computed by the second thread while our program is doing something else. Threading Mode #2: Queued Mode Threading a big single task can be beneficial given the right circumstances, namely the task is big enough to warrant the expense of executing it as a thread and we can structure out program to be doing something else while it's doing it's work. But often in computer games we generally need to perform a series of render operations to create our required output. The operations individually might not be that large, but they soon stack up and really chew through cpu time if we're not careful. Sometimes we can adjust our logic to or skip certain drawing operations, but sometimes we just need to draw a lot of material. This is where BlitImageThreadMode's QUEUE mode can help out. Queue mode turns the Blitimage drawing commands into a stacking mode, conceptually similar to CaptureToScene. When enabled, each blitimage command request is placed on an internal stack. These operations aren't called yet though, rather they stack up until we set the BlitImageThreadMode to RENDER mode. When the render mode is set, the thread is launched and the sequence of jobs are peformed in the order they were captured. Example, Imagine if we called BlitImageAlphaPostMultColour a bunch of times in a row (don't know why you would) when the queue is enabled, it'd stack like this,
So when the thread executes, it'll call job #0, #1, #2, #3 , #4 in that order. This allows you to set up a list of brute force operations and run them externally to our game/program logic. This is really the whole idea of multiple CPU CORES, it allows us divide and conquer. The potentially this that threading can make your program twice as fast, that's assuming a best case scenario where the load can be balanced perfectly, which i'm sorry to say is largely fantasy. The reality is that you're program is bound to have an uneven load. For example. if we shift 5 milliseconds worth of rendering onto core #2, but core #1 only has 1 milliseconds worth of work to do before it needs to use the images core #2 is working on. Then Core #1 ends up sitting around waiting for that task to complete, and we're losing most of the benefit. The goal should be to lay out our program so we can interleave the two tasks together in such a way that they either take the same time or core #2 completes it's task long before we need the result in our program. Threading does introduce a few no no's, the most immediate important one would be ownership of image resources. If we set up a render task and start the thread (run it on core#2), then those the thread is using, are now as no got zone for our program, until the thread has completed it work. This is because the two CPU's are potentially writing to the same memory at the same time. This is not possible in single cpu (core) system, where you can get away with this type of logic, but it's won't work on multi core systems. You can render to the same image, you just have to make you're not drawing over, or reading from drawn over shared pixels. If you do, you'll wacky unpredictable results. How Not To Use Threading Successfully threading our programs rendering can often come down to how well we grasp what is actually happening behind the scenes. In PlayBASIC we've simplified the process as much as possible, but there's no getting around it. When we launch a threaded render functions onto a second cpu core, what we've really doing is asking the operating system to execute this particular piece of code on a different CPU than the main program is executing. By doing this, we're attempting to get two cpu's working on our program at once. Generally our programs only ever use a single cpu. Systems that only contain a single cpu core can still use threads, but the operating system just can't run them together. So they actually run one after the other. Which can be still give some benefit with programs with a lot of rendering work to do.. Now since our primary threading objective is make our program share the work load between two cpu cores (when available), this means thinking not only about how we solve the problems our game design requires, but when we solve them. When you think about a program, it's really just performing a long list of operations in the order we want. Some of those operations are dependant upon previous operations and others aren't. Imagine we want to compute the new X position of our Hero sprite, so we'd query a sprites position, then add it's movement speed and write it's new position back. Which might look like this,
While we could rearrange and condense this example down into a single line of code , the order the individual operations are being executed in, actually stays the same. As in order to compute the sprites new X coordinate, we need to know the sprites previous x coordinate. So each operation is dependant upon the previous operation. Now If we want to make threading work in our programs, then we really need to set out the program isn't dependant upon the result from a threaded jobs. If that occurs then the we're holding up our main program, because it's dependant upon the result of a previous calculation. Which is a no, no with thread So dependency is something of a no no with threading, what we want to do is create a solution where our threaded jobs can be running on the second core while we're off calculating something completely different. Ideally if we can split the jobs evenly we stand to gain the most performance. But what often happens is a programmer starts up a thread and then immediately needs the result, forcing the program to sit and wait for it. When this occurs, it's just dead lost performance time.. In this example, we're wanting to use the BlitIMageClear function to clear an FX image.
Now, the core idea in is good, but the implemntation see's the BlitImageClear job pushed off onto our second core, but then almost immediately after, the program ends up sitting and waiting for it this job to end, since the following circle instruction needs thje previous job to be complete, before it can safely draw our circle. All this waiting is just dead time in your program. In this case there's a few solutions, the most commonly used approach is to double buffer our FX images. Double buffering just means we make two copies of our FX image. So when we call our threaded job(s) they can be off rendering to an alternative copy of the screen, while our program is drawing to the other version. This will minimize the amount of time the program is waiting, but the program is too simple for the balance to be distributed evenly. Here's how we might double buffer the previous example.
This time we've removed the main dependancy from our render loop by using the thread to clear the previous Fx image, while we drawing to the other one. So conceptually we're now able to clear the screen for free, at least in a perfect world. The reality is that the main programs render just isn't complex enough to ensure that our BlitIMageClear task has completed before the next frame needs to be drawn. FACTS: * Threading is only supported on the BlitImage commands. * Threading is available on all windows computers, however it's unlikely to make much difference in performance on single cored systems, or when only performing small idividual image blits. It tends to work best when the thread can draw a batch of material in one big hit * Some caution is needed when using threads that draws onto images in video memory, inparticular when drawing to the screen. The OS might call you thread during a refresh, creating frame tearing artifacts. There's also the potential for a crash if the OS deletes the video surface, such as when the log in screen appears. Example: |
|||||||||||
Example Source: Download This Example
|
Related Info: | BlitIMageThreadPriority | CpuCount | GetBlitImageThreadStatus | WaitOnBlitImageThread : |
|
|||||||||||||||||||||||||||||||||||||||
(c) Copyright 2002 - 2024 - Kevin Picone - PlayBASIC.com |