DirectX (13.)

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.

13.1. Návrh třídy CDisplay

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

CreateFullscreenSystem(HWND, DWORD)

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

DefineBackground(COLORREF)

Následující dvě funkce definují pozadí dvěma způsoby. První verze definuje jednobarevné pozadí - jediný parametr určuje barvu.

HRESULT

DefineBackground(CString, DWORD)

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

UpdateBackground()

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

HasGammaSupport()

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.

13.2. Deklarace třídy

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 |.

13.3. Implementace funkcí

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.

13.4. Export funkcí Display.dll

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.

13.5. Závěr

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.

Jiří Formánek