32. DirectDraw I
  1. Další program, kterým se budeme zabývat je aplikace DirectDraw. Tento program vyžaduje nainstalovaný  běhový DirectDraw na počítači. To je součástí operačních systémů Windows 98 a Windows 2000 (NT verze 5.0). Pokud používáme Windows NT 4, musíme mít nainstalovaný alespoň Service Pack 3, který obsahuje DirectDraw 3.0. Zda DirectDraw je na našem systému zjistíme jednoduše tak, že se podíváme do adresáře Windows\System nebo do adresáře Winnt\System32 zda zde existují soubory DDRAW.DLL a DSOUND.DLL.

  2. DirectDraw umožňuje v prostředí Windows provádět velmi rychlé grafické operace (umožňuje přímý přístup do videopaměti na grafické kartě). Pro dobré výsledky je vhodné mít na grafické kartě alespoň 2 Mslabik paměti. DirectDraw umožňuje vytvořit vyrovnávací paměť, kreslit do ní a pak ji přesunout do viditelné oblasti v naší videopaměti. Pokud jsme v exklusivním režimu a máme dostatek videopaměti k uložení jak primární a zadní vrstvy ve video RAM, pak operace přepnutí není kopírovací procedura, ale jednoduchá změna adresy bloku paměti určující viditelnou oblast paměti grafické karty. Operace je velmi rychlá a proběhne v synchronizaci s obnovovací operací našeho monitoru. DirectDraw můžeme použít k provádění dokonalých animací.
    Kód naší aplikace je nejjednodušší možný program DirectDraw. Je to příklad DDX1 z SDK Microsoft DirectDraw, který je přepsán do prostředí založeném na formuláři (kód používá objekt TTimer namísto volání SetTimer a reaguje na události typu OnKeyDown namísto přímého zpracování zpráv WM_KEYDOWN; zvyšuje to čitelnost programu).
    Kód našeho programu má následující metody: Podívejme se na náš program. Začneme vývoj nové aplikace. Na formulář vložíme komponentu Timer. Do deklarace třídy formuláře přidáme následující soukromé složky (je zde i jedna metoda):
    LPDIRECTDRAW            lpDD;           // objekt DirectDraw
    LPDIRECTDRAWSURFACE     lpDDSPrimary;   // primární vrstva DirectDraw
    LPDIRECTDRAWSURFACE     lpDDSBack;      // zadní vrstva DirectDraw
    BOOL                    bActive;        // je aplikace aktivní?
    BYTE phase;
    AnsiString FrontMsg;
    AnsiString BackMsg;
    void __fastcall Start();
    Následuje výpis celého zdrojového souboru formuláře. Podívejte se jak jsou jednotlivé metody naprogramované a aplikaci vyzkoušejte.
    #include <vcl.h>
    #include <ddraw.h>
    #pragma hdrstop
    #include "Main.h"
    #pragma resource "*.dfm"
    #define TIMER_ID        1
    #define TIMER_RATE      500
    TForm1 *Form1;
    ///////////////////////////////////////
    // Konstruktor
    ///////////////////////////////////////
    __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
    {
      lpDD = NULL;
      phase = 0;
      bActive = False;
      FrontMsg = "Front buffer (F12 or Esc to quit)";
      BackMsg = "Back buffer (F12 or Esc to quit)";
    }
    ///////////////////////////////////////
    // Obsluha OnDestroy formuláře
    ///////////////////////////////////////
    void __fastcall TForm1::FormDestroy(TObject *Sender)
    {
      if(lpDD != NULL)
      {
        if(lpDDSPrimary != NULL)
        {
          lpDDSPrimary->Release();
          lpDDSPrimary = NULL;
        }
        lpDD->Release();
        lpDD = NULL;
      }
    }
    ///////////////////////////////////////
    // Metoda Start
    ///////////////////////////////////////
    void __fastcall TForm1::Start()
    {
      HRESULT ddrval;
      DDSURFACEDESC ddsd;
      DDSCAPS ddscaps;
      HDC DC;
      char buf[256];
      ddrval = DirectDrawCreate(NULL, &lpDD, NULL);
      if(ddrval == DD_OK)
      {
        // získání exklusivního režimu
        ddrval = lpDD->SetCooperativeLevel(Handle,
          DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
        if(ddrval == DD_OK)
        {
          ddrval = lpDD->SetDisplayMode(640, 480, 8);
          if(ddrval == DD_OK)
          {
            // vytvoření primární a zadní vrstvy
            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)
            {
              // získání ukazatele na zadní vrstvu
              ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
              ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps,
                                                    &lpDDSBack);
              if(ddrval == DD_OK)
              {
                // zobrazení textu
                if (lpDDSPrimary->GetDC(&DC) == DD_OK)
                {
                    SetBkColor(DC, RGB(0, 0, 255));
                    SetTextColor(DC, RGB(255, 255, 0));
                    TextOut(DC, 0, 0, FrontMsg.c_str(), FrontMsg.Length());
                    lpDDSPrimary->ReleaseDC(DC);
                }
                if (lpDDSBack->GetDC(&DC) == DD_OK)
                {
                    SetBkColor(DC, RGB(0, 0, 255));
                    SetTextColor(DC, RGB(255, 255, 0));
                    TextOut(DC, 0, 0, BackMsg.c_str(), BackMsg.Length());
                    lpDDSBack->ReleaseDC(DC);
                }
                // Povolení časovače
                Timer1->Enabled = True;
                bActive = True;
                return;
              }
            }
          }
        }
      }
      wsprintf(buf, "Direct Draw Init Failed (%08lx)\n", ddrval);
      MessageBox(Handle, buf, "ERROR", MB_OK);
      Close();
    }
    ///////////////////////////////////////
    // Obsluha stisku klávesy
    ///////////////////////////////////////
    void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,
            TShiftState Shift)
    {
       switch (Key)
       {
         case VK_F3:
           Start();
           break;
         case VK_ESCAPE:
         case VK_F12:
           Close();
           break;
       }
    }
    ///////////////////////////////////////
    // Obsluha OnPaint formuláře
    ///////////////////////////////////////
    void __fastcall TForm1::FormPaint(TObject *Sender)
    {
      RECT rc;
      SIZE size;
      char szMsg[]="Page Flipping Test: Press F3 to start, F12 or Esc to exit";
      if (!bActive)
      {
        HDC DC = GetDC(Handle);
        rc = GetClientRect();
        GetTextExtentPoint(DC, szMsg, lstrlen(szMsg), &size);
        SetBkColor(DC, RGB(0, 0, 0));
        SetTextColor(DC, RGB(255, 255, 0));
        TextOut(DC, (rc.right - size.cx)/2, (rc.bottom - size.cy)/2,
          szMsg, sizeof(szMsg)-1);
        ReleaseDC(Handle, DC);
      }
    }
    ///////////////////////////////////////
    // Obsluha OnTimer
    ///////////////////////////////////////
    void __fastcall TForm1::Timer1Timer(TObject *Sender)
    {
      HDC DC;
      if (lpDDSBack->GetDC(&DC) == DD_OK)
      {
        if(phase)
        {
          SetBkColor(DC, RGB(0, 0, 255));
          SetTextColor(DC, RGB(255, 255, 0));
          TextOut(DC, 0, 0, FrontMsg.c_str(), FrontMsg.Length());
          phase = 0;
        }
        else
        {
          SetBkColor(DC, RGB(0, 0, 255));
          SetTextColor(DC, RGB(0, 255, 255));
          TextOut(DC, 0, 0, BackMsg.c_str(), BackMsg.Length());
          phase = 1;
        }
        lpDDSBack->ReleaseDC(DC);
      }
      while(1)
      {
        HRESULT ddrval;
        ddrval = lpDDSPrimary->Flip(NULL, 0);
        if(ddrval == DD_OK) break;
        if(ddrval == DDERR_SURFACELOST)
        {
          ddrval = lpDDSPrimary->Restore();
          if(ddrval != DD_OK) break;
        }
        if(ddrval != DDERR_WASSTILLDRAWING) break;
      }
    }
    V aplikaci jsou přepínány jednotlivé vrstvy (obsahují pouze text).
  3. Další aplikace se podobá předcházející. Po obrazovce se v ní pohybuje zelený kruh. Začneme vývoj nové aplikace. Formulář nyní necháme prázdný. Jako soukromé složky formuláře nyní vložíme:

  4. LPDIRECTDRAW            lpDD;
    LPDIRECTDRAWSURFACE     lpDDSPrimary;
    LPDIRECTDRAWSURFACE     lpDDSBack;
    BOOL FActive;
    BYTE FPhase;
    RECT FShapeRect;
    int FValueAdd;
    AnsiString FrontMsg;
    AnsiString BackMsg;
    void __fastcall Start();
    MESSAGE void Run(TMessage &Message);
    void DrawShape(HDC &DC);
    Je zde také provedeno mapování zprávy:
    BEGIN_MESSAGE_MAP
      MESSAGE_HANDLER(WM_RUNAPP, TMessage, Run);
    END_MESSAGE_MAP(TForm);
    Následuje výpis zdrojového souboru formuláře (obsluhy OnDestroy, OnKeyDown a OnPaint jsou stejné jako v předchozí aplikaci a nejsou zde uvedeny):
    __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
    {
      lpDD = NULL;
      FPhase = 0;
      FActive = False;
      FrontMsg = "Front buffer (F12 or Esc to quit)";
      BackMsg = "Back buffer (F12 or Esc to quit)";
      FShapeRect = Rect(25, 25, 50, 50);
      FValueAdd = 2;
    }
    ///////////////////////////////////////
    // Spuštění programu
    ///////////////////////////////////////
    void TForm1::Run(TMessage &Message)
    {
      do {
        Timer1Timer(NULL);
        Application->ProcessMessages();
      } while (FActive);
    }
    ///////////////////////////////////////
    // Zprávy WM_TIMER
    ///////////////////////////////////////
    void __fastcall TForm1::Timer1Timer(TObject *Sender)
    {
      HDC DC;
      if (lpDDSBack->GetDC(&DC) == DD_OK)
      {
        DrawShape(DC);
        lpDDSBack->ReleaseDC(DC);
      }
      while(1)
      {
        HRESULT ddrval;
        ddrval = lpDDSPrimary->Flip(NULL, 0);
        if(ddrval == DD_OK) break;
        if(ddrval == DDERR_SURFACELOST)
        {
          ddrval = lpDDSPrimary->Restore();
          if(ddrval != DD_OK)
            break;
        }
        if(ddrval != DDERR_WASSTILLDRAWING)
        {
          FActive = False;
          Close();
        }
      }
    }
    /* Nezapomeňte, že máme dvě vrstvy. Když rušíme starý obrázek, musíme
       provést dvě iterace zpět a ne jednu. */
    void TForm1::DrawShape(HDC &DC)
    {
      HBRUSH Brush, OldBrush;
      Brush = CreateSolidBrush(RGB(0, 0, 0));
      OldBrush = SelectObject(DC, Brush);
      Ellipse(DC, FShapeRect.left - FValueAdd, FShapeRect.top,
        FShapeRect.right - FValueAdd, FShapeRect.bottom);
      SelectObject(DC, OldBrush);
      DeleteObject(Brush);
      FShapeRect.left += FValueAdd;
      FShapeRect.right += FValueAdd;
      if (FShapeRect.right > 637)
      {
        FValueAdd = -2;
      }
      if (FShapeRect.left < 3)
      {
        FValueAdd = 2;
      }
      Brush = CreateSolidBrush(RGB(0, 255, 0));
      OldBrush = SelectObject(DC, Brush);
      Ellipse(DC, FShapeRect.left, FShapeRect.top, FShapeRect.right, FShapeRect.bottom);
      SelectObject(DC, OldBrush);
      DeleteObject(Brush);
    }
    Metoda Start se liší pouze v několika málo řádcích. Namísto (na konci obsluhy):
                // Povolení časovače
                Timer1->Enabled = True;
                bActive = True;
                return;
    je zde nyní:
                // Povolení časovače
                PostMessage(Handle, WM_RUNAPP, 0, 0);
                return;
    Program vyzkoušejte.
  5. Další program ukazuje animaci více objektů pomocí DirectDraw. Po přepnutí do exklusivního režimu je nakresleno několik barevných objektů (kruhů a čtverců). Každý tvar se pohybuje po své vlastní dráze. Nyní jsou v programu dvě hlavní třídy: TDrawShape (reprezentuje jeden tvar zobrazený na obrazovce; každá instance má místo, tvar, směr a barvu - volání metody Draw přesune objekt na další místo na jeho dráze) a TForm1 (tento objekt řídí běh programu; obsahuje objekt TList ve kterém jsou uloženy jednotlivé tvary). Hlavičkový soubor má následující obsah:

  6. #ifndef MainH
    #define MainH
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    #include <Forms.hpp>
    #include <Menus.hpp>
    #include <ExtCtrls.hpp>
    #define WM_RUNAPP WM_USER
    #define CIRCLETYPE 0
    #define RECTTYPE 1
    class TDrawShape : public TObject
    {
    private:
      int FX;
      int FY;
      int FX1;
      int FY1;
      int FMoveValX;
      int FMoveValY;
      int FShapeType;
      RECT FPrevRect, FPrevRect2;
      TColor FColor;
      RECT __fastcall GetRect(){return Rect(FX,FY,FX1,FY1);}
      void __fastcall SetRect(RECT R)
      {
        FX = R.left; FY = R.top; FX1 = R.right; FY1 = R.bottom;
      }
    public:
      __fastcall TDrawShape(int ValX, int ValY)
        { FMoveValX = ValX; FMoveValY = ValY; }
      __fastcall TDrawShape(int ValX, int ValY, int X, int Y, int AType,
                            TColor AColor);
      void Move();
      void Draw(HDC &DC);
      __property RECT ShapeRect={read=GetRect, write=SetRect};
      __property int ShapeType={read=FShapeType, write=FShapeType};
      __property TColor Color={read=FColor, write=FColor};
    };
    class TForm1 : public TForm
    {
    __published:
      void __fastcall FormDestroy(TObject *Sender);
      void __fastcall FormKeyDown(TObject *Sender, WORD &Key,
        TShiftState Shift);
      void __fastcall FormPaint(TObject *Sender);
    private:
      LPDIRECTDRAW            lpDD;
      LPDIRECTDRAWSURFACE     lpDDSPrimary;
      LPDIRECTDRAWSURFACE     lpDDSBack;
      BOOL FActive;
      BYTE FPhase;
      RECT FShapeRect;
      TList *FShapeList;
      int FValueAdd;
      void BuildList();
      void DrawShape(HDC &DC);
      void PerformAction();
      MESSAGE void Run(TMessage &Message);
      void __fastcall Start();
    public:
      __fastcall TForm1(TComponent* Owner);
    BEGIN_MESSAGE_MAP
      MESSAGE_HANDLER(WM_RUNAPP, TMessage, Run);
    END_MESSAGE_MAP(TForm);
    };
    extern TForm1 *Form1;
    #endif
    Zdrojový soubor formuláře obsahuje:
    #include <vcl.h>
    #include <ddraw.h>
    #pragma hdrstop
    #include "Main.h"
    #pragma resource "*.dfm"
    TForm1 *Form1;
    __fastcall TDrawShape::TDrawShape(int ValX, int ValY, int X, int Y,
      int AType, TColor AColor)
    {
      FMoveValX = ValX;
      FMoveValY = ValY;
      ShapeRect = Rect(X, Y, X+25, Y+25);
      ShapeType = AType;
      Color = AColor;
    }
    void TDrawShape::Move()
    {
      FPrevRect2 = Rect(FPrevRect.left, FPrevRect.top, FPrevRect.right, FPrevRect.bottom);
      FPrevRect = Rect(FX, FY, FX1, FY1);
      FX += FMoveValX;
      FY += FMoveValY;
      FX1 += FMoveValX;
      FY1 += FMoveValY;
      if (FX1 > 637) FMoveValX = -2;
      if (FX < 3) FMoveValX = 2;
      if (FY1 > 477) FMoveValY = - 2;
      if (FY < 3) FMoveValY = 2;
    }
    void TDrawShape::Draw(HDC &DC)
    {
      HBRUSH Brush, OldBrush;
      Brush = CreateSolidBrush(RGB(0, 0, 0));
      OldBrush = SelectObject(DC, Brush);
      if (FShapeType==CIRCLETYPE)
        Ellipse(DC, FPrevRect2.left-1, FPrevRect2.top-1, FPrevRect2.right+1,
                FPrevRect2.bottom+1);
      else if (FShapeType == RECTTYPE)
        Rectangle(DC, FPrevRect2.left-1, FPrevRect2.top-1,
                  FPrevRect2.right+1, FPrevRect2.bottom+1);
      SelectObject(DC, OldBrush);
      DeleteObject(Brush);
      Move();
      Brush = CreateSolidBrush(FColor);
      OldBrush = SelectObject(DC, Brush);
      if (FShapeType==CIRCLETYPE) Ellipse(DC, FX, FY, FX1, FY1);
      else if (FShapeType == RECTTYPE) Rectangle(DC, FX, FY, FX1, FY1);
      SelectObject(DC, OldBrush);
      DeleteObject(Brush);
    }
    __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
    {
      lpDD = NULL;
      FPhase = 0;
      FActive = False;
      FShapeRect = Rect(25, 25, 50, 50);
      FValueAdd = 2;
      BuildList();
    }
    void TForm1::BuildList()
    {
      FShapeList = new TList();
      FShapeList->Add(new TDrawShape(-2, 2, 175, 175, CIRCLETYPE, clLime));
      FShapeList->Add(new TDrawShape(2, 2, 125, 125, RECTTYPE, clBlue));
      FShapeList->Add(new TDrawShape(2, -2, 200, 200, RECTTYPE, clYellow));
      FShapeList->Add(new TDrawShape(2, -2, 75, 75, CIRCLETYPE, clRed));
      FShapeList->Add(new TDrawShape(-2, 2, 325, 350, RECTTYPE, clPurple));
      FShapeList->Add(new TDrawShape(-2, -2, 275, 250, CIRCLETYPE, clFuchsia));
      FShapeList->Add(new TDrawShape(-2, 2, 125, 325, CIRCLETYPE, clTeal));
      FShapeList->Add(new TDrawShape(2, 2, 350, 175, RECTTYPE, clNavy));
      FShapeList->Add(new TDrawShape(2, -2, 150, 250, CIRCLETYPE, clOlive));
      FShapeList->Add(new TDrawShape(-2, 2, 225, 25, CIRCLETYPE, clSilver));
    }
    void __fastcall TForm1::FormDestroy(TObject *Sender)
    {
      TDrawShape *Shape;
      int i;
      for (i = 0; i < FShapeList->Count; i++)
      {
        Shape = (TDrawShape*)FShapeList->Items[i];
        delete Shape;
      }
      delete FShapeList;
      if(lpDD != NULL)
      {
        if(lpDDSPrimary != NULL)
        {
          lpDDSPrimary->Release();
          lpDDSPrimary = NULL;
        }
        lpDD->Release();
        lpDD = NULL;
      }
    }
    void __fastcall TForm1::Start()
    {
      HRESULT ddrval;
      DDSURFACEDESC ddsd;
      DDSCAPS ddscaps;
      char buf[256];
      ddrval = DirectDrawCreate(NULL, &lpDD, NULL);
      if(ddrval == DD_OK)
      {
        ddrval = lpDD->SetCooperativeLevel(Handle,
          DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN);
        if(ddrval == DD_OK)
        {
          ddrval = lpDD->SetDisplayMode(640, 480, 8);
          if(ddrval == DD_OK)
          {
            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)
            {
              ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
              ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps,
                                                    &lpDDSBack);
              if(ddrval == DD_OK)
              {
                PostMessage(Handle, WM_RUNAPP, 0, 0);
                return;
              }
            }
          }
        }
      }
      wsprintf(buf, "Direct Draw Init Failed (%08lx)\n", ddrval);
      MessageBox(Handle, buf, "ERROR", MB_OK);
      Close();
    }
    void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,
            TShiftState Shift)
    {
       switch (Key)
       {
         case VK_F3:
           FActive = True;
           Start();
           break;
         case VK_ESCAPE:
         case VK_F12:
           FActive = False;
           Close();
           break;
       }
    }
    void __fastcall TForm1::FormPaint(TObject *Sender)
    {
      RECT rc;
      SIZE size;
      char szMsg[] = "Page Flipping Test: Press F3 to start, F12 or Esc to exit";
      if (!FActive)
      {
        HDC DC = GetDC(Handle);
        rc = GetClientRect();
        GetTextExtentPoint(DC, szMsg, lstrlen(szMsg), &size);
        SetBkColor(DC, RGB(0, 0, 0));
        SetTextColor(DC, RGB(255, 255, 0));
        TextOut(DC, (rc.right - size.cx)/2, (rc.bottom - size.cy)/2,
          szMsg, sizeof(szMsg)-1);
        ReleaseDC(Handle, DC);
      }
    }
    void TForm1::Run(TMessage &Message)
    {
      do {
        PerformAction();
        Application->ProcessMessages();
      } while (FActive);
    }
    void TForm1::PerformAction()
    {
      HDC DC;
      if (lpDDSBack->GetDC(&DC) == DD_OK)
      {
        DrawShape(DC);
        lpDDSBack->ReleaseDC(DC);
      }
      while(1)
      {
        HRESULT ddrval;
        ddrval = lpDDSPrimary->Flip(NULL, 0);
        if(ddrval == DD_OK) break;
        if(ddrval == DDERR_SURFACELOST)
        {
          ddrval = lpDDSPrimary->Restore();
          if(ddrval != DD_OK) break;
        }
        if(ddrval != DDERR_WASSTILLDRAWING)
        {
          FActive = False;
          Close();
        }
      }
    }
    void TForm1::DrawShape(HDC &DC)
    {
      TDrawShape *Shape;
      int i;
      for (i = 0; i < FShapeList->Count; i++)
      {
        Shape = (TDrawShape*)FShapeList->Items[i];
        Shape->Draw(DC);
      }
    }
    Pokuste se pochopit, jak program pracuje a vyzkoušejte jej.
32. DirectDraw I