Grafické aplikace ve Visual C++ (4.)


V dnešní lekci si konečně vytvoříme přední a zadní buffer a ukážeme si, jak přepsat funkci Run() tak, aby prohazovala oba povrchy. Výsledkem aplikace nebude nic světoborného, protože oba povrchy budou prázdné, ale i tak se posuneme o notný kus dál.

4.2. Vytvoření předního a zadního bufferu

Minule jsme si říkali, že zapisujeme data pouze do zadního bufferu (back buffer) tzn. že musíme získat ukazatel na tento buffer. Aby jsme tento ukazatel mohli získat musíme nejdříve vytvořit přední buffer (front buffer), protože zadní je součást tzv. flipovací smyčky nebo také řetězce (flipping chain). DirectDraw poskytuje funkci CreateSurface(), která je členskou funkcí objektu DirectDraw, který jsme vytvořili už minule.


HRESULT CreateSurface( LPDDSURFACEDESC lpDDSurfaceDesc,
                       LPDIRECTDRAWSURFACE FAR * lplpDDSurface, IUnknown FAR *pUnkOuter);


Tato funkce má tři následující parametry :

  • Ukazatel na strukturu typu DDSURFACEDESC2, ve které jsou uloženy parametry vytvářeného povrchu, viz níže.

  • Ukazatel na ukazatel na vlastního povrch

  • Integrace COM. Prozatím rovno NULL.

Vrací DD_OK pokud je úspěšná jinak některou z návratových hodnot DirectDraw.


Struktura DDSURFACEDESC2

Tato struktura popisuje vlastnosti nového povrch. Ať už vytváříte přední, či pomocný buffer, budete ji využívat. Obsahuje mnoho atributů, ale my jich zatím využijeme jen málo. Zde předkládám ty nejdůležitější:

  • DWORD dwSize ......................velikost struktury v bytech

  • DWORD dwFlags ........................udává, které členy jsou platné

  • DWORD dwWidth ........................šířka vytvářeného povrchu v pixelech

  • DWORD dwHeight .......................výška vytvářeného povrchu v pixelech

  • DWORD dwBackBufferCount...............počet zadních bufferů připojených k přednímu

  • DDSCAPS ddsCaps ..................další struktura určující další vlastnosti


Příklad vytvoření předního bufferu

Do třídy CControl přidejte dvě členské proměnné typu LPDIRECTDRAWSURFACE7 (IDirectDrawSurface7), jednu pojmenujte m_lpDDSFront a druhou m_lpDDSBack. Budou představovat ukazatele na přední a zadní povrch. Pomocí těchto ukazatelů budete pracovat s povrchy.
Dále přidejte následující kód do funkce DDInit().

  DDSURFACEDESC2 ddsd;

  ZeroMemory(&ddsd, sizeof(ddsd)); //1
  ddsd.dwSize = sizeof(ddsd); //2
  ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT; //3
  ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP |
          DDSCAPS_COMPLEX; //4
  ddsd.dwBackBufferCount = 1; //5 máme jeden zadní buffer !!!

  if((dwResult = m_lpDD->CreateSurface(&ddsd, &m_lpDDSFront, NULL)) != DD_OK) {
    TRACE("Cannot create front buffer due %d\n", dwResult);
    return dwResult;
  } //6

Nyní máme ukazatel na přední buffer : m_lpDDSPrimary
Postup :

  1. Vynulování paměti před inicializací struktury. Tento bod není nutný, ale obecně se doporučuje, protože tak můžete předejít chybám.

  2. Inicializace proměné dwSize na velikost struktury. Naproti bodu 1 je tento krok naprosto nutný. Atribut dwSize se objeví i u jiných struktur a vždy se musí inicializovat na správnou velikost struktury.

  3. Nastavení příznaků, které atributy struktury budou platné. V našem případě jsou to dssCaps a dwBackBufferCount

  4. Inicializace členu dssCaps Inicializace členu dwBackBufferCount. Určuje počet zadních bufferů, v našem případě máme jen jeden, ale můžeme jich použít více (triple buffer).

  5. Zavolání funkce CreateSurface() se zadanou strukturou. Konečné vytvoření povrchu, alokování paměti pro povrch a vrácení ukazatele.


