?? gamedev_net - the simple directmedia layer from a win32 perspective, part 2 sdl video.htm
字號:
if ( SDL_PollEvent ( &event ) )
{
//an event was found
if ( event.type == SDL_QUIT ) break ;
}
}//end of message pump
//done
return ( 0 ) ;
}
</PRE></BLOCKQUOTE>
<P>This is our basic shell application for SDL, and during this TGO, we will
build on it. As you can see, I already have initialized SDL's video subsystem
with my call to SDL_Init, and I have already set the video mode (it is currently
set up for windowed mode and uses the current display format).</P>
<P>This application doesn't do a whole lot, other than give you a blank, black
window with a border around it. You can't resize the window, but you can move it
around. This program simply waits for you to quit. However, I will point out
that the equivalent application in GDI or DirectDraw would be about five times
as many lines as we have here.</P>
<P>Now we've got to make the application actually DO something. Let's get to
work.</P>
<H2>Rectangular Fills</H2>
<P>Often, you will need a large rectangular area of a surface cleared out to a
particular color. The rectangular area can be as small as a single pixel, or as
large as the entire screen. The function for doing this is SDL_FillRect.</P>
<BLOCKQUOTE><PRE class=code>int SDL_FillRect(SDL_Surface *dst, SDL_Rect *dstrect, Uint32 color);
</PRE></BLOCKQUOTE>
<P>This function takes three parameters: a pointer to the destination surface, a
pointer to an SDL_Rect that describes the rectangle you wish to fill, and a
color (in the native pixel format of the surface). If you want to clear out the
entire surface, you can use NULL for the dstrect parameter.</P>
<P>The surface we've got, and an SDL_Rect is easy enough to fill out. One thing
we need before we can use this function is the ability to convert from a device
independent color (i.e. an SDL_Color variable) into the native pixel format (and
I don't want to use the code we looked at for this purpose earlier).</P>
<P>Luckily, SDL has such a function. It is called SDL_MapRGB.</P>
<BLOCKQUOTE><PRE class=code>Uint32 SDL_MapRGB(SDL_PixelFormat *fmt, Uint8 r, Uint8 g, Uint8 b);
</PRE></BLOCKQUOTE>
<P>This function takes a pointer to an SDL_PixelFormat structure, and a red,
green, and blue component from 0-255, and it figures out what value works for a
native pixel of that same color (or the nearest equivalent).</P>
<P>So, let us say we want to fill the screen with pure red. This is how we'd go
about it:</P>
<BLOCKQUOTE><PRE class=code>SDL_FillRect ( pSurface , NULL , SDL_MapRGB ( pSurface->format , 255 , 0 , 0 ) ) ;
</PRE></BLOCKQUOTE>
<P>But we're still not done. SDL's video system doesn't automatically display
any of the changes to the surface. In order to make the new data visible, we
have to update the rectangle. The function for doing this is SDL_UpdateRect.</P>
<BLOCKQUOTE><PRE class=code>void SDL_UpdateRect(SDL_Surface *screen, Sint32 x, Sint32 y, Sint32 w, Sint32 h);
</PRE></BLOCKQUOTE>
<P>This function takes four parameters: the pointer to the surface, and an x ,
y, w, and h values describing the rectangle to update. If you wish to update the
entire surface, you can place 0 into each of x, y, w, and h, like so:</P>
<BLOCKQUOTE><PRE class=code>SDL_UpdateRect ( pSurface , 0 , 0 , 0 , 0 ) ;
</PRE></BLOCKQUOTE>
<P>So, let's do a quick example. Start a new project named SDLRandomFillRect (or
whatever you want to call it), set it up for SDL, and put the following code
into main.cpp:</P>
<BLOCKQUOTE><PRE class=code>#include "SDL.h"
#include <stdlib.h>
enum {
SCREENWIDTH = 512,
SCREENHEIGHT = 384,
SCREENBPP = 0,
SCREENFLAGS = SDL_ANYFORMAT
} ;
int main( int argc, char* argv[] )
{
//initialize systems
SDL_Init ( SDL_INIT_VIDEO ) ;
//set our at exit function
atexit ( SDL_Quit ) ;
//create a window
SDL_Surface* pSurface = SDL_SetVideoMode ( SCREENWIDTH , SCREENHEIGHT ,
SCREENBPP , SCREENFLAGS ) ;
//declare event variable
SDL_Event event ;
//message pump
for ( ; ; )
{
//look for an event
if ( SDL_PollEvent ( &event ) )
{
//an event was found
if ( event.type == SDL_QUIT ) break ;
}
<B> //set up random rectangle
SDL_Rect rect ;
rect.x = rand ( ) % ( SCREENWIDTH ) ;
rect.y = rand ( ) % ( SCREENHEIGHT ) ;
rect.w = rand ( ) % ( SCREENWIDTH - rect.x ) ;
rect.h = rand ( ) % ( SCREENHEIGHT - rect.y ) ;
//fill the rectangle
SDL_FillRect ( pSurface , &rect ,
SDL_MapRGB ( pSurface->format , rand ( ) % 256 ,
rand ( ) % 256 , rand ( ) % 256 ) ) ;
//update the screen
SDL_UpdateRect ( pSurface , 0 , 0 , 0 , 0 ) ;
</B> }//end of message pump
//done
return ( 0 ) ;
}
</PRE></BLOCKQUOTE>
<P>The lines in bold are new to this example (as opposed to the base shell
code), if you just want to add those. Most of the lines are fairly obvious. Five
of them deal with setting up the SDL_Rect variable, another does the filled
rectangle, and yet another updates the display. On my display, this example runs
really fast, but I've got a good video card and that's to be expected. Its
output looks something like Figure 1.</P>
<P class=maintext-2 align=center><IMG height=409
src="GameDev_net - The Simple DirectMedia Layer from a WIN32 Perspective, Part 2 SDL Video_files/02-01.jpg"
width=518> <BR><B>Figure 1: Random FillRect Example Program</B></P>
<P>Filling rectangular areas in SDL is a lot simpler than the equivalent GDI or
DirectDraw code (no HBRUSH to make, no DDBLTFX structure to fill out). Just give
it a rectangle, a format, and color, and you've drawn yourself a rectangle.</P>
<H2>Pixel Plotting</H2>
<P>In theory, you could draw pixels using SDL_FillRect, and just use a w and h
of 1. That, of course, is not how the SDL_FillRect function is intended to be
used, but the SDL Police won't come and stop you, and it *IS* a completely cross
platform method of drawing pixels, whereas doing pixel plotting with direct
memory access can have portability issues, as we shall see in a moment.</P>
<P>In DirectDraw, in order to start writing to the memory of a surface itself,
you need to Lock the surface and Unlock it when you are done. The same is true
for SDL, in general. Some surfaces never need to be locked, typically those that
dwell in system memory.</P>
<P>How do you tell? SDL provides a macro for you called SDL_MUSTLOCK. To test a
surface, you do the following:</P>
<BLOCKQUOTE><PRE class=code>if ( SDL_MUSTLOCK ( pSurface ) )
{
//surface must be locked for access
}
else
{
//no need to lock surface
}
</PRE></BLOCKQUOTE>
<P>If you don't need to lock a surface, you will typically do nothing special,
and so the entire else clause above won't be there.</P>
<P>In order to lock a surface, you call SDL_LockSurface. In order to unlock a
surface, you call SDL_UnlockSurface. I doubt either function name comes as a big
surprise to you. The same sort of caveats exist for SDL locks as for DirectDraw
locks. Don't lock a surface longer than you have to, and don't call any system
functions while a surface is locked. Treat the locked surface time as a critical
section of your code's execution. Here are the prototypes for SDL_LockSurface
and SDL_UnlockSurface.</P>
<BLOCKQUOTE><PRE class=code>int SDL_LockSurface(SDL_Surface *surface);
void SDL_UnlockSurface(SDL_Surface *surface);
</PRE></BLOCKQUOTE>
<P>Each of these have only one parameter... the pointer to the surface you
intend to lock. In the case of SDL_LockSurface, there is a return value. If all
goes well, this return value will be 0. If there is some sort of problem, you
will get -1 instead.</P>
<P>Something important to point out here. You can lock a surface more than once
prior to unlocking it. If you do this, you need to have as many unlock calls as
you have lock calls, like so:</P>
<BLOCKQUOTE><PRE class=code>SDL_LockSurface ( pSurface ) ; //first lock
SDL_LockSurface ( pSurface ) ; //second lock
SDL_UnlockSurface ( pSurface ) ; //first unlock... surface is still locked
SDL_UnlockSurface ( pSurface ) ; //second unlock... surface is now unlocked
</PRE></BLOCKQUOTE>
<P>This is just something to be aware of. Generally speaking, if you have a
large operation that requires some sort of direct access to the pixels of a
surface, do a lock, do the operation, and do the unlock. If you have an even
larger operation that relies on other operations that lock and unlock the
surface, that's ok, because locking and unlocking is recursive.</P>
<P>Once the surface is locked, you can begin to directly access the pixel data
through the SDL_Surface's pixels member. This includes working with the pixel
format of the surface as well as the pitch member of the surface.</P>
<P>When setting pixels, I personally like to work with a pixel format
independent representation of the color, i.e the SDL_Color structure, or
something very much like it. I like having red, green, and blue all represented
by numbers between 0 and 255. Unfortunately, an SDL_Surface can have a variety
of different formats, and so we'll need to convert from an SDL_Color structure
into the native format using SDL_MapRGB. No big deal.</P>
<P>From there, next need to know where exactly to write to. The pixels member of
SDL_Surface is a void*. I personally like to convert it to a char*, and work the
on the surface as though it were like a character buffer.</P>
<P>The offset from the beginning of the pixels pointer depends on the x and y
coordinates of the pixel you wish to set. For every value in y, you increase by
the contents of the SDL_Surface's pitch member. For every value in x, you
increase by the contents of the SDL_Surface's pixel format's BytesPerPixel
member. Calculating the position looks something like the following.</P>
<BLOCKQUOTE><PRE class=code>//point to beginning of buffer
char* pPosition = ( char * ) pSurface->pixels ;
//increment y
pPosition += ( y * pSurface->pitch ) ;
//increment x
pPosition += ( x * pSurface->format->BytesPerPixel ) ;
//pPosition now points to the proper pixel
</PRE></BLOCKQUOTE>
<P>We've got the proper position, and we've got the proper color. The only thing
left is to copy the data from the color over into the buffer. This is where we
meet a very small portability issue.</P>
<P>On WIN32 ( and many other operating systems), we can simply do the following
code to copy the pixel data from the color variable into the buffer:</P>
<BLOCKQUOTE><PRE class=code>memcpy ( pPosition , &color , pSurface->format->BytesPerPixel ) ;
</PRE></BLOCKQUOTE>
<P>And this will work, no problem, on most of the machines that the program is
likely ever to be compiled for. But then there are the oddballs. The code above
relies on the fact that the target machine is little endian. On a big endian
system, unless using a 32 bpp surface, you are going to get garbled colors, or
black. The solution? Well, I have one in mind, but I haven't had a chance to
test it out yet ( since I don't have a machine that supports SDL but has a big
endian). The basic idea is that you want to reverse the order of the bytes in
the color variable if the machine is big endian, and then do the writing. There
are a few macros and functions in SDL that can assist with this.</P>
<P>So, here's a basic putpixel function. This function assumes that the surface
has already been locked, or does not need locking.</P>
<BLOCKQUOTE><PRE class=code>void SetPixel ( SDL_Surface* pSurface , int x , int y , SDL_Color color )
{
//convert color
Uint32 col = SDL_MapRGB ( pSurface->format , color.r , color.g , color.b ) ;
//determine position
char* pPosition = ( char* ) pSurface->pixels ;
//offset by y
pPosition += ( pSurface->pitch * y ) ;
//offset by x
pPosition += ( pSurface->format->BytesPerPixel * x ) ;
//copy pixel data
memcpy ( pPosition , &col , pSurface->format->BytesPerPixel ) ;
}
</PRE></BLOCKQUOTE>
<P>To retrieve a pixel, we can recycle much of our pixel setting code, because
the task is simply reversed. We determine the position in the exact same way, we
memcpy it into a Uint32 variable (with the same minor caveat about big endians),
and then convert it to an SDL_Color variable. The function for doing that is
SDL_GetRGB.</P>
<BLOCKQUOTE><PRE class=code>void SDL_GetRGB(Uint32 pixel, SDL_PixelFormat *fmt, Uint8 *r, Uint8 *g, Uint8 *b);
</PRE></BLOCKQUOTE>
<P>This function is much the same as SDL_MapRGB, except that this time, we send
pointers to Uint8s into which are placed the various color components
corresponding to the native pixel color. So, if we have a Uint32 variable called
col, and an SDL_Color variable called color, this is how we would convert
it.</P>
<BLOCKQUOTE><PRE class=code>SDL_GetRGB ( col , pSurface->format , &color.r , &color.g , &color.b ) ;
</PRE></BLOCKQUOTE>
<P>And with that, we can make a GetPixel function.</P>
<BLOCKQUOTE><PRE class=code>SDL_Color GetPixel ( SDL_Surface* pSurface , int x , int y )
{
SDL_Color color ;
Uint32 col = 0 ;
//determine position
char* pPosition = ( char* ) pSurface->pixels ;
//offset by y
pPosition += ( pSurface->pitch * y ) ;
//offset by x
pPosition += ( pSurface->format->BytesPerPixel * x ) ;
//copy pixel data
memcpy ( &col , pPosition , pSurface->format->BytesPerPixel ) ;
//convert color
SDL_GetRGB ( col , pSurface->format , &color.r , &color.g , &color.b ) ;
return ( color ) ;
}
</PRE></BLOCKQUOTE>
<P>And now time for another example. Create a project, and call it something
like SDLRandomPixels. Place the following code into main.cpp.</P>
<BLOCKQUOTE><PRE class=code>#include "SDL.h"
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -