距离上一次更新有一个月了,这个月进行了SHADOWMAP和HDR,幸运的是,4*4PCF已经搞定,HDR仍然在继续中。后来发现了还需要动态贴图,这个2D的引擎需要继续写下去,尽管不知道会如何。
闲话少说,现在看看离屏表面,第一类是后备缓冲,
后备缓,是指一些动画链中的表面,具有和主表面相同的尺寸和色深,创建主表面时也创建后备缓冲。
后备缓冲通常在VRAM中,另外,也可以将后备缓冲 和主表面进行页面切换。
创建关联有后备缓冲的主表面,
1, 将DDSD_BACKBUFFERCOOUNT加到dwFlags标志字段,从而使dwBackBufferCount字段有效,并填上后备缓冲数目
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.dwBackBufferCount = 1;
2, ddsCaps.dwCaps字段上加上DDSCAPS_COMPLEX和DDSCAPS_FLIP
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_COMPLEX | DDSCAPS_FLIP;
3, 创建主表面
lpdd->CreateSurface(&ddsd, &lpddsprimary, NULL);
4, 存放后备缓冲变量
Ddsd.ddsCaps.dwCaps = DD3CAPS_BACKBUFFER;
5,关联后备缓冲,用GetAttachedSurface()
lpddsprimary->GetAttachedSurface( & ddsd.ddsCaps, & lpddsback );
页面切换的几个步骤
1, 清除后备缓冲
2, 将场景渲染到后备缓冲
3, 用后备缓冲表面切换掉主表面
4, 锁定在某个帧速率
5, 重复步骤1
另外提到了显卡不足的可能,不过,现在的显卡都很大了,不存在这个问题,不过,也是一种解决问题的方法。
根据惯例,先看下运行结果
现在考虑引擎,使用双缓冲,首先,加上后备缓冲
DDRAW_Interface.h
LPDIRECTDRAWSURFACE7 m_lpddsback;
Interface.cpp
构造函数中,
DDRAW_Interface::DDRAW_Interface()
{
m_lpddsback = NULL;
}
析构函数中释放
DDRAW_Interface::~DDRAW_Interface()
{
if( m_lpddsback )
{
m_lpddsback->Release();
m_lpddsback = NULL;
}
}
下一步,在MAIN函数中使用的话,要返回值,
LPDIRECTDRAWSURFACE7 DDRAW_Interface::getbackbuffer()
{
return m_lpddsback;
}
下一步,进行创建带缓冲的主表面,
HRESULT DDRAW_Interface::InitDDraw( HWND hWnd, DWORD width, DWORD height, DWORD bpp,bool windowed )
{
。。。。。。
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.dwBackBufferCount = 1;
// request primary surface
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_COMPLEX | DDSCAPS_FLIP;
ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
lpddsprimary->GetAttachedSurface( & ddsd.ddsCaps, & lpddsback );
}
在main()函数中,进行循环
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))
{
exit( 0 );
//PostMessage(main_window_handle,WM_CLOSE,0,0);
} // end if
DDSURFACEDESC2 ddsd;
// copy the double buffer into the primary buffer
DDRAW_INIT_STRUCT(ddsd);
// lock the primary surface
ddraw->getbackbuffer()->Lock(NULL,&ddsd, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT,NULL);
// get video pointer to primary surfce
UCHAR * back_buffer = (UCHAR *)ddsd.lpSurface;
// test if memory is linear
if (ddsd.lPitch == SCREEN_WIDTH)
{
// copy memory from double buffer to primary buffer
memset( back_buffer, 0, SCREEN_WIDTH*SCREEN_HEIGHT);
} // end if
else
{ // non-linear
// make copy of source and destination addresses
UCHAR *dest_ptr = back_buffer;
// memory is non-linear, copy line by line
for (int y=0; y < SCREEN_HEIGHT; y++)
{
// copy line
memset( dest_ptr, 0, SCREEN_WIDTH);
// advance pointers to next line
dest_ptr+=ddsd.lPitch;
} // end for
} // end else
// you would perform game logic...
// draw the next frame into the double buffer
// plot 5000 random pixels
for (int index=0; index < 1000; index++)
{
int x = rand()%SCREEN_WIDTH;
int y = rand()%SCREEN_HEIGHT;
UCHAR col = rand()%256;
back_buffer[x+y*ddsd.lPitch] = col;
} // end for index
// now unlock the primary surface
if (FAILED(ddraw->getbackbuffer()->Unlock(NULL)))
return(0);
while( FAILED( ddraw->getPrimarySurface()->Flip( NULL, DDFLIP_WAIT ) ) );
// sleep a bit
Sleep(30);
// return success or failure or your own return code here
return(1);
}
运行OK
第三步,就是如何根据书上的t3dlib1引擎更改。以标准些。
先看看《3D游戏编程大师技巧》中,如何说的,第3章把它们当做黑盒子对待,不过,说明的原理很透彻,
这里有必要说明下缓存交换,
方法是:同时锁定辅助缓存和主缓存,逐行复制。
具体步骤
1, 清除辅助缓存
2, 锁定辅助缓存
3, 写象素
4, 解锁辅助缓存
5, 交换主缓存和辅助缓存
这里有两个注意的地方
1, 逐行复制辅助缓存的时候,不用memcpy(),因为这个函数无法知道辅助缓存的行与行之间是否连续。因此,要根据内存跨距复制缓存,并处理每一行。
2, 访问缓存的实际内存时,必须锁定缓存,完成操作时,必须解除对缓存的锁定。
3, 窗口显示时,不允许使用双缓存(主缓存和后备缓存),而是创建一个主缓存(整个屏幕)和一个离屏缓存(大小与窗口客户区相同),换页时,把离屏后缓存复制到窗口的客户区域。即全屏FLIP(),窗口blit()
T3DLIB中,有三个函数对应这个DEMO
1,FLIP_DISPLAY()交换缓存
2,清除辅助缓存Fill_Secondary( int color)
3,清除主缓存Fill_Primary( int color )
先看看ddraw_Init(),发现FLIP仅使用于全屏,所以要改变到全屏区域部分,T3DLIB写了好几个判断是否全屏,我觉得写一个比较好,因为可读性容易些。窗口模式暂且不管。
现在可以加入DDRAW_FLIP(),分为全屏和窗口,
void DDRAW_Interface::DDraw_Flip()
{
if( !m_bWindowed )
{
while( FAILED( m_lpddsprimary->Flip( NULL, DDFLIP_WAIT ) ) );
}
}
并且在main()函数调用时ddraw->DDraw_Flip();即可,
现在是晚上22:52,我觉得还有必要将T3DLIB中的一些东西搞进来,对了,比如判断是否为全屏,而进行的标志,#define WINDOWED_APP 1 //0--全屏,-窗口
将创建窗口的标志WS_POPUPWINDOW | WS_VISIBLE,改为判断
WINDOWED_APP ? (WS_OVERLAPPED | WS_SYSMENU | WS_VISIBLE ) : ( WS_POPUP | WS_VISIBLE ),
这样,以前的问题应该能够解决了,把DDRAW_INIT()中的setdisplaymode()从窗口模式中也去掉吧。
再看下DDrawFlip()m这里涉及到了主从缓冲变量,可以加上。primary_buffer 和 back_buffer,初始值均为NULL,FLIP()函数改为
int DDRAW_Interface::DDraw_Flip()
{
if( m_primarybuffer || m_backbuffer )
return ( 0 );
if( !m_bWindowed )
{
while( FAILED( m_lpddsprimary->Flip( NULL, DDFLIP_WAIT ) ) );
}
return ( 1 );
}
再加上primary_lpitch和back_lpitch,来对内存定位,以及返回back_lpitch,锁定和解锁各内存,
UCHAR * DDRAW_Interface::DDraw_Lock_Primary_Surface()
{
if( m_primarybuffer )
return m_primarybuffer;
DDSURFACEDESC2 ddsd;
DDRAW_INIT_STRUCT( ddsd );
m_lpddsprimary->Lock( NULL, & ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL );
m_primarybuffer = ( UCHAR * ) ddsd.lpSurface;
m_primarylpitch = ddsd.lPitch;
return m_primarybuffer;
}
int DDRAW_Interface::DDraw_Unlock_Primary_Surface(void)
{
if( !m_primarybuffer )
return 0;
m_lpddsprimary->Unlock( NULL );
m_primarybuffer = NULL;
m_primarylpitch = 0;
return 1;
}
UCHAR * DDRAW_Interface::DDraw_Lock_Back_Surface()
{
if( m_backbuffer )
return m_backbuffer;
DDSURFACEDESC2 ddsd;
DDRAW_INIT_STRUCT( ddsd );
m_lpddsback->Lock( NULL, & ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL );
m_backbuffer = ( UCHAR * ) ddsd.lpSurface;
m_backlpitch = ddsd.lPitch;
return m_backbuffer;
}
int DDRAW_Interface::DDraw_Unlock_Back_Surface(void)
{
if( !m_backbuffer )
return 0;
m_lpddsback->Unlock( NULL );
m_backbuffer = NULL;
m_backlpitch = 0;
return 1;
}
UCHAR * DDRAW_Interface::DDraw_Lock_Surface(LPDIRECTDRAWSURFACE7 lpdds,int *lpitch)
{
if( !lpdds )
return NULL;
DDSURFACEDESC2 ddsd;
DDRAW_INIT_STRUCT( ddsd );
lpdds->Lock( NULL, & ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL );
if( lpitch )
* lpitch = ddsd.lPitch;
return ( UCHAR * ) ddsd.lpSurface;;
}
int DDRAW_Interface::DDraw_Unlock_Surface(LPDIRECTDRAWSURFACE7 lpdds)
{
if( !lpdds )
return 0;
lpdds->Unlock( NULL );
return 1;
}
int DDRAW_Interface::getbacklpitch()
{
return m_backlpitch;
}
在game_main()中,
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))
{
exit( 0 );
//PostMessage(main_window_handle,WM_CLOSE,0,0);
} // end if
UCHAR * back_buffer = ddraw->DDraw_Lock_Back_Surface();
// test if memory is linear
//if (ddsd.lPitch == SCREEN_WIDTH)
if (ddraw->getbacklpitch() == SCREEN_WIDTH)
{
// copy memory from double buffer to primary buffer
memset( back_buffer, 0, SCREEN_WIDTH*SCREEN_HEIGHT);
} // end if
else
{ // non-linear
// make copy of source and destination addresses
UCHAR *dest_ptr = back_buffer;
// memory is non-linear, copy line by line
for (int y=0; y < SCREEN_HEIGHT; y++)
{
// copy line
memset( dest_ptr, 0, SCREEN_WIDTH);
// advance pointers to next line
// dest_ptr+=ddsd.lPitch;
dest_ptr+=ddraw->getbacklpitch();
} // end for
} // end else
// you would perform game logic...
// draw the next frame into the double buffer
// plot 5000 random pixels
for (int index=0; index < 1000; index++)
{
int x = rand()%SCREEN_WIDTH;
int y = rand()%SCREEN_HEIGHT;
UCHAR col = rand()%256;
// back_buffer[x+y*ddsd.lPitch] = col;
back_buffer[x+y*ddraw->getbacklpitch()] = col;
} // end for index
ddraw->DDraw_Unlock_Back_Surface();
ddraw->DDraw_Flip();
// sleep a bit
Sleep(30);
// return success or failure or your own return code here
return(1);
}
Ok了,基本上全屏的就这个意思了。现在是4月10日0:23了,花费了6个小时。