LinuxDevCenter.com
oreilly.comSafari Books Online.Conferences.

advertisement


Animation in SDL: Hardware Surfaces
Pages: 1, 2

Other Hardware Surfaces

After you have set the video mode, you can use SDL_CreateRGBSurface() to create more hardware surfaces and SDL_FreeSurface() to release them. These surfaces are used to hold image data, such as sprites or fonts, that you want to draw onto the screen. If your screen and your graphics are both in hardware surfaces, SDL can use the graphics hardware to copy from one surface to another. Using the graphics hardware gives you a significant performance boost.



This may sound obvious, but if the video card has 32 megabytes of memory you aren't going to store more than 32 megabytes of data in it. You won't get the full 32 megabytes because the windowing system and other applications may also be storing information in graphics memory. When you use hardware surfaces, you have to set a budget for graphics memory use and then stick to that budget.

Hardware Locking

Graphics hardware is a shared resource. Operating systems generally require that we lock shared resources before we use them and unlock them after we are done. SDL provides SDL_LockSurface() and SDL_UnlockSurface() to lock and unlock hardware surfaces. It is possible to have a hardware surface that should not be locked and SDL provides the SDL_MUSTLOCK() macro so that we can tell them apart.

Failing to lock a hardware surface can cause unexpected results or even program crashes. Locking the surface ensures that all graphics hardware pending operations are completed before you can touch the buffer. In hardlines.cpp the call to SDL_FillRect() may be performed by the graphics hardware and run in parallel with your code. In fact, there could be several graphics operations that are queued up waiting for the graphics accelerator to perform them. If we don't wait for those operations to complete, the program can be drawing lines in software while the background is being filled by the graphics accelerator. No matter what happens, the results are unpredictable and certainly not what you want. Further, the pointer stored in the surface record can change. If you are using double buffering and you swap the buffers, the current buffer is a different block of video memory. The pointer can also change if the window was moved. The pointer is only guaranteed to be valid while the surface is locked.

After learning why you have to lock hardware surfaces, you might think that you should just lock them at the beginning of the program and leave them locked. We can't do that because while the hardware is locked, we cannot safely make any system calls. System calls may not be able to complete until the hardware is unlocked.

To make the sample program work with hardware surfaces I have added code around the code that updates the screen that locks and unlocks the hardware screen surface.

if (SDL_MUSTLOCK(screen))
{
  if (-1 == SDL_LockSurface(screen))
  {
    printf("Can't lock hardware surface\n");
    exit(1);
  }
}

rl->update(t);
gl->update(t);
bl->update(t);

if (SDL_MUSTLOCK(screen))
{
  SDL_UnlockSurface(screen);
}

To be as portable and fast as possible, I only lock the surface if SDL_MUSTLOCK() says it must be locked. There is a real cost to locking the surface, so we don't want to lock it if we don't have to. Using SDL_MUSTLOCK() also lets the code work with software buffers.

Screen Flipping

At the very end of the original animation loop, we had two lines of code:

SDL_Flip(screen);
SDL_Delay(10);

When using a double buffered display, graphics are drawn into the back buffer and only become visible after the call to SDL_Flip(). When used with software surfaces SDL_Flip() copies the contents of the back buffer to the display and returns immediately. The story is more complicated with hardware surfaces.

The version of SDL_Flip() used for hardware surfaces can be implemented in at least two different ways. It can copy the back buffer to the front buffer, or it can tell the hardware to stop displaying the current surface and start displaying what is in the back buffer. In the second case it just changes the value of a pointer that tells the hardware where the graphics are. At that point the display surface (also called the front buffer) becomes the back buffer and the back buffer becomes the display buffer. No copying is done at all.

Copying and swapping both get the next frame on the screen. You only care about the difference if you are doing incremental updates of the frames. If SDL_Flip() is copying buffers, the back buffer always has a copy of the last frame that was drawn. If SDL_Flip() is doing page swapping, the back buffer usually contains the next-to-last frame. I say usually because double buffering can be implemented using a hidden third buffer to reduce the time spent waiting for the buffer swap to happen. You can find out what kind of swapping is being done by watching the value of the back buffer pointer (screen->pixels in hardware.cpp) to see if it changes and how many different values it has. If it never changes, then SDL_Flip() is copying the pixels. If it toggles back and forth between two values, then page swapping is being used.