K získání ukazatele na zadní buffer je zde ovšem jiná funkce, která počítá s tím, že je zadní buffer ve flippovací smyčce :

  HRESULT GetAttachedSurface( LPDDSCAPS2 lpDDSCaps,
                              LPDIRECTDRAWSURFACE7 FAR * lplpDDSurface);

Důležité je, že tato funkce je členská funkce rozhraní IDirectDrawSurface7 a nikoliv (jak tomu bylo dosud) IDirectDraw7.

Příklad vytvoření zadního bufferu

  DDSCAPS2 ddscaps;

  ZeroMemory(&ddscaps, sizeof(ddscaps));//1
  ddscaps.dwCaps = DDSCAPS_BACKBUFFER;//2

  if((dwResult = m_lpDDSFront->GetAttachedSurface(&ddscaps, &m_lpDDSBack)) != DD_OK) {
    TRACE("Cannot create back buffer due %d\n", dwResult);
    return dwResult;
  } //3

Postup :

  1. Vynulování paměti před inicializaci struktury. Viz výše.

  2. Nastavení, že vytvářený povrch bude zadní (back). Vidíte, že nepoužíváme strukturu DDSURFACEDESC2, ale jen její část DDSCAPS2.

  3. Získání ukazatele na povrch: m_lpDDSBack. Všimněte si, že nepoužíváme výše zmíněnou funkci CreateSurface(), je to z toho důvodu, že vytváříme buffer ve flippovací smyčce. Povrch je vlastně vytvořen již nahoře a nyní si žádáme jen o ukazatel na něj.


4.2. Přepsání funkce Run()

Minule jsme si ve třídě aplikace (CDirectDrawApp) připravili funkci Run(). V dnešní lekci ji upravíme tak, aby volala funkci UpdateFrame() třídy CControl, když smyčka zpráv nepřijímá žádné zprávy. Objekt CControl máte vytvořený buď přímo jako členskou proměnnou ve třídě aplikace nebo ji můžete vytvořit globálně a funkci UpdateFrame() vytvořit také jako globální funkci. Poté bude vypadat funkce Run() následovně.


  int CDirectDrawApp::Run()
  {
     MSG msg;
    
     while( 1 )
     {
       if( ::PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
       {
         if( !::GetMessage( &msg, NULL, 0, 0 ) )
         {
           return msg.wParam;
         }
         ::TranslateMessage(&msg);
         ::DispatchMessage(&msg);
       }
       else
       {
           m_theControl.UpdateFrame();
      }
    }
  }

Nesmíte zapomenout vymazat původní jeden řádek této funkce. Funkce nyní bude volat funkci UpdateFrame() jak nejrychleji to půjde – pokud nemá co na práci.

4.3. Funkce UpdateFrame()


Jako poslední v této lekci ještě něco napíšeme do funkce UpdateFrame(), aby se povrchy skutečně prohazovaly.

  void CControl::UpdateFrame()
  {      
       if(m_lpDDSFront) {
           m_lpDDSFront->Flip(NULL, DDFLIP_WAIT);
       }
  }

Zatím zde nemáme žádné ochrany proti ztracení povrchu atd. Druhý parametr funkce Flip() znamená, že funkce počká až se ostatní blitovací akce dokončí. Příště funkci dále rozšíříme.

4.4. Závěr

Pokud dnešní aplikaci zkompilujete, mělo by se vám přepnout rozlišení a obrazovka je zatím bílá. Prohazování povrchů probíhá, ale není vidět jelikož mají oba buffery stejný obsah. Někdy se může stát, že v zadním bufferu zbudou nějaké zbytky oken z Windows, protože při vytváření povrchu se může alokovat paměť, kterou předtím používaly Windows a nechaly ji "prasácky" nevymazanou. Příště si vytvoříme pomocné buffery (off-screen surfaces) v systémové paměti, do kterých uložíme bitmapy, které potom budeme vykreslovat do zadního bufferu.

Těším se příště nashledanou.

Jiří Formánek