|
By: Kevin Picone What's This ? The following is a collection of tutorial / discussions taken from the PlayBASIC forums. For the most part, they're designed to outline some of the do's and dont's about Images, Maps & Sprites in terms of performance. How well you understand them, can make a huge difference to the quality of programs you'll be able to produce. Since the material covered here is quite technical, we highly recommend picking over the provided samples and re-reading it as many times as necessary. I N D E X: What Image Formats can I load ? The PlayBASIC image loader(s) currently supports a subset of common image formats plus it's own Image format PBI.
Alpha channel is supported for PBI, BMP, TGA & PNG images. See LoadAFXimage / LoadImage for loading images with alpha channel. Can I load all permutations of common Image Files ? No. The internal image loading library only supports the most common file specifications at the time of image loaders were written. Therefore there are BMP, TGA, PNG and JPEG files it will not load out of the box. This is not uncommon as most so called 'game' image loaders have similar limitations to keep the library sizes down. So you might need to convert some files in order to use them. The package comes with a version of the FreeImage libraries, which includes various saving routines for formats like BMP, PNG, JPG (See FreeImage_LoadImage, FreeImage_SaveJPG, FreeImage_SavePNG , FreeImage_SaveBMP ) What are PBI image files ? PBI is PlayBASIC's very own image format. The format is designed primarily as a compressed 32 bit lossless format for 2D game images. The compression is only light though, focusing mostly on zone based colour and area duplications, which often appear in animations and tile maps. The light compression allows PBI files to load quickly (about the same or faster than BMP / TGA files) as well archiving ( Zip / RAR / 7zip) very well, generally much better than PNG files, meaning you can save a lot of file size in your final games distribution by swapping to PBI format. You also get the added bonus of PBI not being a 'common' place format, so only people with access to PlayBASIC would be able to load none encrypted images. You can load files using the standard LoadImage commands. To save an image to disc in PBI format you'll need to the use the SavePBI command. Video Cards like Big Blit's Even if you've only been programming for a short while, you've perhaps noticed a strange anomaly when drawing (bliting) images. Which is, why does the blitter (the image drawer) seem to choke up drawing a lots of small images, when it can throw around huge ones easily ? To answer this, we have to look at how Windows give us control of the video hardware. It should be noted, that we're primarily discussing the rendering of Video Images (those stored into your graphics cards video memory) and not those in system memory (FX/AFX image types). Your PC has what's termed a blitter. You can think of this as the part of the graphics chip solely designed to draw / copy rectangular graphic data very quickly. Now every time you create a image (in video memory) and render it. PlayBASIC uses the blitter device to transfer the pixel information for you. Now this is all lovely, but there's a catch ! The Blitter is a shared device, and we're not the only one's using it! (Remember, Windows + any other program running in the background are also using it.) Since the blitter is shared, when we want to draw (blit) some graphics, PlayBASIC is forced to wait inline for the blitter to become available (from either a pervious draw call, or some other program / task is using it). Once it's free, we can send it our drawing job and continue on our way. The drawing will take place while our program continues running. This is called asynchronous rendering. Which is fancy way of saying the graphics chip is drawing, while the main computer CPU continues on doing something else, such us running our program code. (Note: Not all video cards support this!) The issue is that if we call the blitter frequently, we're inevitably going end up stalling our program in wait loops, while DirectX waits for the graphics cards Blitter to become available to us. This is what occurs when we're try and draw lots of small images with the blitter. While the drawing might be quick once acquired, all this polling & waiting around for the blitter equals one sure thing, lots of wasted time. Which can really impact performance. You've probably noticed this when drawing Maps with really small blocks for example. Lets demonstrate. In the example bellow were going to render a tiled image to the screen and monitor the FPS. Obviously newer GPU's with faster blitters will cope better with this, than older ones, but this doesn't mean the issue is gone, just that it'd impacting those users little less. In this example we'll draw a tiled image with 8*8 tiles, 16*16, 32*32, 64*64, 128*128, 256*256 blocks etc etc then monitor the average FPS at these sizes.
Results: (Duron 800 mhz & GeForce 2 MX200) ( 1999/2000 computer)
Results: (AMD 64FX 3000 mhz & GeForce 6600GT) (2005/2006 computer)
OK, Notice how that even though the program is drawing the same amount of pixels, the smaller the block sizes, the more the program chokes up ? The best solution for overcoming such problems, might be to use larger tiles, or be more selective how and when you refresh the entire screen. A crash course in Image & Sprites Draw Modes One the most misunderstood aspects of PlayBASIC (among other things), is how to design your program to take advantage of Image & Sprite draw modes (SpriteDrawMode. Draw mode gives us control over how the sprites are rendered. This is nice and all, but certain render modes can place additional stress on your program if you're not careful. Making the program slower than need be. This most commonly occurs when using the Alpha Blended modes. In fact, it'll effect any draw mode that has to read from the destination surface. Getting the best out it, comes down to how well you understand the different types of image buffers PlayBASIC offers us. Today (PlayBasicV1.64 and bellow) we have 2 primary image types. ( note: future editions of PlayBASIC have more). Image Type #1 - Video Image Video Images are images where the pixel data is stored in your computers graphics card memory. These images can be copied / drawn to the screen (which is also in your video cards memory) very quickly, since they utilize you've video cards blitter. Which is the part of the GPU specially designed for this purpose. While it's fast to transfer Images around in video memory with the blitter (on most cards), the blitter has it's fair share of limitations. Those being, that it can basically only Copy & fill rectangles of pixels, and that's about it. So it's not very interesting. So all other rendering is in the hands of the CPU. While the CPU can write data to images in video memory very quickly, it can't read from them. Effectively reading from video memory is 20->30 times slower than writing! Even worse on some systems. Most people no doubt assume this is a PlayBASIC thing, it's not, it's limitation of how the PC was designed. It doesn't matter what language you use, we just can't write or read from video memory at the same rate. Basically, Video images are our best option if we want to draw loads of solid (no alpha), static sprites (not rotated) around the screen. Image Type #2 - FX Image FX images are a variation of normal images. The primary difference however, is that the Pixel data is stored in your computers main memory. While there's no visible difference between the two, they do give us a different set of abilities. Being stored in system memory grants us the freedom of faster access to the pixels in the image. So we can we can read & write pixels as fast as our CPU can manage. But there's a price, FX images are slower to draw to the display screen. Since the drawing has to be performing using the CPU. We can't use the video cards blitter, since it was designed to work with image data stored in video memory, not system memory. Now since FX's images are stored in system memory, this gives the added ability to draw them rotated / scaled in real time. This is possible as the software rotation code can read the pixels from the image as fast as CPU / Memory will allow. We couldn't do this as fast, if the image was stored in video memory. You can try that yourself. LoadImage "myIMageNameHere",1, then try and draw it rotated.... it'll be slow for the aforementioned reason. Then try it with an FX image. Common Mistakes OK, so we've established that if we want to rotate an image or read from an image a lot, then FX images are probably our bet at this time. But what about rendering translucence styled image effects such as Alpha Blending, Alpha Addition, Alpha Subtract, Logical operations etc... ? This is where the most common mistakes are made! I.e. A new user loads up an FX image and tries to alpha blend it to the screen. First thing they notice that's nice and slow! To explain why, we'll examine the basic process that's being performed when we blend images together.. For each pixel we're doing the following.
Now that seems simple enough, so what are we missing ? Notice how that in order to blend a pixel, we have to the read pixels from the destination image ? Hmmm, well what if that destination is in Video memory ? Wouldn't all that reading video memory slow our drawing routine down ? Yep, it certainly will ! While you can get away with blending small images directly to the screen or video images, the better approach, if you want a heavy amounts of blending, would be to draw your screen to a screen sized FX image and do all your blending stuff on that. Then once you're done, transfer (draw) that FX image to the screen. This will avoid video memory reading completely. The upside of this approach is that we get much faster Alpha blending. However, we do loose the assistance of the video cards blitter while doing this. So commands like CLS/BOX & drawing solid images/map will be slower. How slow, depends on how fast your cpu/memory is, and don't forget we have to transfer the whole image to the screen. But even with this added Burdon, it's still way faster than attempting to render alpha blend effects directly to video memory. Example This example shows the process of rendering sprites to an FX image, then drawing the image the screen so we can see it. Those with a keen eye will notice it's a variation of the Alpha sprite example that comes in the PlayBASIC example pack. The main difference is the scrolling backdrop isn't present in this version. Anyway, I've provided the example so you can get an idea of how much FX image blitting your system can push around. I recommend experimenting with the sprite particle size, screen depth. Every system will have a balancing point.
Keys Enter = Toggle between Render to an FX image or rendering Directly to Video memory. Space = See basic filler stats on your machine. Results All test were conducted in 640*480 (full screen exclusive) display mode in both 16 & 32bit modes. The test tries to calc how many sprites of size X, can be on screen while holding steady 30fps on the machine. All sprites are rotating, but have randomly assigned an alpha draw modes. 800mhz Duron & GF2 mx200 * 32bit
* 16 bit
3gig AMD 64 & GF6600 * 32bit
* 16 bit
Copying/Drawing Between Images Types A good rule of thumb when working out how to arrange your image data in memory, is to always keep them in the same format all the way through the drawing process. This is not always possible, as how you render will also determinate what format the image needs to be held in. While the performance will vary system to system, here's some general tips on the subject. Also See: PrepareFXImage , PrepareAFXImage Image Type Key VI = Video Image FX = FX image Performance Guide when Drawing fixed sized solid/transparent images
Performance Guide when Drawing Rotated/scaled images
Performance Guide when Drawing Blended (Alpha/Add/Sub/50 etc etc ) images
* = AVOID IF POSSIBLE *** = AVOID AT ALL COSTS Locking / Unlocking Buffers - Where and When ? Unlike the good old days of the 8/16 and early 32bit machines, when we write programs on the PC, were not alone! As while our program is running, there are other background operating system tasks & program running also. This means our PlayBASIC programs have to share system resources with other tasks. So windows act's as a mediator of sorts. Allocating time for our program to use certain device and then doing the same of other programs. Primarily when we think about shared resources in the computer, we're thinking of Image Data, Hardware devices (mouse/keyboard/joystick/ Image Blitter/ 3D hardware /Sound etc etc ) and memory. While the majority of those are transparent to the PlayBASIC programmer (as we're hidden the tedious tasks away), it is very advantageous to have some understanding of how windows manages the key ingredients in your games, AKA your images. Hopefully, you've aware that we have two or more primary types of images that our PB programs can create. Images stored in Video memory & FX images (image stored in system memory) We won't concern our selves with when/why we should use either, or both, we just want to get some idea of how windows handles resources for us. Lets start from the start. We when create a program we're also creating the screen graphics device. To the user the screen is the output display of what our program has been drawing. Behind the scenes, it actually handles various other devices for us also, but basically to the user it's nothing more than a big image. The screens image will be stored in your computer Video Memory. Which allows your computers graphics card to display it on your monitor, or even move it around in the video memory easily if need be. Yes, because video memory is shared commodity, Windows can move stuff around when it's not being used ! So how does this affect our programs ? Well, because nothing is nailed down inside windows, PlayBASIC has to constantly ask Windows prior to accessing any of it's resources. Lets look at example. Often one of the first programs people write is something simple to render a bunch vector graphics elements to the screen. In our example here we'll draw 5000 dots.
This program creates a 800*600 display, draws 5000 dots to the screen and shows just how fast your computer can update the screen, or does it ? I guess this comes from the assumption that we all think our computers are ultra fast today. However, if you tried that demo you'll no doubt get a shock. It'll crawl! Why ? Now remember when we said that screen/images are shared resources? Well, the problem in this case, is that every time we draw a single dot to the screen, PlayBASIC has to beg windows for access to the screen. This begging process in windows speak, is called Surface Locking. So in order to draw a single dot, PB has do the following.
In the case of rendering a DOT, steps 2 & 3 are trivial, the bottle neck occurs in step 1. When PB requests a lock on the surface, we're at the total mercy of windows to react quickly to this request each time. Some times the locking is completed virtually instantly, but it generally seems to take anywhere from 1/2 millisecond to a few milliseconds or even more. This might not sound like much time, but when we consider there's only 1000 milliseconds in one full second, and were going to be wasting the majority of that just locking and unlocking this surface constantly. Clearly there must be better way. User Controlled Buffer Locking / Unlocking The solution to such problems lies firmly in the users hands. If you know your going to be drawing a batch of GFX items (as per our previous example) then rather than force PB to constantly request a lock on the current surface each time it draws an item, you could do the locking and unlocking yourself. To do this, we use the LockBuffer & UnlockBuffer commands. Lockbuffer will lock the current surface were drawing to. The surface will remain locked until you call it's partner command UnlockBuffer. It should be noted, that it's not advisable to leave a surface locked indefinitely! Windows tends can get very upset and close your program. So prior to your drawing batch lock the surface, do your drawing, then unlock it again. Lets put this new found knowledge to test and expand the previous example to include them. This time we'll include Lock Buffer prior to draw our batch of dots, and the unlock once were done.
Are there times when I shouldn't use lockbuffer ? Yes, unfortunately when we lock a surface there's few by-products of this. While locking ensures that windows won't try and move the surface while we're drawing to it, it also restricts other devices from doing anything to the surface until we've released our control over it. This will mainly affect any rendering functionality that requires the graphics card GPU / blitter. Some general guides when you've locked a surface, will be to avoid attempting to the draw the following to that locked surface.
While nothing bad should happen, what's likely to the occur is that item your drawing will unlock the before it starts to draw, or it'll simply refuse to draw anything since the surface was locked. |
Related Info: | CameraBasics | DrawRotatedImage | LoadFXImage | LockBuffer | Maps | SavePBI | SpriteDRawMode | Sprites : |
|
|||||||||||||||||||||||||||||||||||||||
(c) Copyright 2002 - 2024 - Kevin Picone - PlayBASIC.com |