1、DirectX错误处理
通过上一章知道DirectDrawCreate函数可以创建一个DirectDraw COM对象,并得到一个接口指针:
LPDIRECTDRAW lpdd = NULL;
DirectDrawCreate(NULL, &lpdd, NULL);
可以用宏FAILED检测函数是否执行失败,宏SUCCEEDED()来检测函数是否执行成功,eg:
LPDIRECTDRAW lpdd = NULL;
if(FAILED(DirectDrawCreate(NULL, &lpdd, NULL)))
{
//failed
...
return;
}
//succeeded
...
LPDIRECTDRAW lpdd = NULL;
if(SUCCEEDED(DirectDrawCreate(NULL, &lpdd, NULL)))
{
//succeeded
...
}
else
{
//failed
...
}
也可以通过
DirectDrawCreate()的返回值来判断是否出错。
2、DirectDraw接口
DirectDraw由五个接口组成:IUnknown、IDirectDraw、IDirectDrawSurface、IDirectDrawPalette、IDirectDrawClipper。
IUnknown:所有com接口都必须从它继承。
IDirectDraw:开始使用DirectDraw时必须创建的主接口对象。IDirectDraw一对一的表示视频卡及其支持硬件。
IDirectDrawSurface:用DirectDraw创建、控制和显示的实际显示表面。显示表面可以使用显存存与视频卡或系统内存中,分为主显示表面和从显示表面。主显示表面通常表示正在被视频卡光栅化和显示的实际视频缓冲区,而从显示表面通常是离屏的。通常需要创建单个的主显示表面以表示实际视频显示,创建一个或多个从显示表面以表示对象位图、后备缓冲以表示将构造下一帧动画的离屏绘图区。
IDirectDrawPalette:创建、加载和控制调色板,以及将调色板关联到显示表面,例如主显示表面和从显示表面。
IDirectDrawClipper:剪切光栅和位图操作到一些可见显示表面的子集。
3、设置协作级别
对DDraw来说,我们唯一关心的是视频显示设备,有两种情况:全屏模式和窗口模式。用IDirectDraw7::SetCooperativeLevel来设置DDraw的协作级别。声明如下:
HRESULT SetCooperativeLevel(HWND hWnd, // 窗口句柄,一般使用主窗口句柄 DWORD dwFlags); // 控制标志
dwFlags控制标志,它直接影响DDraw与Windows之间协作方式。
SetCooperativeLevel()的控制标记 | |
值 | 描述 |
DDSCL_ALLOWMODEX | 允许使用Mode X (320x200,240,400) 显示模式.仅当设置DDSCL_EXCLUSIVE和DDSCL_FULLSCREEN时生效 |
DDSCL_ALLOWREBOOT | 允许在排他(全屏)模式下检测到Ctrl+Alt+Del |
DDSCL_EXCLUSIVE | 请求排他级别。此标记需要与DDSCL_FULLSCREEN标记同时作用 |
DDSCL_FPUSETUP | 表示调用 程序希望配置FPU以得到最佳的Direct3D性能(禁用度和异常),这样Direct3D不需要每次都明确地FPU。更多信息,请在DirectX SDK中查询“DDraw协作级别和FPU数度” |
DDSCL_FULLSCREEN | 表示使用全屏模式。其他应用程序的GDI将不能写屏,此标记必须与DDSCL_EXCLUSIVE标记同时使用。 |
DDSCL_MULTITHREADED | 请求对于多线程安全的DDraw行为。 |
DDSCL_NORMAL | 表示应用程序是一个通常的Windows应用程序。这个标记不能与DDSCL_ALLOWMODEX, DDSCL_EXCLUSIVE, 或 DDSCL_FULLSCREEN标记一起使用。 |
DDSCL_NOWINDOWCHANGES | 表示不允许DDraw激活时最小化或还原应用程序窗口。 |
上面写了一大堆标记,其实大多数情况是下面两种情况。
全屏模式:DDSCL_FPUSETUP | DDSCL_ALLOWMODEX | DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT
窗口模式:DDSCL_NORMAL
4 、设置显示(视频)模式
IDirectDraw7::SetDisplayMode()用来设置显示(视频)模式,函数原型:
HRESULT SetDisplayMode( DWORD dwWidth, //宽 DWORD dwHeight, //高 DWORD dwBPP, //色深,即每像素的位数 DWORD dwRefreshRate, //刷新频率,0为使用默认值 DWORD dwFlags//一般设为0 );
例如,创建一个256色(8位)的640×480模式:
lpdd->SetDisplayMode(640, 480, 8, 0, 0);
HWND main_window_handle = NULL; // 主窗口句柄
LPDIRECTDRAW7 lpdd = NULL; // DDraw 对象
int Game_Init(void *parms = NULL, int num_parms = 0)
{
// 创建ddraw对象
if (FAILED(DirectDrawCreateEx(NULL, (void **)&lpdd, IID_IDirectDraw7, NULL)))
return(0);
// 设置协作级别
if (FAILED(lpdd->SetCooperativeLevel(main_window_handle,
DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX |
DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT)))
return(0);
//设置视频模式
if (FAILED(lpdd->SetDisplayMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP,0,0)))
return(0);
return 1;
}
/
int Game_Shutdown()
{
if (lpdd)
{
lpdd->Release();
lpdd = NULL;
}
return 1;
}
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd)
{
//创建窗口
......
CreateWindow();
Game_Init();
//消息循环
while(TRUE)
{
if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
Game_Shutdown();
return msg.wParam;
}
5、创建调色板
调色板项结构如下:
typedef struct tagPALETTEENTRY { BYTE peRed; //红色强度值 BYTE peGreen;//绿色强度值 BYTE peBlue; //蓝色强度值 BYTE peFlags;//一般设为PC_NOCOLLAPSE } PALETTEENTRY;
调色板数据结构是一个包含256个调色板项的数组:
PALETTENTRY palette[256];
以下代码创建一个随机调色板结构,并设置0位置为黑,255位置为白:
PALETTEENTRY save_palette[256]; // used to save palettes
// build up the palette data array
for (int color=1; color < 255; color++)
{
// fill with random RGB values
palette[color].peRed = rand()%256;
palette[color].peGreen = rand()%256;
palette[color].peBlue = rand()%256;
// set flags field to PC_NOCOLLAPSE
palette[color].peFlags = PC_NOCOLLAPSE;
} // end for color
// now fill in entry 0 and 255 with black and white
palette[0].peRed = 0;
palette[0].peGreen = 0;
palette[0].peBlue = 0;
palette[0].peFlags = PC_NOCOLLAPSE;
palette[255].peRed = 255;
palette[255].peGreen = 255;
palette[255].peBlue = 255;
palette[255].peFlags = PC_NOCOLLAPSE;
IDirectDraw7::CreatePalette()函数用来创建调色板对象,获得调色板接口,函数原型:
HRESULT CreatePalette(
DWORD dwFlags, //控制标记
LPPALETTEENTRY lpDDColorArray, //初始调色板,可以为NULL
LPDIRECTDRAWPALETTE FAR* lplpDDPalette, //获得的IDirectDrawPalette接口指针
IUnknown FAR* pUnkOuter//一般设为NULL
);
对于8位调色板,控制标记dsFlags一般设为DDPCAPS_8BIT | DDPCAPS_ALLOW256 | DDPCAPS_INITIALIZE。
以下为利用上面的随机调色板获得调色板对象:
LPDIRECTDRAWPALETTE lpddpal = NULL; // a pointer to the created dd palette
// create the palette object
if (FAILED(lpdd->CreatePalette(DDPCAPS_8BIT | DDPCAPS_ALLOW256 |
DDPCAPS_INITIALIZE,
palette,&lpddpal, NULL)))
{
// error
return(0);
} // end if
6、创建显示表面
IDirectDraw7::CreateSurface()用来创建显示表面,获得显示表面接口,函数原型:
HRESULT CreateSurface( LPDDSURFACEDESC2 lpDDSurfaceDesc2, //DDSURFACEDESC2数据结构,用来描述希望创建的显示表面 LPDIRECTDRAWSURFACE4 FAR* lplpDDSurface, //用来接收IDirectDrawSurface接口指针 IUnknown FAR* pUnkOuter //一般设为NULL );以下为创建一个主显示表面的代码:
LPDIRECTDRAWSURFACE7 lpddsprimary = NULL; // dd primary surface
DDSURFACEDESC2 ddsd; // a direct draw surface description struct
// clear ddsd and set size
memset(&ddsd,0,sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
// enable valid fields
ddsd.dwFlags = DDSD_CAPS;
// request primary surface
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;//显示表面是主显示表面
//ddsd.width和ddsd.height等不必设置,会默认使用SetDisplayMode()中的设置
// create the primary surface
if (FAILED(lpdd->CreateSurface(&ddsd, &lpddsprimary, NULL)))
{
// error
return(0);
} // end if
7、关联调色板到显示表面
IDirectDrawSurface7::SetPalette()用来关联调色板到显示表面,函数原型:
HRESULT SetPalette(LPDIRECTDRAWPALETTE* lpDDPalette);
参数lpDDPalette为DirectDraw7::CreatePalette()函数获得的调色板接口指针。
8、绘制像素
DirectDraw视频模式和显示表面都是线性的,当从一行移到另一行时,内存从左到右、从上到下增长。
要定位显示表面的任何位置,需要每排的内存间距(每行的字节数)和每个像素的大小(8位、16位、24位、32位),内存间距可以通过显示表面加锁lock()函数的第二个参数获得。可以使用下面的公式:
video_buffer_8[x+y*mempitch] = pixel_color_8;
video_buffer_16[x+y*(mempitch>>1)] = pixel_color_16;
其中video_buffer为指向显示表面内存的的指针(8位或16位模式),pixel_color为颜色值(8位或16位)。8位的颜色值可以直接用一个UCHAR来表示,而16位的颜色值通常编码成一个R5G6B5或R5G5B5(一位弃用),可以用下面的宏来表示一个16位的RGB颜色值:
#define _RGB16BIT565(r, g, b) ((b&31) + ((g&63<<5)) + ((r&31)<<11))
#define _RGB16BIT555(r, g, b) ((b&31) + ((g&31)<<5) + ((r&31)<<10))
要访问任何显示表面,必须对内存加锁和解锁,IDirectDrawSurface7::Lock()用来加锁,函数原型:
HRESULT Lock(LPRECT lpDestRect, //显示表面中要上锁的区域,NULL为整个表面上锁
LPDDSURFACEDESC2 lpDDSurfaceDesc,//DDSURFACEDESC2的地址,获得显示表面的信息
DWORD dwFlags,//控制标记
HANDLE hEvent);//高级特性,一般设为NULL
参数dwFlags的取值及意义:
参数DDSurfaceDesc结构的成员lPitch为每行的内存间距,成员lpSurface为指向显示表面的指针。
对显示表面解锁使用IDirectDrawSurface7::UnLock(LPRECT lpRect),如果是整个显示表面则传NULL。
9、清理资源
使用完DirectX对象后要Release()它们,而且应该按照创建时相反的顺序来Release(),eg:
<span style="font-size:14px;">// first the palette
if (lpddpal)
{
lpddpal->Release();
lpddpal = NULL;
} // end if
// now the primary surface
if (lpddsprimary)
{
lpddsprimary->Release();
lpddsprimary = NULL;
} // end if
// now blow away the IDirectDraw4 interface
if (lpdd)
{
lpdd->Release();
lpdd = NULL;
} // end if</span>
10、完整代码示例
综上所述,要在全屏的DirectDraw模式下绘制像素:必须建立DirectDraw对象以获得接口,设置协作级别,设置显示(视频)模式,创建调色板和显示表面并关联调色板到显示表面,然后在显示表面绘制像素,使用完后还要清理资源。完整示例代码如下:
#define WIN32_LEAN_AND_MEAN // just say no to MFC
#define INITGUID // make sure directX guids are included
#include <windows.h>
#include <ddraw.h>
#pragma comment(lib,"ddraw.lib")
// DEFINES
#define WINDOW_CLASS_NAME L"WINCLASS1"
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define SCREEN_BPP 8 // bits per pixel
#define MAX_COLORS 256 // maximum colors
// TYPES //
typedef unsigned short USHORT;
typedef unsigned short WORD;
typedef unsigned char UCHAR;
typedef unsigned char BYTE;
// MACROS /
#define KEYDOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEYUP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)
#define DD_INIT_STRUCT(ddstruct) { memset(&ddstruct,0,sizeof(ddstruct)); ddstruct.dwSize=sizeof(ddstruct); }
// GLOBALS
HWND main_window_handle = NULL; // globally track main window
HINSTANCE hinstance_app = NULL; // globally track hinstance
LPDIRECTDRAW7 lpdd = NULL; // dd object
LPDIRECTDRAWSURFACE7 lpddsprimary = NULL; // dd primary surface
LPDIRECTDRAWSURFACE7 lpddsback = NULL; // dd back surface
LPDIRECTDRAWPALETTE lpddpal = NULL; // a pointer to the created dd palette
LPDIRECTDRAWCLIPPER lpddclipper = NULL; // dd clipper
PALETTEENTRY palette[256]; // color palette
PALETTEENTRY save_palette[256]; // used to save palettes
DDSURFACEDESC2 ddsd; // a direct draw surface description struct
DDBLTFX ddbltfx; // used to fill
DDSCAPS2 ddscaps; // a direct draw surface capabilities struct
HRESULT ddrval; // result back from dd calls
DWORD start_clock_count = 0; // used for timing
int min_clip_x = 0, // clipping rectangle
max_clip_x = SCREEN_WIDTH-1,
min_clip_y = 0,
max_clip_y = SCREEN_HEIGHT-1;
int screen_width = SCREEN_WIDTH, // width of screen
screen_height = SCREEN_HEIGHT, // height of screen
screen_bpp = SCREEN_BPP; // bits per pixel
char buffer[80]; // general printing buffer
LRESULT CALLBACK WindowProc(HWND hwnd,
UINT msg,
WPARAM wparam,
LPARAM lparam)
{
PAINTSTRUCT ps; // used in WM_PAINT
HDC hdc; // handle to a device context
char buffer[80]; // used to print strings
switch(msg)
{
case WM_CREATE:
{
// do initialization stuff here
return(0);
}
break;
case WM_PAINT:
{
// simply validate the window
hdc = BeginPaint(hwnd,&ps);
// end painting
EndPaint(hwnd,&ps);
return(0);
}
break;
case WM_DESTROY:
{
// kill the application, this sends a WM_QUIT message
PostQuitMessage(0);
return(0);
}
break;
default:break;
}
// process any messages that we didn't take care of
return (DefWindowProc(hwnd, msg, wparam, lparam));
}
int Game_Init(void *parms = NULL, int num_parms = 0)
{
// this is called once after the initial window is created and
// before the main event loop is entered, do all your initialization here
//创建DirectDraw对象,获得IDirectDraw interface 7.0接口指针
if (FAILED(DirectDrawCreateEx(NULL, (void **)&lpdd, IID_IDirectDraw7, NULL)))
return(0);
//设置DirectDraw协作级别
if (FAILED(lpdd->SetCooperativeLevel(main_window_handle,
DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX |
DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT)))
{
return(0);
}
//设置显示模式
if (FAILED(lpdd->SetDisplayMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP,0,0)))
{
return(0);
}
//创建显示表面
memset(&ddsd,0,sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
if (FAILED(lpdd->CreateSurface(&ddsd, &lpddsprimary, NULL)))
{
return(0);
}
//创建一个随机调色板结构,设置0位置为黑,255位置为白
for (int color=1; color < 255; color++)
{
// fill with random RGB values
palette[color].peRed = rand()%256;
palette[color].peGreen = rand()%256;
palette[color].peBlue = rand()%256;
palette[color].peFlags = PC_NOCOLLAPSE;
}
// now fill in entry 0 and 255 with black and white
palette[0].peRed = 0;
palette[0].peGreen = 0;
palette[0].peBlue = 0;
palette[0].peFlags = PC_NOCOLLAPSE;
palette[255].peRed = 255;
palette[255].peGreen = 255;
palette[255].peBlue = 255;
palette[255].peFlags = PC_NOCOLLAPSE;
//创建调色板对象,获得调色板接口
LPDIRECTDRAWPALETTE lpddpal = NULL; // a pointer to the created dd palette
if (FAILED(lpdd->CreatePalette(DDPCAPS_8BIT | DDPCAPS_ALLOW256 |
DDPCAPS_INITIALIZE,
palette,&lpddpal, NULL)))
{
return(0);
}
//关联调色板到显示表面
if (FAILED(lpddsprimary->SetPalette(lpddpal)))
{
return(0);
}
return(1);
}
int Game_Main(void *parms = NULL, int num_parms = 0)
{
// for now test if user is hitting ESC and send WM_CLOSE
if (KEYDOWN(VK_ESCAPE))
SendMessage(main_window_handle,WM_CLOSE,0,0);
//使用显示表面前要加锁
memset(&ddsd,0,sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
if (FAILED(lpddsprimary->Lock(NULL, &ddsd,
DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT,
NULL)))
{
return(0);
}
int mempitch = (int)ddsd.lPitch;//每行内存间距
UCHAR *video_buffer = (UCHAR *)ddsd.lpSurface;//指向显示表面的指针
//绘制1000个随机像素到显示表面,颜色随机,立即可见
for (int index=0; index < 1000; index++)
{
// select random position and color for 640x480x8
UCHAR color = rand()%256;
int x = rand()%640;
int y = rand()%480;
// plot the pixel
video_buffer[x+y*mempitch] = color;
}
if (FAILED(lpddsprimary->Unlock(NULL)))
return(0);
Sleep(30);
return(1);
}
int Game_Shutdown(void *parms = NULL, int num_parms = 0)
{
//清理资源
// first the palette
if (lpddpal)
{
lpddpal->Release();
lpddpal = NULL;
}
// now the primary surface
if (lpddsprimary)
{
lpddsprimary->Release();
lpddsprimary = NULL;
}
// now blow away the IDirectDraw4 interface
if (lpdd)
{
lpdd->Release();
lpdd = NULL;
}
return(1);
}
// WINMAIN
int WINAPI WinMain( HINSTANCE hinstance,
HINSTANCE hprevinstance,
LPSTR lpcmdline,
int ncmdshow)
{
WNDCLASSEX winclass; // this will hold the class we create
HWND hwnd; // generic window handle
MSG msg; // generic message
HDC hdc; // graphics device context
// first fill in the window class stucture
winclass.cbSize = sizeof(WNDCLASSEX);
winclass.style = CS_DBLCLKS | CS_OWNDC |
CS_HREDRAW | CS_VREDRAW;
winclass.lpfnWndProc = WindowProc;
winclass.cbClsExtra = 0;
winclass.cbWndExtra = 0;
winclass.hInstance = hinstance;
winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
winclass.hCursor = LoadCursor(NULL, IDC_ARROW);
winclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
winclass.lpszMenuName = NULL;
winclass.lpszClassName = WINDOW_CLASS_NAME;
winclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
// save hinstance in global
hinstance_app = hinstance;
// register the window class
if (!RegisterClassEx(&winclass))
return(0);
// create the window
if (!(hwnd = CreateWindowEx(NULL, // extended style
WINDOW_CLASS_NAME, // class
L"DirectDraw Full-Screen Demo", // title
WS_POPUP | WS_VISIBLE,
0,0, // initial x,y
SCREEN_WIDTH,SCREEN_HEIGHT, // initial width, height
NULL, // handle to parent
NULL, // handle to menu
hinstance,// instance of this application
NULL))) // extra creation parms
return(0);
// save main window handle
main_window_handle = hwnd;
// initialize game here
Game_Init();
// enter main event loop
while(TRUE)
{
// test if there is a message in queue, if so get it
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// main game processing goes here
Game_Main();
}
// closedown game here
Game_Shutdown();
// return to Windows like this
return(msg.wParam);
}
需要注意的地方有两点:一是CreateWindowEx()创建窗口的时候使用了WS_POPUP窗口风格,即去除一切控件和Windows GDI内容,而这正是全屏DirectX应用程序所需要的。
二是GetAsyncKeyState()函数的使用:GetAsyncKeyState(int vKey)确定用户是否按下了键盘上的一个键,参数为要检查键的虚键代码。返回值的最高位用来表示这个键是否按下(是为1,否为0),最低位表示在上次调用GetAsyncKeyState()后这个键是否被按下。
另一个函数GetKeyState与GetAsyncKeyState函数不同。GetAsyncKeyState在按下某键的同时调用,判断正在按下某键GetKeyState则在按过某键之后再调用,它返回最近的键盘消息从线程的队列中移出时的键盘状态,判断刚按过了某键。
SHORT GetKeyState(int nVirtKey);
SHORT GetAsyncKeyState(int vKey);
BOOL GetKeyboardState(PBYTE lpKeyState);
三个取key status的函数的最大区别是:
第一个:是从windows消息队列中取得键盘消息,返回key status.
第二个:是直接侦测键盘的硬件中断,返回key status.
第三个:是当从windows消息队列中移除键盘消息时,才返回key status