目前mobile phone 遊戲API簡稱GAPI為手機上的遊戲開發人員提供了強有力的高效率的編程介面,當然GAPI不僅僅使用在遊戲方面,需要高效率圖形顯示處理的地方都可以使用GAPI。
GAPI是基於動態串連庫方式,應用程式直接調用動態庫裡的函數,一般GAPI庫的檔案名稱為GX.dll,目前mobile phone裡都提供了gx.dll檔案。
一個典型的遊戲或者應用程式使用下列GAPI函數:
OpenDisplay (fullscreenflag)
開啟GAPI顯示功能。
OpenInput
開啟直接響應硬體鍵盤輸入訊息功能
GetDisplayProperties
獲得VFB詳細結構資訊
GetDefaultKeys
獲得預設的索引值
操縱GAPI
開始一個遊戲編寫,首先要開啟GAPI顯示功能,獲得控制視頻顯示緩衝的控制許可權。可以調用
GXOpenDisplay(HWND hwnd, DWORD dwFlags)
hwnd參數是遊戲程式的視窗控制代碼,dwFlags定義了顯示模式,宏GX_FULLSCREEN定義全螢幕模式,就能對裝置的全屏地區進行控制。傳回值1說明開啟成功,0是失敗。
雖然都是使用mobile phone系統但是不同系列的產品可能使用不同的顯示裝置,那麼對於不同的顯示裝置就可能有不同的顯示績效參數,不同的解析度率,不同的色深,不同的顏色顯示能力。當在編寫一個為mobile phone 系列啟動並執行遊戲程式時不得不考慮這些問題,以使程式能適應在不同的顯示環境下達到程式所希望的顯示效果。
如何得到這些相關顯示資訊?可以調用下面函數:
GXDLL_API GXDisplayProperties GXGetDisplayProperties ();
它能得到顯示裝置的所有相關細節資訊,這些都是在開發基於GAPI遊戲時需要的。所有資訊被返回到GXDisplayProperties結構中,其結構如下:
struct GXDisplayProperties {
DWORD cxWidth;
DWORD cyHeight;
long cbxPitch;
long cbyPitch;
long cBPP;
DWORD ffFormat;
};
這個結構提供了顯示裝置的資訊,也就是當前視頻快取區域的參數指標。
cxWidth和cyHeight是顯示裝置的寬和高的像素值,提供了顯示裝置橫、縱能顯示的像素個數;cBPP是每個像素點需要的位元,總是等於2的n次方。cbxPitch和cbyPitch提供了相鄰兩個像素間從記憶體資料上相差的位元組數,cbxPitch表示的是左右兩個像素間的差值,當cBPP大於等於8時,cbxPitch表示相差的位元組個數,當cBPP小於8時,cbxPitch已經不能真實的反映出相差的位元組數,事實上必須自己計算得到相鄰的地址:
比如:
cBPP = 4;
Leftpointaddr = pb + (((current_x+1) * cBPP) >> 3)
+ (current_y * cbyPitch)
cbyPitch表示的上下方向間兩個像素的差值,計算時通過加減cbyPitch來的到上下方向的像素點的地址:
downpointaddr = currentpointaddr + cbyPitch;
ffFormat參數說明顯示裝置對色深的處理方式及顯示的格式:
當ffFormat 等於 KfLandscape 說明當前顯示方式是橫屏方式,即原點(0,0)變成了左下角。
ffFormat 等於 KfPalette 說明色彩顯示是基於調色盤方式。
ffFormat 等於 KfDirect 說明色彩顯示是直接映射,不引用調色盤。
ffFormat 等於 KfDirectInverted 說明顏色顯示是反轉的。
ffFormat 等於 kfDirect555 、kfDirect565、kfDirect888 說明映射顏色顯示時,數字表示紅綠藍所佔的位資訊。
計算每一個像素座標地址方法如下(x,y):
unsigned char * pb;
if (cBPP < 8) {
address = pb + ((x * cBPP) >> 3) + (y * cbyPitch)
}
else
{
address = pb + (x * cbxPitch) + (y * cbyPitch);
}
判斷是否是標準顯示裝置
可以使用函數GXIsDisplayDRAMBuffer (),傳回值為TRUE說明是非標準顯示裝置,傳回值為FLASE說明是標準顯示裝置。當是非標準顯示裝置時,需要使用函數GXSetViewport來定義顯示螢幕的地區,在標準顯示裝置上使用GXSetViewport是無效的。
GXDLL_API int GXSetViewport (
DWORD dwTop,
DWORD dwHeight,
DWORD dwReserved1,
DWORD dwReserved2)
dwTop定義了螢幕顯示地區的y座標,dwHeight顯示地區的高度,dwReserved1、dwReserved被保留,必須需設定為0。
開始繪製像素
現在就可以準備對緩衝區進行操作繪製圖形,通過GXBeginDraw得到緩衝區的首地址:
void * GXBeginDraw ();
函數傳回值就是需要的首地址,如果是NULL說明顯示緩衝區得不到。然後就可以進行一些列像素的操作,操作完畢後需要調用GXEndDraw 結束一次操作:
int GXEndDraw ();
返回1說明調用成功,0說明錯誤。
提交繪製的資訊,已使變化的畫面生效。當程式失去焦點時必須調用GXSuspend ()掛起所有GAPI的操作,把螢幕控制交給其他程式,當接收到獲得焦點資訊時,程式必須調用GXResume ()使得程式繼續運行GAPI函數。
當退出程式時,必須釋放GAPI資源,可以調用:
int GXCloseDisplay ();
GAPI高效貼圖
在開發一些影像處理或遊戲時我們可以使用GDI製作出滿意的產品,但是開發複雜高速的圖形顯示或高效率的動態遊戲時,往往對GDI的顯示效率不高而感到沮喪,雖然可以使用雙緩衝等技術,但是GDI層介面畢竟效率低,無法滿足要求。
GAPI對顯示緩衝區的直接操作,使顯示效率大大提高,所以在目前mobile phone上當需要處理高速貼圖時GAPI就當之無愧了。
雖然GAPI高效強大,提供了對顯示緩衝區直接的讀寫權限,但是基於如此低級的功能函數,在編寫一個稍微複雜的程式時,就會花費大量的時間和精力在處理對顯示緩衝區的操作,因為此緩衝區並不像GDI提供的繪製緩衝區對圖片顯示一樣操作容易,為了顯示一幅bmp圖就需要編寫好幾頁的代碼,這是非常令人厭倦的事。
在這裡主要介紹一下一個使用GAPI編寫的第三方貼圖類STGapiBuffer。STGapiBuffer提供了類似於GDI方式的介面操作簡單,很容易就構造出了需要顯示的緩衝內容,最後只要簡單的把它們拷貝進顯示緩衝區,就可以顯示出來了。
只要簡單的把STGapiBuffer.h 和 STGapiBuffer.cpp加入到工程裡面就可以方面的使用了。
為了需要繪製一個jpg圖片,首先需要把圖片載入入此類裡,並建立適合STGapiBuffer處理的資料,使用CreateNativeBitmap函數,需要如下操作:
HBITMAP hBitmap = SHLoadDIBitmap(_T("//image.bmp");
g_pNativeBitmap = g_gapiBuffer.CreateNativeBitmap(hBitmap);
:eleteObject(hBitmap);
接下去需要為繪製到哪裡設定目標對象,可以是另一個STGapiBuffer的緩衝區,也可以是顯示的顯示裝置緩衝區,調用函數SetBuffer,代碼如下:
g_gapiBuffer.SetBuffer(pDisplayBuffer);
最好可以使用CSTGapiBuffer::BitBlt來把需要的資料繪製到緩衝區裡,類似於GDI函數BitBlt,代碼如下:
g_gapiBuffer.BitBlt(0, 0, 100, 100, g_pNativeBitmap);
這樣就把一幅圖片顯示到了螢幕上。
CSTGapiBuffer還提供了繪製透明圖片的函數,在繪圖時經常會遇到這樣的情況,使用CSTGapiBuffer::MaskedBlt方便的繪製指定透明色的圖案。當然我在用GDI繪圖時經常使用CreateMemoryDC建立一個臨時記憶體DC來繪製,CSTGapiBuffer也提供了類似的功能的函數CSTGapiBuffer:: CreateMemoryBuffer
CSTGapiBuffer類使用樣本(部分代碼):
CNativeBitmap* pAsteroidBitmap = NULL;
CNativeBitmap* pAsteroidMask = NULL;
CSTGapiBuffer gapiBufferBackground; // 背景
CSTGapiBuffer gapiBufferMemory;
CSTGapiBuffer gapiBufferScreen;
HBITMAP hBackground = ::LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BACKGROUND));
CNativeBitmap * pBackgroundBitmap =
gapiBufferMemory.CreateNativeBitmap(hBackground);
:eleteObject(hBackground);
gapiBufferBackground.CreateMemoryBuffer();
gapiBufferBackground.BitBlt(0, 20, dwDispWidth, dwDispHeight,
pBackgroundBitmap);
delete pBackgroundBitmap;
pBackgroundBitmap = NULL;
////////////////////////////////////
HBITMAP hAsteroid = ::LoadBitmap(hInst, MAKEINTRESOURCE(IDB_ASTEROID));
pAsteroidBitmap = gapiBufferMemory.CreateNativeBitmap(hAsteroid);
:eleteObject(hAsteroid);
HBITMAP hAsteroidMask = ::LoadBitmap(hInst, MAKEINTRESOURCE(IDB_ASTEROID_MASK));
pAsteroidMask = gapiBufferMemory.CreateNativeBitmap(hAsteroidMask);
::DeleteObject(hAsteroidMask);
/////////////////////////////////////////
//
dwTransparentColor = gapiBufferMemory.GetNativeColor(RGB(249, 57, 198));
// create an offscreen buffer
gapiBufferMemory.CreateMemoryBuffer();
gapiBufferMemory.BitBlt(&gapiBufferBackground);
///////////////////////////////////////////////////
//
gapiBufferMemory.TransparentBltEx(0, nOffset%dwDispHeight,
50, 60, pAsteroidBitmap, dwTransparentColor);
gapiBufferMemory.TransparentBltEx(100, (nOffset*2)%dwDispHeight,
50, 60, pAsteroidBitmap, dwTransparentColor);
gapiBufferMemory.TransparentBltEx(nOffset%dwDispWidth,
50, 50, 60, pAsteroidBitmap, dwTransparentColor);
gapiBufferMemory.TransparentBltEx((nOffset*2)%dwDispWidth,
150, 50, 60, pAsteroidBitmap, dwTransparentColor);
gapiBufferMemory.MaskedBlt(dwDispWidth-(nOffset*2)%dwDispWidth,
(nOffset*8)%dwDispHeight, 50, 60, pAsteroidBitmap, pAsteroidMask);
gapiBufferMemory.MaskedBlt((nOffset%dwDispWidth*5), (nOffset*3)%
dwDispHeight, 50, 60, pAsteroidBitmap, pAsteroidMask);
gapiBufferMemory.MaskedBlt((nOffset*3)%dwDispWidth, (nOffset*4)%
dwDispHeight, 50, 60, pAsteroidBitmap, pAsteroidMask);
gapiBufferMemory.MaskedBlt((nOffset%dwDispWidth*2), dwDispHeight-
(nOffset*5)%dwDispHeight, 50, 60, pAsteroidBitmap, pAsteroidMask);
RECT rc = { 0, 20, dwDispWidth/3, 10 };
FillRect(&rc, RGB(255, 0, 0));
rc.left = dwDispWidth/3;
rc.right = 2*dwDispWidth/3;
FillRect(&rc, RGB(0, 255, 0));
rc.left = 2*dwDispWidth/3;
rc.right = dwDispWidth;
FillRect(&rc, RGB(0, 0, 255));
void* pBuffer = GXBeginDraw();
gapiBufferScreen.SetBuffer(pBuffer);
gapiBufferScreen.BitBlt(&gapiBufferMemory);
GXEndDraw();
Gapi鍵盤訊息
使用GXOpenInput()函數獲得鍵盤的控制權,調用GXGetDefaultKeys(GX_NORMALKEYS)函數來獲得預設鍵盤的訊息映射。然後在Windows訊息處理函數中我們就能收到由GAPI發送過來的鍵盤訊息,當你按下某一個鍵,程式會收到WM_KEYDOWN,wParam參數包含GAPI映射的這個鍵的訊息,通過與得到的GXKeyList結構中的欄位定義來判斷當前收到的鍵是不是定義的功能鍵。
GXKeyList結構如下:
struct GXKeyList {
short vkUp;
POINT ptUp;
Short vkDown;
POINT ptDown;
Short vkLeft;
POINT ptLeft;
Short vkRight;
POINT ptRight;
Short vkA;
POINT ptA;
Short vkB;
POINT ptB;
Short vkC;
POINT ptC;
Short vkStart;
POINT ptStart;
};
遊戲的震動感
在遊戲中提供震動效果,是讓玩家非常興奮的事情,可以提升遊戲的吸引程度。Mobile Phone SDK 提供了震動效果的API,允許你控制震動,類似與控制聲音一樣簡單。
獲得震動裝置屬性
要獲知手機是否支援震動,震動的效能,當前的設定情況,可以調用下面的函數:
int VibrateGetDeviceCaps(VIBRATEDEVICECAPS vdc);
VIBRATEDEVICECAPS是個枚舉類型,結構如下
typedef enum {
VDC_AMPLITUDE,
VDC_FREQUENCY,
VDC_LAST
} VIBRATEDEVICECAPS
VDC_AMPLITUDE 查詢震動裝置所能支援的振幅大小
VDC_FREQUENCY 查詢震動裝置所能支援的震動頻率大小
VDC_LAST 查詢震動裝置所能支援的振幅大小
如果函數成功,它將返回數字0到7,數字0說明裝置沒有提供震動功能,1說明裝置具有震動功能,並且可以使用,但是僅僅具有開啟關閉震動,無法對震動進行調節,2到7說明了裝置提供了不同等級的震動功能,數字越大提供的調節能力越強。當裝置具有不同等級震動能力時,我就可以通過VIBRATENOTE結構做詳細設定。
怎麼才能開始真正的使用震動功能呢?mobile phone SDK提供Vibrate函數:
HRESULT Vibrate(
DWORD cvn,
const VIBRATENOTE * rgvn,
BOOL fRepeat,
DWORD dwTimeout
);
它能提供不同振幅,不同頻率,並可以調節需要震動時間。cvn參數是第二個參數rgvn數組的維數,rgvn是指向一組VIBRATENOTE結構的指標。
VIBRATENOTE結構如下:
typedef struct {
WORD wDuration;
BYTE bAmplitude;
BYTE bFrequency;
} VIBRATENOTE
wDuration說明了震動持續的時間,bAmplitude定義了震動的振幅大小,允許設定0-7級,如果等於0xff系統使用預設值作為參數,bFrequency定義了震動的頻率高低,允許設定0-7級,如果0xff系統使用預設值作為參數。
當你需要停止當然的震動時,可以調用VibrateStop()函數,返回S_OK說明成功調用,E_FAIL說明調用失敗。
下面是程式碼範例:
int caps = -1;
caps = VibrateGetDeviceCaps(VDC_AMPLITUDE);
if(caps<=0)
return FALSE; //振幅返回失敗,說明不支援震動功能
HRESULT hr = Vibrate(0, NULL, TRUE, INFINITE); //設定為無時間限制
if(hr == E_FAIL)
{
MessageBox(NULL,L"E_FAIL",L"",MB_OK);
}
else if(hr == E_NOTIMPL)
{
MessageBox(NULL,L"E_NOTIMPL",L"",MB_OK);
}
Sleep(1000); //震動所花時間
VibrateStop();
開始第一手機遊戲曆程
在這裡使用GAPI類比一個貪食者遊戲,它非常簡單。主要注重怎麼具體使用GAPI,在使用中怎麼對視頻緩衝區操作示範,並不去美化外表。
初始化GAPI庫,在InitInstance函數裡我們對GAPI的顯示和輸入進行了初始化。
if (GXOpenDisplay( hWnd, GX_FULLSCREEN) == 0)
return FALSE;
gx_displayprop = GXGetDisplayProperties();
if (gx_displayprop.cBPP != 16)
{
// Only dealing with 16 bit color in this code
GXCloseDisplay();
MessageBox(hWnd,L"Sorry, only supporting 16bit color",L"Sorry!", MB_OK);
return FALSE;
}
framebuf = (unsigned short*) malloc(sizeof(short)*gx_displayprop.cxWidth*gx_displayprop.cyHeight);
if(framebuf==NULL)
return FALSE;
ClearScreen(framebuf,0xff,0xff,0xff);
GXOpenInput();
// Get default buttons for up/down etc.
gx_keylist = GXGetDefaultKeys(GX_NORMALKEYS);
初始化工作完成後,我們就需要對遊戲的內容進行必要準備工作。我們首先初始化貪食者對象,我們為它建立蛇頭和它的身體。為了使貪食者不停的遊動,必須有一個事件觸發。在這個我們使用定時器,以100ms的間隔發送訊息,這樣將得到一秒10幀的效果,這足以滿足普通遊戲的效果。
為了對定時器發送過來的訊息進行處理,我們調用Run函數
void Run(HWND hwnd)
{
if(1 == JudgeDeath(framebuf))
{
KillTimer(hwnd,1);
RunVibrate(1000);
MessageBox(hwnd,_T("Snake has died!",_T("died",
MB_OK | MB_ICONINFORMATION);
SendMessage(hwnd,WM_PAINT,0,0);
InvalidateRect(hwnd,NULL,TRUE);
}
ChangeDirection();
SortAll();
RedrawSnake();
}
JudgeDeath每次都會判斷是否已經死亡(蛇的任何部位有重疊),一旦滿足死亡的條件,就取消定時器,以便中止蛇的遊動。ChangeDirection判斷方向是否發生了改變。在這裡我對蛇部位的重疊利用了兩個象素是否相同的顏色,如果顏色一直說明重疊發生。
void Get16Pixel(unsigned short *buffer,int x, int y,int *r, int *g, int *b)
{
unsigned short *pixeladd;
int address = (x * gx_displayprop.cbxPitch>>1)
+ (y * gx_displayprop.cbyPitch>>1);
pixeladd = (buffer+address);
if (gx_displayprop.ffFormat & kfDirect565)
{
unsigned short PixelCol;
PixelCol = (*pixeladd);
*r = (PixelCol & 0xf800) >> 11;
*g = (PixelCol & 0x07e0) >> 5;
*b = (PixelCol & 0x001f);
}
else//555
{
unsigned short PixelCol;
PixelCol = (*pixeladd);
*r = (PixelCol & 0x7c00) >> 11;
*g = (PixelCol & 0x03e0) >> 5;
*b = (PixelCol & 0x001f);
}
}
第三方開發庫介紹
GapiDraw的設計與DirectDraw非常相似,而且將更加容易使用,極大限度的為掌上裝置進行了最佳化。下面是一些DriectDraw中的一般功能,以及如何在GapiDraw中實現這些功能。
開啟顯示裝置
電腦的顯示記憶體是一塊包含了映像資料的記憶體地區。為了直接向這塊地區進行寫操作,DirectDraw和GapiDraw都需要你建立一個指定的稱之為主介面的介面。直接繪製到這個主介面來影響螢幕的可見內容。
建立主介面的第一步是開啟顯示器,設定一個顯示模式。下面的步驟是使用DirectDraw建立主介面的最少步驟。
DirectDraw
LPDIRECTDRAW lpDD;
HRESULT ddrval;
//建立主Direct Draw對象
ddrval = DirectDrawCreate(NULL, &lpDD, NULL);
if(ddrval != DD_OK)
{
return(false);
}
//設定合作層級以允許Direct Draw全屏運行
ddrval = lpDD->SetCooperativeLevel(hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
if(ddrval != DD_OK)
{
lpDD->Release();
return(false);
}
//設定顯示模式為320x240x16
ddrval = lpDD->SetDisplayMode(320, 240, 16);
if(ddrval != DD_OK)
{
lpDD->Release();
return(false);
}
使用GapiDraw是比較容易的。因為介面是對象而不是COM介面,所以根本就不用手工進行釋放。下面的GapiDraw例子只使用了一條命令開啟顯示裝置,設定預設顯示模式。
GapiDraw
CGapiDisplay display;
HRESULT gdrval;
//使用標準Pocket PC240x320x16模式開啟顯示
gdrval = display.OpenDisplay(hwnd, GDOPENDISPLAY_FULLSCREEN);
if(gdrval != GD_OK)
{
return(false);
}
取回主介面和後背緩衝
使用Direct Draw,你必須手工請求建立一個指定介面的主Direcr Draw對象,主介面主要用於直接在顯示器上繪製。在Direct Draw中只有一個介面介面用於雙記憶體介面和顯示。這可以簡單地解釋為過去使用的COM模式中的子類化缺陷。下面的例子建立一個主介面,使用Direct Draw取回它的後背緩衝。
DirectDraw
LPDIRECTDRAWSURFACE lpDDSPrimary; // DirectDraw 主介面
LPDIRECTDRAWSURFACE lpDDSBack; // DirectDraw 後背緩衝
DDSURFACEDESC ddsd;
DDSCAPS ddscaps;
HRESULT ddrval;
memset(&ddsd, 0, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1;
//建立主介面
ddrval = lpDD->CreateSurface(&ddsd, &lpDDSPrimary, NULL);
if(ddrval != DD_OK)
{
lpDD->Release();
return(false);
}
// Get the back buffer獲得背後緩衝
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps, &lpDDSBack);
if(ddrval != DD_OK)
{
lpDDSPrimary->Release();
lpDD->Release();
return(false);
}
相反,使用GapiDraw 事情會更容易。一旦調用了CGapiSurface::OpenDisplay,CGapiDisplay 對象自動地變成主介面。因為CGapiDisplay 是CGapiSurface的子類,所有的位元影像傳輸(blit)和繪製操作都已經是可用的了。為了從CGapiDisplay得到後背緩衝,使用如下代碼:
GapiDraw
CGapiSurface backbuffer; // GapiDraw 後背緩衝
HRESULT gdrval;
//得到後背緩衝
gdrval = display.GetBackBuffer(&backbuffer);
if(gdrval != GD_OK)
{
return(false);
}
失敗的介面
DirectDraw的介面通常儲存在映像儲存空間中,可以在任何時候被覆蓋(在使用者切換程式或者啟動另外一個使用GDI的程式的情況下)。這是因為對介面的每個操作在任何時候都有可能失敗,簡單地說是因為介面資料被覆蓋。因此所有使用Direct Draw的操作必須每次檢查介面是否失敗,然後從失敗處手工恢複和重新建立介面。下面的例子說明了這一點。
DirectDraw
ddrval = lpDDSBack->Blt(&rcRectDest, lpDDSMySurf, &rcRectSrc, DDBLT_WAIT, NULL);
if(ddrval == DDERR_SURFACELOST)
{
//介面被覆蓋,現在你必須手工恢複和重新建立所有的介面
}
ddrval = lpDDSPrimary->Flip(NULL, DDFLIP_WAIT);
if(ddrval == DDERR_SURFACELOST)
{
//介面被覆蓋,現在你必須手工恢複和重新建立所有的介面
}
Pocket PC不使用映像儲存空間,所有介面資料被儲存在RAM實體記憶體,只有調用CGapiDisplay::Flip時才被複製到顯示地區。如果Pocket PC裝置訪問了顯示地區的緩衝,那麼Pocket PC可能會移動它的後背緩衝的位置。到目前為止,還沒有裝置被告之可以做到這點,但是它始終是最好的設計。可以使用下面的代碼捕獲在GapiDraw中丟失的後背緩衝。
GapiDraw
//普通操作中的介面不能丟失
gdrval = backbuffer.Blt(&rcRectDest, &mysurf, &rcRectSrc, 0, NULL);
gdrval = display.Flip();
if(gdrval == GDERR_BACKBUFFERLOST)
{
//顯示緩衝被移動,剛好獲得一個更新的後背緩衝
display.GetBackBuffer(&backbuffer);
}
結論
上面提及的是GapiDraw和Direct Draw之間的主要不同。其它的特性,象blit、顏色值、矩形座標等都是有差異的。GapDraw也包含一個巨大的擴充特性,這些特性在Direct Draw不可用,象進階的blit影響、快速旋轉、從檔案或記憶體裝載位元影像映像、繪製工具、衝突掩碼、介面交叉、線程定時器、位元影像字型支援以及更多。