-
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.
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:
-
Konstruktor - provádí inicializaci proměnných.
-
OnDestroy formuláře - ruší vrstvy přímého kreslení vytvořené v metodě
Start.
-
Start - Volá DirectDrawCreate, která inicializuje DirectDraw
a vrací ukazatel na objekt DirectDraw. Dále voláme SetCooperativeLevel
pro přepnutí do exklusivního režimu, voláme SetDisplayMode k přepnutí
na 8 bitové rozlišení 640 x 480, voláme CreateSurface k vytvoření
primární vrstvy, voláme GetAttachedSurface k získání ukazatele na
zadní vrstvu, vybarvíme primární i zadní vrstvu černou barvou a umístíme
na nich text k jejích rozlišení a povolíme časovač.
-
OnKeyDown formuláře - reaguje na stisk klávesy, kterou uživatel
provede přepnutí do exklusivního režimu a zahájí demonstraci. Reaguje také
na stisk F12 nebo Esc k ukončení aplikace.
-
OnPaint formuláře - zobrazí text uprostřed obrazovky. Tato metoda
není volána, když program je v exklusivním režimu.
-
OnTimer časovače - Přepíná mezi primární a zadní vrstvou. Toto je
klíčová metoda, ve které je ukázáno přepínání mezi vrstvami, které provádíme
při animaci. Tato metoda je problematická, protože potřebujeme nejrychlejší
možné přepínání. Pro dokonalou animaci potřebujeme 25 přepnutí za sekundu.
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).
-
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:
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.
-
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:
#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.