Třídu CSurface již máme zcela naimplementovanou, dnes nám tedy zbývá třídy CDisplay, abychom dokončili knihovnu Display.dll. Článek opět bude trochu delší než je obvyklé a budeme se vesměs zabývat kódem, který znáte, takže popis bude stručný. Nakonec si ještě ukážeme, jak vytvořené funkce exportovat z knihovny.
Za prvé je potřeba si udělat návrh třídy - tabulku jako v případě třídy CSurface.
Pro CDisplay bude vypadat nějak takto:
Členské proměnné |
|
|
Typ |
Jméno |
Popis |
LPDIRECTDRAW7 |
m_lpDD |
Ukazatel na rozhraní objektu DirectDraw. |
LPDIRECTDRAWCLIPPER |
m_lpDDClipper |
Ukazatel na rozhraní objektu clipper. |
LPDIRECTDRAWPALETTE |
m_lpDDPalette |
Ukazatel na rozhraní objektu palety. |
CSurface |
m_surBack |
Objekt zadního bufferu (back buffer). |
CSurface |
m_surFront |
Objekt předního bufferu (front buffer). |
Proměnné potřebné pro vykreslování pozadí |
||
CSurface |
m_surBackground |
Objekt povrchu pozadí. |
CRect |
m_ScrollRect |
Obdélník použitý při posunu pozadí, viz funkce UpdateBackGround(). |
CRect |
m_BackSource |
Opět je to obdélník vztahující se k vykreslování pozadí. V tomto případě zdrojový obdélník použitý při vykreslování statického pozadí. |
CString |
m_BackBMP |
Cesta k bitmapě pozadí potřebná při obnově pozadí. |
COLORREF |
m_BackColor |
Pokud je pozadí jednobarevné, tato hodnota určuje jeho barvu. |
Informace o rozlišení a formátu zadního a předního povrchu |
||
UINT |
m_Res_X |
Počet pixelů v horizontálním směru - rozlišení. |
UINT |
m_Res_Y |
Počet pixelů ve vertikálním směru. |
BYTE |
m_Depth |
Barevná hloubka povrchů. |
Následují proměnné, které jsou součástí počítadla FPS (Frame per second) |
||
int |
m_StartTime |
Počáteční čas při počítaní FPS. |
int |
m_StopTime |
Koncový čas FPS. O tom jak funguje FPS counter, se více dovíte při popisu funkce UpdateFPS(). |
double |
m_Frames |
Počet snímků za určitý časový okamžik určený konstantou FPS_REFRESH_RATE. |
CString |
m_strFrames |
Řetězec, který je vykreslován na monitor. Je tu jen kvůli tomu, aby se nemusel po každé obnově pozadí znovu formátovat nový řetězec. |
Ostatní proměnné |
||
BOOL |
m_bIsInit |
TRUE pokud je objekt CDisplay řádně zinicializován. |
DWORD |
m_dwFlags |
Nastavení objektu CDisplay. |
IDirectDrawGammaControl* |
m_GammaControl |
Nakonec je tu podpora řízení gammy (světlosti). Toto je ukazatel na rozhraní IDirectDrawGammaControl. Řízení gammy je novinka, takže to probereme trochu podrobněji u funkcí, které se gammou zabývají. |
int |
m_CurGamma |
Aktuální nastavení Gamma v rozmezí 0 - 255. |
Metody |
|
|
Návratová hodnota |
Jméno |
Popis |
HRESULT |
Základní funkce knihovny, která musí být zavolána na prvním místě! První parametr je handle na okno aplikace. Druhý parametr typu DWORD nastavuje objekt CDisplay, viz dále. |
|
void | Present() | Funkce Present() provádí prohození povrchů a obnovu FPS počítadla. |
HRESULT | UpdateFPS() | O této funkci již byla řeč. Spočítá a obnoví FPS counter. |
HRESULT |
SetPalette() |
Tato funkce nejprve vytvoří paletu z předem určeného souboru a poté ji nastaví. |
Práce s pozadím | ||
HRESULT |
Následující dvě funkce definují pozadí dvěma způsoby. První verze definuje jednobarevné pozadí - jediný parametr určuje barvu. |
|
HRESULT |
Druhá verze naproti tomu definuje pozadí z bitmapy určené prvním parametrem. Další vlastnosti pozadí jsou určeny druhým parametrem typu DWORD. |
|
void |
Překreslí pozadí definované předchozími dvěma funkcemi. Tuto funkci je nutno volat na začátku obnovovací smyčky. |
|
Funkce řídící Gamma corection |
||
BOOL |
Funkce zjistí, zda-li je systém vůbec schopen gammu řídit. |
|
HRESULT | IncrementGamma(int) | Zvýší stupeň gammy o hodnotu určenou parametrem. V praxi to znamená zesvětlení scény. |
HRESULT | DecrementGamma(int) | Sníží stupeň gammy o hodnotu určenou parametrem. V praxi to znamená ztmavení scény. |
Inline metody | ||
BOOL | IsInit() | Vrací hodnotu proměnné m_bIsInit. |
LPDIRECTDRAW7 | GetDirectDraw() | Vrací ukazatel na rozhraní objektu DirectDraw. |
CSurface* | GetBackBuffer() | Vrací ukazatel na objekt zadního povrchu. |
CSize | GetResolution() | Vrací aktuální rozlišení ve struktuře CSize(cx, cy). |
Ostatní funkce | ||
void | Clean() | Funkce provádějící zametací práce, viz minulá lekce. |
- | CDisplay() | Konstruktor třídy. |
- | ~CDisplay() | Destruktor třídy. |
Vidíte, že třída je poměrně složitá. V následujících dvou částech si podrobněji probereme jednotlivé funkce.
Za prvé se podívejme na deklaraci:
class CDisplay
{
private:
//
// Objects of DirectDraw
LPDIRECTDRAW7 m_lpDD;
LPDIRECTDRAWCLIPPER m_lpDDClipper;
LPDIRECTDRAWPALETTE m_lpDDPalette;
// Front&back
buffer
CSurface m_surFront;
CSurface m_surBack;
//
// Background&scrolling
CSurface m_surBackground;
CRect m_ScrollRect;
CRect m_BackSource;
CString m_BackBMP;
COLORREF m_BackColor;
//
// Common display flags
DWORD m_dwFlags;
//
// Information about display mode
UINT m_Res_X;
UINT m_Res_Y;
BYTE m_Depth;
//
// Initialization of system
BOOL m_bIsInit;
//
// Display FPS counter
int m_StartTime;
int m_StopTime;
double m_Frames;
CString m_strFrames;
//
// Gamma control support
IDirectDrawGammaControl* m_GammaControl;
int m_CurGamma;
public:
BOOL IsInit() {return m_bIsInit;}
//
// Create surfaces etc.
HRESULT CreateFullScreenSystem(HWND hWnd, DWORD dwFlags);
//
// Palette functions
HRESULT SetPalette();
//
// Flipping surfaces
void Present();
//
// Return directdraw objects
LPDIRECTDRAW7 GetDirectDraw() {return m_lpDD;}
CSurface* GetBackSurface() {return &m_surBack;}
// Resolution
CSize GetResolution() {return CSize(m_Res_X, m_Res_Y);}
//
// Background functions
HRESULT DefineBackground(COLORREF crColor);
HRESULT DefineBackground(CString strBMPFile, DWORD dwFlags);
HRESULT UpdateBackground();
//
// Gamma corection support
HRESULT IncrementGamma(int _Amount = 8);
HRESULT DecrementGamma(int _Amount = 8);
private:
HRESULT UpdateFPS();
void Clean();
BOOL HasGammaSupport();
public:
CDisplay();
~CDisplay();
};
Na deklaraci není nic zvláštního. Držíme se přesně tabulky z
úvodu. Jen si všimněte, že některé metody jsou soukromé. Jsou to metody, ke
kterým nemá být přístup zvenku. Bylo by to zbytečné, protože jsou to ryze
interní funkce a uživatel o nich nepotřebuje vědět. Také si všimněte některých
implicitních parametrů.
Vždy se snažím oddělit metody a atributy třídy. Také nechci
míchat všechny soukromé prvky dohromady, proto najdete v deklaraci více sekcí
private a public. Na
tyto drobnosti upozorňuji především méně zkušené céčkaře, kteří nemají velké
zkušenosti s objektovým programováním.
Ještě před deklarací samotné třídy bychom měli nadefinovat některé konstanty:
Soubor Core.h:
#define FPS_REFRESH_RATE
1000
#define _C_DEFAULT_SCREENWIDTH 640
#define _C_DEFAULT_SCREENHEIGHT 480
#define _C_DEFAULT_SCREENDEPTH 16
#define _S_LOADING1 _T("\\Graphics\\Backgrounds\\loading1.bmp")
#define _S_ARIAL_DDF _T("\\Graphics\\Fonts\\Arial.ddf")
#define _S_HUMANS_DDF _T("\\Graphics\\Fonts\\Humanst521 BT.ddf")
#define _S_FONT_ARIAL _T("Arial")
#define _S_FONT_HUMANS _T("Humans")
Zde definujeme za prvé obnovovací interval FPS ukazatele.
Číslo 1000 znamená, že FPS counter, se bude obnovovat po 1 vteřině. Dále tam
máme implicitní hodnoty rozlišení, které se nastavují v případě, že se nepodaří
načíst správné hodnoty z konfiguračního souboru. To samé platí pro barevnou
hloubku. Další tři konstanty jsou cesty do datového souboru. Knihovna
Display.dll totiž interně používá některé bitmapy
(fonty a pozadí při inicializaci). Všechny bitmapy máme v adresáři Graphics.
Všimněte si, jakým způsobem cesty definujeme. Budeme tak totiž definovat cesty i
k vašim vlastním souborům (nemusí jít jen o bitmapy).
Poslední dvě konstanty definují identifikační řetězec jednotlivých fontů.
Podle těchto řetězců jsou fonty přepínány pomocí metod ze třídy
CDDFontEngine. Tato třída se stará o vykreslování
fontů. Tu zde nebudu rozebírat, protože jsem ji nepsal já a nám stačí pouze pár
jejich funkcí.
Soubor Common.h (tento soubor je součástí projektu Display a nemá nic společného s knihovnou Common.dll):
//
// Flags for CreateFullScreenSystem()
#define DDFLG_CLIPPER 0x00000001
#define DDFLG_WINDOWMODE 0x00000002
#define DDFLG_FULLSCREEN 0x00000004
#define DDFLG_GAMMA 0x00000008
#define DDFLG_PALETTE 0x00000010
// Common flags
#define DDFLG_FPS 0x00000020
// Flags for DefineBackground()
#define DDFLG_SYSTEM_MEMORY
0x10000000
#define DDFLG_VIDEO_MEMORY 0x20000000
#define DDFLG_SCROLLING 0x40000000
#define DDFLG_SOLID 0x80000000
#define DDFLG_COLORKEY 0xF0000000
//
// Keys
// Graphics section
#define _S_KEY_WIDTH _T("ScreenWidth")
#define _S_KEY_HEIGHT _T("ScreenHeight")
#define _S_KEY_DEPTH _T("ScreenDepth")
#define _S_KEY_FPS _T("FPS")
#define _S_KEY_USEGAMMA _T("UseGamma")
#define _S_KEY_SHOWVIDMEM _T("ShowVidMem")
Zde definujeme za prvé konstanty pro funkce CreateFullScreenSystem() a pro DefineBackground(). Pomocí těchto konstant můžeme nastavit další vlastnosti aplikace a pozadí. Rozeberme si je podrobněji:
DDFLG_CLIPPER - aktivuje clipper.
Pokud tuto hodnotu nezahrnete při volání funkce
CreateFullscreenSystem(), nebude se používat clipper.
DDFLG_WINDOWMODE - tento flag myslím nefunguje, ale
měl zabránit přepnutí do fullscreen módu a aplikace tak zůstala v okně.
DDFLG_FULLSCREEN - opak předchozího.
DDFLG_GAMMA - zapnutí podpory pro řízení gammy.
Systém si zjistí, zda-li je to vůbec možné.
DDFLG_PALETTE - Tento flag se nastaví, pokud je
nastavena paleta. Paleta by se měla
automaticky zapnout při 8-mi bitové hloubce barev.
DDFLG_FPS - tento flag je aktivován přes
konfigurační soubor. Pokud je v setup.ini 1, je
tento flag nastaven a FPS counter je zobrazen. V opačném případě nikoliv.
DDFLG_SYSTEM_MEMORY - vynuceni uložení povrchu
pozadí do systémové paměti. Je potřeba nastavit na grafických kartách, které
mají méně grafické paměti.
DDFLG_VIDEO_MEMORY - pravý opak předchozí
informace. Pozadí je vytvořeno ve video paměti a až když to nejde, je místo
alokováno v RAM.
DDFLG_SCROLLING - zapíná scrolling
pozadí. Zkuste si to a uvidíte jak to funguje. Tato funkce je značně nepružná a
jednoúčelová. Záleží jen na Vás, jak si to upravíte podle potřeb.
DDFLG_SOLID - informace o tom, že pozadí je
jednobarevné.
DDFLG_COLORKEY - tuto hodnotu využívá
CSurface při uchování informace o CK.
Další hodnoty jsou klíče v konfiguračním souboru, ze kterého čteme spoustu
informací. Tak můžete modifikovat chování aplikace, aniž by se musela znovu
překládat. Co znamenají, se dá snadno vyčíst z jejich názvu. Ještě je celkem
důležité, že flagy se dají kombinovat pomocí operátoru OR |.
V další části budeme postupně implementovat a rozebírat všechny metody třídy CDisplay. Začneme logicky u inicializace systému:
HRESULT CDisplay::CreateFullScreenSystem(HWND
hWnd, DWORD dwFlags)
{
//
// Temporary front and back buffer
LPDIRECTDRAWSURFACE7 lpDDSBack, lpDDSFront;
DDCAPS Hel; Hel.dwSize = sizeof(DDCAPS);
DDCAPS Driver; Driver.dwSize = sizeof(DDCAPS);
HRESULT dwRet;
//
// Init olny if we not init before
if(m_bIsInit) {
return 1;
}
//
// Check handle to window
ASSERT(hWnd);
//
// 1. krok
// Get resolution from "setup.ini" file
m_Res_X = setGetSetupInt(_S_SECTION_GRAPHICS, _S_KEY_WIDTH);
m_Res_Y = setGetSetupInt(_S_SECTION_GRAPHICS, _S_KEY_HEIGHT);
m_Depth = setGetSetupInt(_S_SECTION_GRAPHICS, _S_KEY_DEPTH);
if(m_Res_X == 0) m_Res_X = _C_DEFAULT_SCREENWIDTH;
if(m_Res_Y == 0) m_Res_Y = _C_DEFAULT_SCREENHEIGHT;
if(m_Depth == 0) m_Depth = _C_DEFAULT_SCREENDEPTH;
if(setGetSetupInt(_S_SECTION_GRAPHICS, _S_KEY_FPS)) {
m_dwFlags |= DDFLG_FPS;
}
//
// Set scroll rect
m_ScrollRect.SetRect(0, 0, m_Res_X, m_Res_Y);
//
DXTRACE("Initializing display...");
//
// 2. krok
// Creating DDraw
object
dwRet = DirectDrawCreateEx(NULL, (void**)&m_lpDD, IID_IDirectDraw7,
NULL);
DXERR ("Creating DirectDraw object", dwRet);
if(dwRet != DD_OK) {
Clean();
return dwRet;
}
//
// 3. krok
// Get capabilities of hardware
dwRet = m_lpDD->GetCaps(&Driver, &Hel);
DXERR ("Getting caps about hardware", dwRet);
if(dwRet != DD_OK) {
Clean();
return dwRet;
}
//
// 4. krok
// Set
cooperative level
dwRet = m_lpDD->SetCooperativeLevel(hWnd,
DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN);
DXERR ("Setting exclusive mode", dwRet);
if(dwRet != DD_OK) {
Clean();
return dwRet;
}
// Set display
mode
dwRet = m_lpDD->SetDisplayMode(m_Res_X,
m_Res_Y, m_Depth, 0, 0);
DXERR("Setting display mode", dwRet);
if(dwRet != DD_OK) {
Clean();
return dwRet;
}
//
// 5. krok
// Create flipping loop
DDSURFACEDESC2
ddsd;
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS|DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP |
DDSCAPS_COMPLEX | DDSCAPS_3DDEVICE;
ddsd.dwBackBufferCount = 1;
//
// Get a pointer to the front buffer
dwRet = m_lpDD->CreateSurface(&ddsd,
&lpDDSFront, NULL);
DXERR ("Getting pointer to front buffer", dwRet);
if(dwRet != DD_OK) {
Clean();
return dwRet;
}
//
// Create front buffer
dwRet = m_surFront.Create(lpDDSFront);
DXERR ("Creating front buffer", dwRet);
if(dwRet != DD_OK) {
Clean();
return dwRet;
}
//
// Get a pointer to the back buffer
DDSCAPS2 ddscaps;
ZeroMemory(&ddscaps, sizeof(ddscaps));
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
dwRet = m_surFront.GetSurface()->GetAttachedSurface(&ddscaps,
&lpDDSBack);
if(dwRet != DD_OK) {
DXERR ("Getting pointer to back
buffer", dwRet);
Clean();
return dwRet;
}
//
// Create back buffer
dwRet = m_surBack.Create(lpDDSBack);
if(dwRet != DD_OK) {
DXERR ("Creating back buffee", dwRet);
Clean();
return dwRet;
}
////////////////////////////////////////////////////////////////////////
m_bIsInit = true;
//
// 6. krok
dwRet = fntCreateFont(_S_ARIAL_DDF, _S_FONT_ARIAL);
if(dwRet != ERROR_SUCCESS) {
DXERR("Cannot create specified font
due", dwRet);
}
dwRet = fntCreateFont(_S_HUMANS_DDF, _S_FONT_HUMANS, TC_DARKGREEN);
if(dwRet != ERROR_SUCCESS) {
DXERR("Cannot create specified font
due", dwRet);
}
//
// 7. krok
// Display loading bitmap
disDefineBackground(_S_LOADING1, DDFLG_VIDEO_MEMORY);
disUpdateBackground();
disPresent();
//
// 8. krok
// Clipper
if(dwFlags & DDFLG_CLIPPER)
{
//
// Create rectangular region
CRgn rect; RGNDATA data; int n;
rect.CreateRectRgnIndirect(CRect(0,
0, m_Res_X, m_Res_Y));
n = rect.GetRegionData(NULL, 0);
if(rect.GetRegionData(&data, n) !=
ERROR) {
//
// Create
clipper
dwRet = m_lpDD->CreateClipper(0, &m_lpDDClipper, NULL);
DXERR ("Creating
clipper object", dwRet);
if(DD_OK !=
dwRet) {
Clean();
return dwRet;
}
//
// Set
clipper
dwRet = m_lpDDClipper->SetClipList(&data, 0);
DXERR ("Setting
clipper list", dwRet);
if(DD_OK !=
dwRet) {
Clean();
return dwRet;
}
dwRet = m_surBack.GetSurface()->SetClipper(m_lpDDClipper);
DXERR ("Setting
clipper", dwRet);
if(DD_OK !=
dwRet) {
Clean();
return dwRet;
}
m_dwFlags |=
DDFLG_CLIPPER;
}
else {
Clean();
return 1;
}
}
//
// 9. krok
// Gamma correction
BOOL bUseGamma = setGetSetupInt(_S_SECTION_GRAPHICS, _S_KEY_USEGAMMA);
if(bUseGamma && (dwFlags & DDFLG_GAMMA) && HasGammaSupport())
{
dwRet = m_surFront.GetSurface()->QueryInterface(IID_IDirectDrawGammaControl,
(LPVOID*) &m_GammaControl);
DXTRACE6("Getting pointer to gamma
correction object due", dwRet);
if(!m_GammaControl) {
Clean();
return dwRet;
}
m_dwFlags |= DDFLG_GAMMA;
}
//
// 10.krok
// Create and set palette
// If we have only 8-bit color depth, we need palette
if(m_Depth == 8) {
dwRet = SetPalette();
DXERR("Setting pallete due", dwRet);
if(dwRet != DD_OK) {
Clean();
return dwRet;
}
m_dwFlags |= DDFLG_PALETTE;
}
return dwRet;
}
Tato funkce je asi nejdelší, ale rozhodně není nikterak
složitá:
V prvním kroku načteme některé informace z konfiguračního souboru. Konkrétně se
jedná o rozlišení, barevnou hloubku a viditelnost FPS počítadla. Ve druhém kroku
vytváříme objekt DirectDraw tak, jak jsme zvyklí.
Ve 3. kroku získáme informace o funkcích grafické karty. Tyto informace můžete
využít pro případné zjištění některých vlastností grafické karty.
Dále (4.krok) následuje dvojice funkcí SetCooperativeLevel()
a SetDisplayMode(). I tyto dvě funkce dobře známe z
předešlých lekcí.
5.krok: Inicializujeme přední a zadní povrch. Ani zde není nic nového, snad
kromě prvního využití objektu z minulé lekce CSurface.
Nejprve inicializujeme ukazatele na rozhraní
LPDIREDRAWSURFACE7 a poté z těchto ukazatelů vytvoříme objekty
CSurface.
V 6. kroku vytváříme interní fonty pro vykreslení FPS apod. Nyní konečně vidíte,
jak budeme pracovat s fonty. Nejdříve se musí zavolat funkce
fntCreateFont(), která vytvoří nový font z bitmapy. Funkci předáte
řetězec, pod kterým tento font půjde najít (měl by být jedinečný), dále cestu k
bitmapě (obojí jsme definovali) a případně barvu
fontu.
V 7. kroku pouze definuje pozadí (Loading...). Jde tedy pouze o informaci
uživateli. Protože flippovací smyčka ještě není v provozu, musíte uměle zavolat
funkce UpdateBackground() a
Present(). Tím zajistíte, že se pozadí rovnou zobrazí na monitoru.
8. krok je clipper. Pokud si to uživatel přeje, je na zadní povrch připojen
clipper. Práci s ním jsme již rovněž probírali.
V 9. kroku inicializujeme gamma korekci. Nejprve zjistíme zda-li uživatel chce
řídit gamma korekci, po té zda-li to chce programátor a nakonec zda-li je to
vůbec možné ze strany hardwaru. Poté zavoláme funkce
QueryInterface(), abychom získali ukazatel na rozhraní
IDirectDrawGammaControl. Blíže si o řízení gammy
povíme u funkcí Increment a
DecrementGamma().
10. a poslední krok je určen paletě. Pokud uživatel nastaví 8-mi bitovou hloubku
barev, systém načte bitmapu z připraveného zdroje, který je vložen přímo do
knihovny.
Dále si probereme funkci, která zjistí zda-li má vaše grafická karta schopnost řídit gamma korekci:
BOOL CDisplay::HasGammaSupport()
{
// Get driver
capabilities to determine gamma support.
DDCAPS ddcaps;
ZeroMemory( &ddcaps, sizeof(ddcaps) );
ddcaps.dwSize = sizeof(ddcaps);
GetDirectDraw()->GetCaps( &ddcaps, NULL );
// Does the
driver support gamma?
// The DirectDraw emulation layer does not support overlays
// so gamma related APIs will fail without hardware support.
if( ddcaps.dwCaps2
& DDCAPS2_PRIMARYGAMMA )
return TRUE;
else
return FALSE;
}
Funkce HasGammaSupport() vrací TRUE, pokud gamma korekce je možná, jinak FALSE. Použijeme k tomu opět funkce objektu DirectDraw GetCaps(). Budou nás zajímat pouze schopnosti driveru (první parametr funkce) a nikoliv emulace (druhá parametr), která gammu neumí. Gamma tedy musí být podporována hardwarově. Následujícím příkazem zjistíme hodnotu flagu DDCAPS2_PRIMARYGAMMA. Pokud je tato hodnota různá od 0, funkce vrací TRUE, jinak FALSE.
Funkci Present() již také známe z minulých lekcí. Dnes ji obohatíme jen o prát maličkostí:
void CDisplay::Present()
{
HRESULT dwResult;
//
// Check initialization
if(!m_bIsInit) {
return;
}
//
// Check if we want to draw FPS
if(m_dwFlags & DDFLG_FPS) {
//
// Draw FPS
dwResult = UpdateFPS();
if(dwResult != ERROR_SUCCESS) {
DXERR("Cannot
UpdateFPS due", dwResult);
}
}
//
// Try to flip surfaces
// Neverending loop
while(1)
{
dwResult = m_surFront.GetSurface()->Flip(NULL,
0);
//we must restore main buffers
if(dwResult == DDERR_SURFACELOST) {
DXTRACE("Restoring
surfaces");
m_surFront.Restore();
m_surBack.Restore();
if(m_surBackground.IsValid())
{
m_surBackground.Restore(TRUE);
}
}
if(DD_OK == dwResult) {
break;
}
}
}
Za prvé sledujeme, zda-li uživatel má zájem o zobrazení FPS. V kladném případě FPS obnovíme ve funkci UpdateFPS(). Za zmínku ještě stojí obnova hlavních povrchů při jejich ztrátě (viz. předešlé lekce). Využíváme k tomu členské metody Restore(). Jinak na této metodě není nic zajímavého.
Následuje dvojice metod definující pozadí. Náš systém podporuje dva typy pozadí. Za prvé je to jednobarevné (solid) pozadí definované následující metodou:
HRESULT CDisplay::DefineBackground(COLORREF
crColor)
{
//
// Release old
surface if initialized
m_surBackground.Release();
//
// Check initialization
if(!m_bIsInit) {
return 1;
}
//
// Save color of background
m_BackColor = crColor;
//
// Enable solid background
m_dwFlags |= DDFLG_SOLID;
//
// Disable scrolling
m_dwFlags &= ~DDFLG_SCROLLING;
return ERROR_SUCCESS;
}
Parametr crColor určuje barvu pozadí. K definici této barvy použijeme makro SURFACECOLOR(r,g,b). V souboru Common.h (projektu Common) je několik předdefinovaných barev. Na samotné funkci nic není. Uvolníme povrch pozadí, který zde není potřeba a musíme počítat s tím, že uživatel před tím mohl tento povrch potřebovat. Uložíme si barvu pozadí, abychom pozadí mohli obnovit na začátku každého cyklu. Dále systému dáme vědět, že chceme jednobarevné pozadí a vypneme pro jistotu flag dynamického pozadí, který je zde k ničemu (viz. dále). Všimněte si, že v každé funkci testujeme inicializaci objektu CDisplay.
Druhá verze definuje pozadí na základě bitmapy:
HRESULT CDisplay::DefineBackground(CString
strBMPFile, DWORD dwFlags)
{
HRESULT dwRet;
DDSURFACEDESC2 ddsdesc;
// Init size
before use
ddsdesc.dwSize =
sizeof(DDSURFACEDESC2);
//
// Check initialization
if(!m_bIsInit) {
return 1;
}
//
// Release old surface if initialized
m_surBackground.Release();
//
// Try to create new surface for background bitmap
dwRet = m_surBackground.Create(m_Res_X, m_Res_Y, dwFlags);
DXERR("Creating surface for background due", dwRet);
if(dwRet != ERROR_SUCCESS) {
return dwRet;
}
//
// Copy bitmap to surface
dwRet = m_surBackground.CopyBitmap(strBMPFile);
DXERR("Copying bitmap to surface due", dwRet);
if(dwRet != ERROR_SUCCESS) {
return dwRet;
}
//
// Save info about character of background
if(dwFlags & DDFLG_SCROLLING) {
m_dwFlags |= DDFLG_SCROLLING;
}
else {
m_dwFlags &= ~DDFLG_SCROLLING;
}
//
// Save info about background
// Save internal path for restore
m_BackBMP = strBMPFile;
// No solid, we
want bitmap background
m_dwFlags &= ~DDFLG_SOLID;
//
// Init source rectangle for blitting
m_BackSource.left
= 0;
m_BackSource.top = 0;
m_BackSource.right = m_surBackground.Width();
m_BackSource.bottom = m_surBackground.Height();
return dwRet;
}
Zde je to už o trochu složitější. Za prvé opět uvolníme předchozí pozadí (pokud nějaké je). Po té vytvoříme nový povrch o velikosti rozlišení. Do tohoto povrchu nakopírujeme bitmapu určenou parametrem strBMPFile. Dále zapneme nebo vypneme dynamické pozadí. Pokud uživatel chce dynamické pozadí musí funkce předat flag DDFLG_SCROLLING. Na efekt dynamického pozadí se podívejte sami v příkladu. Uložíme si jméno bitmapy, které použijeme při obnově pozadí. Vypneme volbu jednobarevného pozadí a nastavíme zdrojový obdélník pro funkci Blt(). To je vše.
Teď si vysvětlíme možná principielně nejsložitější funkci UpdateBackground():
HRESULT CDisplay::UpdateBackground()
{
HRESULT dwReturn;
CRect dest1, src1, dest2, src2;
//
// Detect if background is scrolling or not
if(m_dwFlags & DDFLG_SCROLLING) {
//
// Moving of blitting area
m_ScrollRect.left += 1;
m_ScrollRect.right += 1;
//
// Reset m_rectScroll ,
// End of the screen: m_ScrollRect.left = 0, m_ScrollRect.right = m_Res_X;
if(m_ScrollRect.left == (int)m_Res_X) {
m_ScrollRect.left = 0;
m_ScrollRect.right = m_Res_X;
}
//
// Compute of right and left parts of blitting source anf destination rectangles
//
// Left destination rectangle
dest1.top = 0;
dest1.left = 0;
dest1.right = m_Res_X - m_ScrollRect.left;
dest1.bottom = m_Res_Y;
//
// Left source rectangle
src1.top = 0;
src1.left = m_ScrollRect.right - m_Res_X;
src1.right = m_Res_X;
src1.bottom = m_Res_Y;
//
// Right destination rectangle
dest2.top = 0;
dest2.left = m_Res_X - m_ScrollRect.left;
dest2.right = m_Res_X;
dest2.bottom = m_Res_Y;
//
// Right source rectangle
src2.top = 0;
src2.left = 0;
src2.right = m_ScrollRect.right - m_Res_X;
src2.bottom = m_Res_Y;
//
// Blit both part of background to the BS
//
// Blit first part of bcg
if(src1.left != src1.right) {
dwReturn = m_surBack.Blt(dest1, src1, &m_surBackground);
if(dwReturn != ERROR_SUCCESS) {
DXERR("Cannot blit bitmap scrl part 1 background due", dwReturn);
return dwReturn;
}
}
//
// Blit second part of bcg
if(src2.left != src2.right) {
dwReturn = m_surBack.Blt(dest2, src2, &m_surBackground);
if(dwReturn != ERROR_SUCCESS) {
DXERR("Cannot blit bitmap scrl part 2 background due", dwReturn);
return dwReturn;
}
}
}
else {
if(m_dwFlags & DDFLG_SOLID) {
//
// Blit solid nonscrolling background to the back buffer
dwReturn = m_surBack.Blt(CRect(0, 0, m_Res_X, m_Res_Y), m_BackColor);
if(dwReturn != ERROR_SUCCESS) {
DXERR("Cannot blit solid background due", dwReturn);
return dwReturn;
}
}
else {
//
// Blit bitmap non scrolling background to the BS
dwReturn = m_surBack.Blt(CRect(0, 0, m_Res_X, m_Res_Y), m_BackSource, &m_surBackground);
if(dwReturn != ERROR_SUCCESS) {
DXERR("Cannot blit bitmap background due", dwReturn);
return dwReturn;
}
}
}
return dwReturn;
}
Funkce obnovuje pozadí, ať už jednobarevné či bitmapové (statické nebo dynamické). V první části obnovujeme právě dynamické pozadí. K vysvětlení si dovolím obrázek:
Princip spočívá ve dvou nezávislých vykreslení. První vykreslí část pozadí do obdélníku dest1 a druhý do obdélníku dest2. Každý cyklus se poměr mezi dest1 a dest2 mění: dest1 se zvětšuje a dest2 se zmenšuje až dest1 vyplní celý zadní buffer. V dalším cyklu se začne na novo. Zároveň se také musí správně inicializovat zdrojové obdélníky. Ty jsou určeny průnikem obdélníků dest do obdélníku povrchu pozadí. Poměr obou obdélníku je dán obdélníkem m_ScrollRect. Vidíme, že hodnoty left a right se každým cyklem inkrementují a po nabytí maximální hodnoty (rozlišení v horizontálním směru) se hodnota left vynuluje a hodnota right inicializuje na rozlišení m_Res_X. Na konci této sekce je samotné vykreslení obou částí.
V další části funkce, se vykresluje statické pozadí buď jednobarevné nebo bitmapové. Jednobarevné vykreslení je velice jednoduché. Prostě zavoláme verzi funkce Blt(), která kreslí jednobarevné plochy do povrchu. Pokud chceme vykreslovat bitmapové pozadí, zavoláme druhou verzi této funkce. Parametry jsou zcela logické.
Pokračujme dále s funkcí UpdateFPS(). Jak název napovídá, funkce má na starosti výpočet a aktualizaci ukazatele FPS, případně stav grafické paměti:
HRESULT CDisplay::UpdateFPS()
{
DWORD dwRet;
//
// Show video memory
if(setGetSetupInt(_S_SECTION_GRAPHICS,
_S_KEY_SHOWVIDMEM)) {
DDCAPS Driver;
ZeroMemory(&Driver, sizeof(DDCAPS));
Driver.dwSize = sizeof(DDCAPS);
//
// Get capabilities of hardware
dwRet = m_lpDD->GetCaps(&Driver, NULL);
if(dwRet != DD_OK) {
DXERR ("Getting
caps about hardware", dwRet);
Clean();
return dwRet;
}
CString csTMem, csFMem;
csTMem.Format("Total video memory: %d
kB", Driver.dwVidMemTotal / 1024);
csFMem.Format("Free video memory: %d
kB", Driver.dwVidMemFree / 1024);
fntDrawText(0, 20, csTMem, _S_FONT_ARIAL);
fntDrawText(0, 40, csFMem, _S_FONT_ARIAL);
}
//
// Get time of system start
m_StartTime = GetTickCount();
//
// Increment frames counter
m_Frames++;
//
// Compute number of frames per one second
if((m_StartTime - m_StopTime) >= FPS_REFRESH_RATE) {
m_strFrames.Format("%2.1f", m_Frames
* 1000 / (m_StartTime - m_StopTime));
m_Frames = 0;
//
// Save old-new time
m_StopTime = m_StartTime;
}
//
// Draw FPS to the screen
return fntDrawText(0, 0, m_strFrames, _S_FONT_ARIAL);
}
Pokud si uživatel prostřednictvím konfiguračního souboru přeje zobrazit stav paměti, musíme tento stav vydolovat pomocí již známé funkce GetCaps(). Struktura DDCAPS skrývá dva atributy dwVidMemTotal pro celkové množství grafické paměti a dwVidMemFree pro volné množství paměti. Hodnoty, které jsou v bajtech, dělíme číslem 1024, abychom dospěli ke kB. Pomocí funkce fntDrawText() vykreslíme řetězec na obrazovku. Funkce má 4 parametry. První dva určují souřadnice textu na monitoru. Další parametr je řetězec, který se bude vykreslovat. Posledním parametrem je ID fontu, který jsme předem vytvořili funkcí fntCreateFont(). Zkuste si vytvořit metodu, která bude fungovat podobně jako funkce printf() tj. bude mít proměnný počet parametrů a požadovaný řetězec automaticky zformátuje a vykreslí.
O trochu složitější je to s FPS. FPS se aktualizuje jednou za periodu určenou konstantou FPS_REFRESH_RATE. V této době zvyšujeme počet obnovených snímků o 1. Pokud je čas obnovit informaci o stavu FPS, spočítáme FPS tak, že počet snímků vydělíme uplynulým časem od poslední aktualizace. Konstanta FPS_REFRESH_RATE je v milisekundách, ale my chceme frame per second, takže je třeba vše převést na sekundy. Navíc je potřeba vynulovat počítadlo snímků. Nakonec vykreslíme aktuální stav FPS stejným způsobem jako u paměti.
Zkusme se nyní rozebrat dvojicí funkce IncrementGamma() a DecrementGamma(). Obě funkce jsou téměř identické, takže je tu nebudu rozepisovat obě. Na malý rozdíl samozřejmě upozorním:
HRESULT CDisplay::IncrementGamma(int
_Amount)
{
DWORD dwRet;
DDGAMMARAMP ramp;
if(m_dwFlags & DDFLG_GAMMA) {
m_CurGamma += _Amount;
if(m_CurGamma > 256) {
m_CurGamma =
256;
return ERROR_SUCCESS;
}
ZeroMemory( &ramp, sizeof(ramp) );
dwRet = m_GammaControl->GetGammaRamp(
0, &ramp);
if(dwRet != DD_OK) {
return dwRet;
}
WORD dwGamma = 0;
m_CurGamma += _Amount;
// ve funkce DecrementGamma() je zde m_CurGamma
-= _Amount;
if(m_CurGamma > 256) m_CurGamma =
256;
for( int
iColor = 0; iColor < 256; iColor++ )
{
ramp.red[iColor] = dwGamma;
ramp.green[iColor] = dwGamma;
ramp.blue[iColor] = dwGamma;
dwGamma += (WORD) m_CurGamma;
}
dwRet = m_GammaControl->SetGammaRamp(
0, &ramp );
if(dwRet != DD_OK) {
return dwRet;
}
}
return 1;//ERROR_NOGAMMA;
}
Princip je jednoduchý. Nejdříve získáme aktuální stav gammy,
po té přičteme ke všem barevným složkám parametr funkce a tento nový stav
nastavíme.
Jak jsem slíbil v úvodu, nyní si povíme něco blíže k řízení gammy. Systém dokáže
změnit barevné vlastnosti povrchů, aniž by měnil jejich obsah. Funkci si můžete
představit jako filtr skrz který proženete všechny pixely daného povrchu (ve
skutečnosti to lze jen na předním povrchu) těsně předtím
než se zobrazí na monitor. Pomocí rozhraní
IDirectDrawGammaControl můžete řídit tento filtr a dosáhnout tak
zajímavých efektů jako jsou různé záblesky či ztmavení scény. V našem systému je
řízení velice jednoduché. Řídíme pouze zesvětlení a ztmavení scény. To v praxi
znamená, že měníme všechny tři složky RGB stejně. K modifikaci barvy
jednotlivých pixelů se používají tzv. ramp levels pomocí nichž se
definuje závislost vstupu a výstupu filtru. Takovou závislost můžete vidět i na
následujícím obrázku:
Tyto modifikované výstupní hodnoty dále procházejí přímo do D/A převodníku grafické karty (DAC) a pak rovnou v analogové podobě na monitor.
Nejprve tedy získáme objekt typu DDGAMMARAMP obsahující ramp levels všech tří barevných složek. Tyto hodnoty získáme pomocí funkce GetGammaRamp().
Pokud používáme gamma korekci je Barva X nahrazena Barvou Y. Pokud graf prochází osou prvního kvadrantu, výstupní barva je stejná jako vstupní (jinými slovy, barva se nijak nemodifikuje). Ve funkci je smyčka for, ve které se hodnoty výstupní barvy modifikují podle proměnné m_CurGamma. Čím vyšší jsou tyto hodnoty, tím je výstupní barva světlejší, protože toto nastavení provádíme u všech tří složek zároveň. V poli tedy budou vzestupné hodnoty podobně jako na obrázku. Funkce může modifikovat tak, že závislost nebude lineární a můžete tak například úplně potlačit některé barvy na povrchu.
V posledním kroku nastavím nové hodnoty gamma ramp pomocí funkce SetGammaRamp(). Funkce DecrementGamma() se liší v jediném znaménku. Ve zdrojovém kódu je na to upozorněno v šedém komentáři.
Konečně tu máme poslední funkci! Je to celkem složitá funkce SetPalette(). Protože paletu nebudeme používat, nebudu zde funkci rozebírat. Funkce získá paletu z předdefinované bitmapy a tuto paletu nastaví. Abyste paletu mohli použít, musíte vložit do knihovny zdroj (bitmapu), kterou nazvěte "PALETA". Tato bitmapa může být například 1x1, ale důležitá je její paleta, kterou vytvoříte například v CorelDraw při exportu.
Nyní již máme knihovnu připravenou k použití. Abychom si to trochu ulehčili, nebudeme v klientské aplikaci vytvářet objekt CDisplay, ale vyexportujeme některé její funkce.
Do projektu přidejte soubory nové Common.h a Common.cpp. V těchto dvou souborech bude deklarace a definice exportovaných funkcí. V Common.cpp navíc musíme vytvořit objekt CDisplay, pomocí kterého budeme volat výše definované metody. Interně také používám objekt CDDFontEngine.
Common.h:
#ifndef DISPLAY_COMMON_H
#define DISPLAY_COMMON_H
//
// Include DD headers
#include <ddraw.h>
#include "..\Common\Common.h"
//
// Export/import macros
#ifndef
DISPLAY_API
#define DISPLAY_API __declspec(
dllimport )
#endif //
DISPLAY_API
class CDisplay;
class CSurface;
//
// Flags for CreateFullScreenSystem()
#define DDFLG_CLIPPER
0x00000001
#define DDFLG_WINDOWMODE 0x00000002
#define DDFLG_FULLSCREEN 0x00000004
#define DDFLG_GAMMA 0x00000008
#define DDFLG_PALETTE 0x00000010
// Common flags
#define DDFLG_FPS
0x00000020
// Flags for
DefineBackground()
#define DDFLG_SYSTEM_MEMORY
0x10000000
#define DDFLG_VIDEO_MEMORY 0x20000000
#define DDFLG_SCROLLING 0x40000000
#define DDFLG_SOLID 0x80000000
#define DDFLG_COLORKEY 0xF0000000
//
// Keys
// Graphics section
#define _S_KEY_WIDTH _T("ScreenWidth")
#define _S_KEY_HEIGHT _T("ScreenHeight")
#define _S_KEY_DEPTH _T("ScreenDepth")
#define _S_KEY_FPS _T("FPS")
#define _S_KEY_USEGAMMA _T("UseGamma")
#define _S_KEY_SHOWVIDMEM _T("ShowVidMem")
#include "Surface.h"
#include "DDFontEngine.h"
#include "Core.h"
//exports
DISPLAY_API
HRESULT disInit(HWND hWnd, UINT uFlags);
DISPLAY_API HRESULT disDefineBackground(COLORREF crColor);
DISPLAY_API HRESULT disDefineBackground(CString strBMPFile,
UINT nFlags);
DISPLAY_API HRESULT disBlt(CRect rectDestin, CRect rectSource,
CSurface *surSource);
DISPLAY_API HRESULT disBlt(CRect rectDestin, COLORREF crColor);
DISPLAY_API CSize disGetResolution();
DISPLAY_API HRESULT disUpdateBackground();
DISPLAY_API HRESULT disPresent();
DISPLAY_API HRESULT disDrawText(CString strText, int x = 0,
int y = 0,
COLORREF crColor = TC_YELLOW,
LPTSTR szFontName = NULL, int iHeight = 100);
DISPLAY_API HRESULT disIncreaseGamma(int d = 8);
DISPLAY_API HRESULT disDecreaseGamma(int d = 8);
DISPLAY_API HRESULT disFlash(DWORD dwFlashTime = 320, DWORD
dwUnflashTime = 500);
DISPLAY_API CDisplay* disGetDisplay();
DISPLAY_API HRESULT fntDrawText(int x, int y, CString csText,
CString csFont);
DISPLAY_API HRESULT fntCreateFont(CString csFontFile, CString
csFontName, COLORREF crColor = 0);
#endif // DISPLAY_COMMON_H
V tomto souboru také vidíte některé definice, které jsem uvedl dříve.
Common.cpp:
#include "stdafx.h"
#define DISPLAY_API __declspec(dllexport)
#include "Common.h"
CDisplay theDisplay;
CDDFontEngine theFontEngine;
//
// Exports function
DISPLAY_API HRESULT disInit(HWND hWnd, UINT uFlags)
{
return theDisplay.CreateFullScreenSystem(hWnd, uFlags);
}
DISPLAY_API HRESULT disDefineBackground(COLORREF crColor)
{
return theDisplay.DefineBackground(crColor);
}
DISPLAY_API HRESULT disDefineBackground(CString strBMPFile, UINT nFlags)
{
return theDisplay.DefineBackground(strBMPFile, nFlags);
}
DISPLAY_API HRESULT disBlt(CRect rectDestin, CRect rectSource, CSurface*
surSource)
{
return theDisplay.GetBackSurface()->Blt(rectDestin,
rectSource, surSource);
}
DISPLAY_API HRESULT disBlt(CRect rectDestin, COLORREF crColor)
{
return theDisplay.GetBackSurface()->Blt(rectDestin, crColor);
}
DISPLAY_API CSize disGetResolution()
{
return theDisplay.GetResolution();
}
DISPLAY_API HRESULT disUpdateBackground()
{
return theDisplay.UpdateBackground();
}
DISPLAY_API HRESULT disPresent()
{
theDisplay.Present();
return ERROR_SUCCESS;
}
DISPLAY_API HRESULT disIncreaseGamma(int d)
{
return theDisplay.IncrementGamma(d);
}
DISPLAY_API HRESULT disDecreaseGamma(int d)
{
return theDisplay.DecrementGamma(d);
}
DISPLAY_API CDisplay* disGetDisplay()
{
return &theDisplay;
}
DISPLAY_API HRESULT fntDrawText(int x, int y, CString csText, CString csFont)
{
return theFontEngine.DrawText(x, y, csText, csFont);
}
DISPLAY_API HRESULT fntCreateFont(CString csFontFile, CString csFontName,
COLORREF crColor)
{
return theFontEngine.CreateFont(csFontFile, csFontName,
crColor);
}
Nyní již stačí vložit hlavičkový soubor Common.h z projektu Display do hlavního projektu Game a můžete použít všechny výše definované funkce. Pokud by Vám něco nešlo nebo nefungovalo, podívejte se do příkladu, který najdete v sekci Downloads.
Výše uvedený postup exportu funkcí jsme probírali v minulých lekcích, takže se zde nebudu dále rozepisovat.
A je tu opět konec. Dnešní lekce byla celkem rozsáhlá, že? Přesto se nedověděli téměř nic nového. Jen aplikujeme to, co už známe.
V příští lekci zapojíme do našeho projektu knihovnu Input.dll, kterou již máme téměř připravenou. Stačí ji jen malinko modifikovat, abychom vykreslovali kurzor pomocí DirectDraw a můžeme to celé spustit. Poté začneme opět něco nového.
Těším se příště nashledanou.