Using hardware surfaces changes the timing behavior of SDL_Flip() and lets us get rid of tearing. Image tearing results from changing the display buffer while the video hardware is drawing what you see on the screen. The video hardware is constantly reading the contents of video memory, your animation frame, and converting it to a video signal that your monitor then turns into a pattern of colored light that you see. The process of painting an image on the screen takes time. At 85 frames per second, it takes just just under 12 milliseconds to draw the frame on your screen. The process is broken up into several phases, but the ones we are interested in are the frame time and the video retrace period. The frame time is the length of time from when the hardware starts displaying the current image on the screen until it starts display the next image on the screen. The video retrace period is a brief period at the end of the frame time when the video system has finished displaying one image but hasn't started displaying the next image.

If we change the content of the display buffer during the frame time, the hardware will display part of the front buffer at the top of the screen and part of the back buffer at the bottom of the screen. Splitting the image like that is called tearing. We want the buffers to switch during the vertical retrace period so we never see parts of two frames on the screen at the same time.

We want our animation programs to

  • Draw a new animation frame
  • Wait until the vertical retrace period
  • Swap the frames
  • Repeat

Unfortunately, that wait can be very long. There is a lot of work that we could be doing instead of waiting for the buffer swap. What we really want to do is

  • Draw a new animation frame
  • Tell the hardware to swap the buffers at the next video retrace period
  • Do other work such as processing user input and network traffic so that we are ready to draw the next frame
  • When there is nothing else left to do, wait for the buffers to swap
  • Repeat

This is precisely what SDL tries to do. The call to SDL_Flip() tells the hardware to swap buffers at the next video retrace, but it does not wait for the retrace. When you try to lock the surface, or when one of the SDL graphics routines tries to, SDL waits until the buffers have swapped. Delaying the wait lets you keep working after calling SDL_Flip() but prevents tearing and prevents you from writing to a buffer that is being displayed. This design lets your program do all the set up work needed for drawing the next frame while waiting for the buffers to swap.

There is, of course, a caveat. On some systems it is not possible to implement SDL_Flip() to work the way I just described. On those systems, SDL_Flip() may wait until the buffers have swapped or it may never wait and give you tearing. I have never encountered these problem, but you need to test SDL_Flip() on your target system before depending on a specific behavior.

SDL_Delay() is rarely needed when using hardware surfaces. The wait for the hardware buffer swap keeps the program from generating frames faster than they can be drawn on the screen and forces the program to give up time to the operating system. Thus the next to last change to hardlines.cpp was to remove that line. Removing the call to SDL_Delay() is not always correct. It would have been more correct to time the animation loop and call SDL_Delay() if we were drawing an unreasonable number of frames per second.

The Last Change

I added code to compute the average frame rate of the animation and print it out at the end. I just count the number of frames that were drawn and divide by the time it took to draw them. If the program is working correctly the frame rate should be very close to the frames per second setting on your display.

Conclusion

This article covered details of using SDL hardware surfaces along with the problems and incompatibilities that interfere with there use. As there are no standards for hardware, device drivers, and operating systems that cross the range of platforms that are supported by SDL, there are bound to be incompatibilities and inconsistencies. This another case where SDL isn't amazing because it works so well, SDL is amazing because it works at all.

Next time I'll be looking at how to use OpenGL from within SDL. The combination of a portable 3D API like OpenGL with the portable input and multimedia capabilities of SDL make it possible to write high performance commercial games that run on Linux, Windows, and the Mac.

Bob Pendleton has been fascinated by computer games ever since his first paid programming job -- porting games from an HP 2100 minicomputer to a UNIVAC 1108 mainframe.


Return to the Linux DevCenter.


Linux Online Certification

Linux/Unix System Administration Certificate Series
Linux/Unix System Administration Certificate Series — This course series targets both beginning and intermediate Linux/Unix users who want to acquire advanced system administration skills, and to back those skills up with a Certificate from the University of Illinois Office of Continuing Education.

Enroll today!


Linux Resources
  • Linux Online
  • The Linux FAQ
  • linux.java.net
  • Linux Kernel Archives
  • Kernel Traffic
  • DistroWatch.com


  • Sponsored by: