前面的例子都是,直接在主表面上绘东西。对于动画,直接在主表面上绘,会产生很严重的闪烁。解决的办法是采用双缓冲或后备缓冲。
双缓冲
双缓冲:在离屏缓冲中绘制图像,然后将其拷贝到显示表面。
见下面代码,先把数据放到double_buffer,最后再拷贝到主表面上。
下面Sleep(300);睡眠了0.3秒这么长的时间是为了更为明显的看到画面的变化。
int Game_Main() { if (window_closed) return 0 ; if (KEYDOWN(VK_ESCAPE)) { PostMessage(main_window_handle,WM_CLOSE, 0 , 0 ); return 0 ; window_closed = 1 ; } memset(( void * )double_buffer, 0 , SCREEN_WIDTH * SCREEN_HEIGHT); for ( int index = 0 ; index < 5000 ; index ++ ) { int x = rand() % SCREEN_WIDTH; int y = rand() % SCREEN_HEIGHT; UCHAR col = rand() % 256 ; // 先把颜色在于缓冲区中 double_buffer[x + y * SCREEN_WIDTH] = col; } DDRAW_INIT_STRUCT(ddsd); lpddsprimary -> Lock(NULL, & ddsd, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT,NULL); // 主表面显存指针 UCHAR * primary_buffer = (UCHAR * )ddsd.lpSurface; // 再把缓冲区的内容拷贝到主表面。 if (ddsd.lPitch == SCREEN_WIDTH) { memcpy(( void * )primary_buffer, ( void * )double_buffer, SCREEN_WIDTH * SCREEN_HEIGHT); } else { UCHAR * dest_ptr = primary_buffer; UCHAR * src_ptr = double_buffer; // 一行一行的拷贝 for ( int y = 0 ; y < SCREEN_HEIGHT; y ++ ) { memcpy(( void * )dest_ptr, ( void * )src_ptr, SCREEN_WIDTH); dest_ptr += ddsd.lPitch; src_ptr += SCREEN_WIDTH; } } if (FAILED(lpddsprimary -> Unlock(NULL))) return 0 ; Sleep( 300 ); return 1 ; }
后备缓冲
后备缓冲也是离屏表面的一种。它是一些用在动画链中的表面,它们具有和主表面相同的尺寸和色深。当创建主表面的时候也创建它们。它和主表面进行页面切换,这比双缓冲方案下所需做的内存拷贝要快得多。
创建一个关联后备缓冲的主表面步骤(复杂表面):
- 把DDSD_BACKBUFFERCOUNT加到dwFlagss标志字段,把DDSURFACEDESC2结构的dwBackBufferCount字段设为有效。(它可以设置后备缓冲的数目)
- 把DDSCAPS_COMPLEX | DDSCAPS_FLIP加到DDSURFACEDESC2 结构的ddsCaps.dwCaps
- 创建主表面。
- 得到后备缓冲有再个方法。设置DDSCAPS2 ddscaps.dwCaps= DDSCAPS_BACKBUFFER; 或 ddsd.ddsCaps.dwCaps =DDSCAPS_BACKBUFFER;
然后调用 GetAttachedSurface(&ddsd.ddsCaps, &lpddsback)得到后备缓冲。
见下面代码:
DDRAW_INIT_STRUCT(ddsd); ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT; // 增加DDSD_BACKBUFFERCOUNT 表明dwBackBufferCount有效 ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_COMPLEX | DDSCAPS_FLIP; // 多了 DDSCAPS_COMPLEX | DDSCAPS_FLIP ddsd.dwBackBufferCount = 1 ; // 后备缓冲的个数 if (FAILED(lpdd -> CreateSurface( & ddsd, & lpddsprimary, NULL))) return 0 ; // 请求一个后备缓冲 ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER; // 得到后备缓冲 if (FAILED(lpddsprimary -> GetAttachedSurface( & ddsd.ddsCaps, & lpddsback))) return 0 ;
以上代码。Lpddsprimary指向主表面,lpddsback指向后备缓冲表面。
对后备缓冲表面进行存取,也需要对其加锁和解锁。在对后备缓冲表面绘图后,把怎么把所之图显示到主表面呢。这就要用到页面切换了(而且这个切换是由硬件完成的)。
用主表面调用下面的函数来切换。(注意在页面切换前主表面或后备缓冲表面都必须被解锁)
HRESULT Flip(LPDIRECTDRAWSURFACE7 lpDDSurfaceTargetOverride ,DWORD dwFlags);
第一个参数:用来覆盖切换链,实现切换到另外一个表面,不是切换到同主表面想关联的后备缓冲,一般为NULL。
第二个参数:控制标志。如下:
DDFLIP_INTERVAL2 | 2次垂直逆程后切换 |
DDFLIP_INTERVAL3 | 3次垂直逆程后切换 |
DDFLIP_INTERVAL4 | 4次垂直逆程后切换 |
这些标志 在DDCAPS结构中设置了DDCAPS2_FLIPINTERVAL才能起作用。默认值是1.
这些标志 表明在两个切换页之间等待多少垂直逆程,只有在指定的垂直逆程数目达到了,DDraw才为切换的每一页返回DERR_WASSTILLDRAWING
DDFLIP_NOVSYNC:执行物理切换时尽量靠近下一条扫描线。
DDFLIP_WAIT:强迫硬件不 在出现问题时立即返回,而是等待直到页面切换能够进行为止。一般就用这个标志比较多。
int MainGame() { if (window_closed) return 0 ; if (KEYDOWN(VK_ESCAPE)) { PostMessage(main_window_handle, WM_CLOSE, 0 , 0 ); window_closed = 1 ; } DDRAW_INIT_STRUCT(ddsd); lpddsback -> Lock(NULL, & ddsd, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT,NULL); UCHAR * back_buffer = (UCHAR * )ddsd.lpSurface; // 清除后备缓冲数据 if (ddsd.lPitch == SCREEN_WIDTH) memset(back_buffer, 0 , SCREEN_WIDTH * SCREEN_HEIGHT); else { UCHAR * dest_ptr = back_buffer; for ( int y = 0 ; y < SCREEN_HEIGHT; y ++ ) { memset(dest_ptr, 0 , SCREEN_WIDTH); dest_ptr += ddsd.lPitch; } } for ( int index = 0 ; index < 5000 ; index ++ ) { int x = rand() % SCREEN_WIDTH; int y = rand() % SCREEN_HEIGHT; UCHAR col = rand() % 256 ; back_buffer[x + y * ddsd.lPitch] = col; } if (FAILED(lpddsback -> Unlock(NULL))) return 0 ; // 切换主表面,是主表面调用Flip,另外要注意的是调用Flip之前,要先解锁。 while (FAILED(lpddsprimary -> Flip(NULL, DDFLIP_WAIT))); Sleep( 500 ); return 1 }
2011.06.07 fangyukuan
转载请保留下面链接
http://www.cnblogs.com/fangyukuan/archive/2011/06/07/2074713.html