Tento článek popisuje vytvoření OpenGL okna pod operačním systémem MS Windows ve vývojovém prostředí Borland Delphi. Já osobně používám Delphi verze 7, ale ani v nižších verzích by neměl být problém vytvořený kód zkompilovat a spustit. Z větší části se jedná o přepis prvního NeHe Tutoriálu z jazyka C/C++ do Pascalu. Takže směle do toho...
Začneme vytvořením nového projektu. V nabídce File - New vybereme konzolovou aplikaci (Console Application). Postup se může v různých verzích Delphi mírně lišit. Ve skutečnosti konzolovou aplikaci vytvářet nebudeme, ale vygenerovaný kód je nejblíže tomu, co potřebujeme.
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
begin
{ TODO -oUser -cConsole Main : Insert code here }
end.
Odstraníme zbytky kódu, které by nám překážely a dostaneme:
program Project1;
begin
end.
Základ tedy máme vytvořen. Na začátek přidáme jednotky, které budeme dále v kódu používat.
uses
Windows,
Messages,
OpenGL;
Dále deklarujeme globální proměnné. Komentáře, myslím, hovoří za vše, takže se zastavím jen u prvních dvou. Každý OpenGL program je spojen s Rendering Contextem. Rendering Context říká, která spojení volá OpenGL, aby se spojilo s Device Context (kontext zařízení). Nám stačí vědět, že OpenGL Rendering Context je definován jako HGLRC. Aby program mohl kreslit do okna, potřebujeme vytvořit Device Context. Ve Windows je Device Context definován jako HDC. Device Context napojí okno na GDI (grafické rozhraní).
var
h_Rc: HGLRC;// Trvalý Rendering Context
h_Dc: HDC;// Privátní GDI Device Context
h_Wnd: HWND;// Obsahuje Handle našeho okna
keys: array [0..255] of BOOL;// Pole pro ukládání vstupu z klávesnice
Active: bool = true;// Ponese informaci o tom, zda je okno aktivní
FullScreen: bool = true;// Ponese informaci o tom, zda je program ve fullscreenu
Následující procedura se volá vždy, když uživatel mění velikost okna. I když nejste schopni změnit velikost okna (například ve fullscreenu), bude tato funkce volána alespoň jednou, aby nastavila perspektivní pohled při spuštění programu. Velikost OpenGL scény se bude měnit v závislosti na šířce a výšce okna, ve kterém je zobrazena.
procedure ReSizeGLScene(Width: GLsizei; Height: GLsizei);// Změna velikosti a inicializace OpenGL okna
begin
if Height = 0 then// Zabezpečení proti dělení nulou
Height := 1;// Nastaví výšku na jedna
glViewport(0, 0, Width, Height);// Resetuje aktuální nastavení
Nastavíme obraz na perspektivní projekci, to znamená, že vzdálenější objekty budou, stejně jako v reálném světě, menší. Příkaz glMatrixMode(GL_PROJECTION) ovlivní formu obrazu, díky ní budeme moci následně definovat, jak výrazná bude perspektiva. Vytvoříme realisticky vypadající scénu. Funkce glLoadIdentity resetuje matici, to znamená, že ji nastaví do výchozího stavu. Perspektiva je vypočítána s úhlem pohledu 45 stupňů a je založena na výšce a šířce okna. Číslo 1.0 je počáteční a 100.0 koncový bod, který říká jak hluboko do obrazovky můžeme kreslit. glMatrixMode(GL_MODELVIEW) oznamuje, že forma pohledu bude znovu změněna.
glMatrixMode(GL_PROJECTION);// Zvolí projekční matici
glLoadIdentity;// Reset matice
gluPerspective(45.0, Width/Height, 1.0, 100.0);// Výpočet perspektivy
glMatrixMode(GL_MODELVIEW);// Zvolí matici Modelview
glLoadIdentity;// Reset matice
end;
Nastavíme vše potřebné pro OpenGL. Definujeme černé pozadí, zapneme depth buffer, aktivujeme smooth shading (vyhlazené stínování), atd. Tato funkce se volá až po vytvoření okna, protože musí být dostupný rendering kontext.
function InitGL: bool;// Všechno nastavení OpenGL
begin
Následující řádek povolí stínování, aby se barvy na polygonech pěkně promíchaly.
glShadeModel(GL_SMOOTH);// Míchání barev na polygonech
Nastavíme barvu pozadí prázdné obrazovky. Rozsah barev se určuje ve stupnici od nuly do jedné. 0.0 je nejtmavší a 1.0 je nejsvětlejší. První parametr ve funkci glClearColor je intenzita červené barvy, druhý zelené a třetí modré. Čím bližší je hodnota barvy 1.0, tím světlejší složka barvy bude. Poslední parametr je hodnota alpha (průhlednost). Když budeme čistit obrazovku, tak se o průhlednost starat nemusíme. Nyní ji necháme na 0.5.
glClearColor(0.0, 0.0, 0.0, 0.5);// Černé pozadí
Následující tři řádky ovlivňují depth buffer. Depth buffer si můžete představit jako vrstvy/hladiny obrazovky. Obsahuje informace, o tom jak hluboko jsou zobrazované objekty.
glClearDepth(1.0);// Nastavení hloubkového bufferu
glEnable(GL_DEPTH_TEST);// Povolí hloubkové testování
glDepthFunc(GL_LEQUAL);// Typ hloubkového testování
Dále oznámíme, že chceme použít nejlepší korekce perspektivy. Jen nepatrně to sníží výkon, ale zlepší se vzhled celé scény.
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);// Nejlepší perspektivní korekce
Nakonec vrátíme true. Když budeme chtít zjistit, jestli inicializace proběhla bez problémů, můžeme zkontrolovat, zda funkce vrátila hodnotu true nebo false. Můžete přidat vlastní kód, který vrátí false, když se inicializace nezdaří - např. při neúspěšném loadingu textur.
Result := true;// Inicializace proběhla v pořádku
end;
Do této funkce umístíme všechno vykreslování. Jediné co nyní uděláme, je smazání obrazovky na barvu, pro kterou jsme se rozhodli, také vymažeme obsah hloubkového bufferu a resetujeme scénu. Zatím nebudeme nic kreslit. Příkaz Result := true nám říká, že při kreslení nenastaly žádné problémy.
function DrawGLScene: bool;// Vykreslování
begin
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);// Smaže obrazovku a hloubkový buffer
glLoadIdentity;// Reset matice
Result := true;// Vykreslení proběhlo v pořádku
end;
Následující část kódu je volána těsně před koncem programu. Úkolem funkce KillGLWindow je uvolnění renderovacího kontextu, kontextu zařízení a handle okna. Přidal jsem zde nezbytné kontrolování chyb. Když není program schopen něco uvolnit, oznámí chybu, která říká co selhalo. Usnadní tak složité hledání problémů.
procedure KillGLWindow;// Zavírání okna
begin
Zjistíme, zda je program ve fullscreenu. Pokud ano, tak ho přepneme zpět do systému. Mohli bychom vypnout okno před opuštěním fullscreenu, ale na některých grafických kartách tím způsobíme problémy a systém by se mohl zhroutit.
if FullScreen then// Jsme ve fullscreenu?
begin
K návratu do původního nastavení systému používáme funkci ChangeDisplaySettings(devmode(nil^),0). Jako první parametr zadáme devmode(nil^) a jako druhý 0 - použijeme hodnoty uložené v registrech Windows (původní rozlišení, barevnou hloubku, obnovovací frekvenci, atd.). Po přepnutí zviditelníme kurzor.
ChangeDisplaySettings(devmode(nil^), 0);// Přepnutí do systému
showcursor(true);// Zobrazí kurzor myši
end;
Zkontrolujeme, zda máme renderovací kontext. Když ne, program přeskočí část kódu pod ním, který kontroluje, zda máme kontext zařízení.
if h_rc <> 0 then// Máme rendering kontext?
begin
Zjistíme, zda můžeme odpojit h_RC od h_DC.
if (not wglMakeCurrent(h_Dc, 0)) then// Jsme schopni oddělit kontexty?
Pokud nejsme schopni uvolnit DC a RC, zobrazíme zprávu, že DC a RC nelze uvolnit. Nula v parametru znamená, že informační okno nemá žádného rodiče. Text ihned za 0 je text, který se vypíše do zprávy. Další parametr definuje text lišty. Parametr MB_OK znamená, že chceme mít na chybové zprávě jen jedno tlačítko s nápisem OK. MB_ICONERROR zobrazí ikonu.
MessageBox(0, 'Release of DC and RC failed.', 'Shutdown Error', MB_OK or MB_ICONERROR);
Zkusíme vymazat Rendering Context. Pokud se pokus nezdaří, opět se zobrazí chybová zpráva. Nakonec nastavíme h_RC a 0.
if (not wglDeleteContext(h_Rc)) then// Jsme schopni smazat RC?
begin
MessageBox(0, 'Release of Rendering Context failed.', 'Shutdown Error', MB_OK or MB_ICONERROR);
h_Rc := 0;// Nastaví hRC na 0
end;
end;
Zjistíme, zda má program kontext zařízení. Když ano odpojíme ho. Pokud se odpojení nezdaří, zobrazí se chybová zpráva a h_DC bude nastaven na 0.
if (h_Dc = 1) and (releaseDC(h_Wnd,h_Dc) <> 0) then// Jsme schopni uvolnit DC
begin
MessageBox(0, 'Release of Device Context failed.', 'Shutdown Error', MB_OK or MB_ICONERROR);
h_Dc := 0;// Nastaví hDC na 0
end;
Nyní zjistíme, zda máme handle okna a pokud ano, pokusíme se odstranit okno použitím funkce DestroyWindow(h_Wnd). Pokud se pokus nezdaří, zobrazí se chybová zpráva a h_Wnd bude nastaveno na 0.
if (h_Wnd <> 0) and (not destroywindow(h_Wnd)) then// Jsme schopni odstranit okno?
begin
MessageBox(0, 'Could not release hWnd.', 'Shutdown Error', MB_OK or MB_ICONERROR);
h_Wnd := 0;// Nastaví hWnd na 0
end;
Odregistrováním třídy okna oficiálně uzavřeme okno a předejdeme zobrazení chybové zprávy "Windows Class already registered" při opětovném spuštění programu.
if (not UnregisterClass('OpenGL', hInstance)) then// Jsme schopni odregistrovat třídu okna?
begin
MessageBox(0, 'Could Not Unregister Class.', 'SHUTDOWN ERROR', MB_OK or MB_ICONINFORMATION);
end;
end;
Další část kódu má na starosti vytvoření OpenGL okna. Jak si můžete všimnout funkce vrací boolean a přijímá 5 parametrů v pořadí: název okna, šířka okna, výška okna, barevná hloubka, fullscreen (pokud je parametr true program poběží ve fullscreenu, pokud bude false program poběží v okně). Vracíme boolean, abychom věděli, zda bylo okno úspěšně vytvořeno.
function CreateGlWindow(title:Pchar; width, height, bits:integer; FullScreenflag:bool) : boolean stdcall;
Za chvíli požádáme Windows, aby pro nás našel pixel format, který odpovídá tomu, který chceme. Toto číslo uložíme do proměnné PixelFormat.
var
Pixelformat: GLuint;// Ukládá formát pixelů
Wc bude použijeme k uchování informací o struktuře Windows Class. Změnou hodnot jednotlivých položek lze ovlivnit vzhled a chování okna. Před vytvořením samotného okna se musí zaregistrovat nějaká struktura pro okno.
wc: TWndclass;// Struktura Windows Class
DwExStyle a dwStyle ponesou informaci o normálních a rozšířených informacích o oknu.
dwExStyle: dword;// Rozšířený styl okna
dwStyle: dword;// Styl okna
pfd: pixelformatdescriptor;// Nastavení formátu pixelů
dmScreenSettings: Devmode;// Mód zařízení
h_Instance: hinst;// Instance okna
WindowRect: TRect;// Obdélník okna
begin
WindowRect.Left := 0;// Nastaví levý okraj na nulu
WindowRect.Top := 0;// Nastaví horní okraj na nulu
WindowRect.Right := width;// Nastaví pravý okraj na zadanou hodnotu
WindowRect.Bottom := height;// Nastaví spodní okraj na zadanou hodnotu
Získáme instanci pro okno.
h_instance := GetModuleHandle(nil);// Získá instanci okna
Přiřadíme globální proměnné fullscreen, hodnotu fullscreenflag. Takže pokud naše okno poběží ve fullscreenu, proměnná fullscreen se bude rovnat true.
FullScreen := FullScreenflag;// Nastaví proměnnou fullscreen na správnou hodnotu
Definujeme Window Class. CS_HREDRAW a CS_VREDRAW donutí naše okno, aby se překreslilo, kdykoliv se změní jeho velikost. CS_OWNDC vytvoří privátní kontext zařízení. To znamená, že není sdílen s ostatními aplikacemi. WndProc je procedura okna, která sleduje příchozí zprávy pro program. Žádná extra data pro okno nepoužíváme, takže do dalších dvou položek přiřadíme nulu. Nastavíme instanci a hIcon, což je ikona, kterou používá náš program a pro kurzor myši používáme standardní šipku. Barva pozadí nás nemusí zajímat (to zařídíme v OpenGL). Nechceme, aby okno mělo menu, takže i tuto hodnotu nastavíme na nil. Jméno třídy může být libovolné.
with wc do
begin
style := CS_HREDRAW or CS_VREDRAW or CS_OWNDC;// Překreslení při změně velikosti a vlastní DC
lpfnWndProc := @WndProc;// Definuje proceduru okna
cbClsExtra := 0;// Žádná extra data
cbWndExtra := 0;// Žádná extra data
instance := h_Instance;// Instance
hIcon := LoadIcon(0, IDI_WINLOGO);// Standardní ikona
hCursor := LoadCursor(0, IDC_ARROW);// Standardní kurzor myši
hbrBackground := 0;// Pozadí není nutné
lpszMenuName := nil;// Nechceme menu
lpszClassName := 'OpenGl';// Jméno třídy okna
end;
Zaregistrujeme právě definovanou třídu okna. Když nastane chyba, zobrazí se chybové hlášení. Zmáčknutím tlačítka OK se program ukončí.
if RegisterClass(wc) = 0 then// Registruje třídu okna
begin
MessageBox(0, 'Failed To Register The Window Class.', 'Error', MB_OK or MB_ICONERROR);
Result := false;// Při chybě vrátí false
exit;
end;
Nyní si zjistíme, zda má program běžet ve fullscreenu nebo v okně.
if FullScreen then// Budeme ve fullscreenu?
begin
S přepínáním do fullscreenu mívají lidé mnoho problémů. Je zde pár důležitých věcí, na které si musíte dávat pozor. Ujistěte se, že šířka a výška, kterou používáte ve fullscreenu, je totožná s tou, kterou chcete použít v okně. Další věc je hodně důležitá. Musíte přepnout do fullscreenu předtím, než vytvoříte okno. V tomto kódu se o rovnost výšky a šířky nemusíte starat, protože velikost ve fullscreenu i v okně budou stejné.
ZeroMemory(@dmScreenSettings, sizeof(dmScreenSettings));// Vynulování paměti
with dmScreensettings do
begin
dmSize := sizeof(dmScreenSettings);// Velikost struktury Devmode
dmPelsWidth := width;// Šířka okna
dmPelsHeight := height;// Výška okna
dmBitsPerPel := bits;// Barevná hloubka
dmFields := DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT;
end;
Funkce ChangeDisplaySettings se pokusí přepnout do módu, který je uložen v dmScreenSettings. Použiji parametr CDS_FULLSCREEN, protože odstraní pracovní lištu ve spodní části obrazovky a nepřesune nebo nezmění velikost okna při přepínání z fullscreenu do systému nebo naopak.
// Pokusí se použít právě definované nastavení
if (ChangeDisplaySettings(dmScreenSettings, CDS_FULLSCREEN)) <> DISP_CHANGE_SUCCESSFUL THEN
begin
Pokud právě vytvořený fullscreen mód neexistuje, zobrazí se chybová zpráva s nabídkou spuštění v okně nebo opuštění programu.
// Nejde-li fullscreen, může uživatel spustit program v okně nebo ho opustit
if MessageBox(0, 'This FullScreen Mode Is Not Supported. Use Windowed Mode Instead?', 'NeHe GL', MB_YESNO or MB_ICONEXCLAMATION) = IDYES then
Když se uživatel rozhodne pro běh v okně, do proměnné fullscreen se přiřadí false a program pokračuje dále.
FullScreen := false// Běh v okně
else
begin
Pokud se uživatel rozhodl pro ukončení programu, zobrazí se uživateli zpráva, že program bude ukončen. Bude vrácena hodnota false, která našemu programu říká, že pokus o vytvoření okna nebyl úspěšný a potom se program ukončí.
// Zobrazí uživateli zprávu, že program bude ukončen
MessageBox(0, 'Program Will Now Close.', 'Error', MB_OK or MB_ICONERROR);
Result := false;// Vrátí FALSE
exit;
end;
end;
end;
Protože pokus o přepnutí do fullscreenu může selhat nebo se uživatel může rozhodnout pro běh programu v okně, zkontrolujeme ještě jednou, zda je proměnná fullscreen true nebo false. Až poté nastavíme typ obrazu.
if FullScreen then// Jsme stále ve fullscreenu?
begin
Pokud jsme stále ve fullscreenu nastavíme rozšířený styl na WS_EX_APPWINDOW, což donutí okno, aby překrylo pracovní lištu. Styl okna určíme na WS_POPUP. Tento typ okna nemá žádné okraje, což je pro fullscreen výhodné. Nakonec vypneme kurzor myši. Pokud váš program není interaktivní, je většinou vhodnější ve fullscreenu kurzor vypnout.
dwExStyle := WS_EX_APPWINDOW;// Rozšířený styl okna
dwStyle := WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN;// Styl okna
Showcursor(false);// Skryje kurzor
end
else
begin
Pokud místo fullscreenu používáme běh v okně, nastavíme rozšířený styl na WS_EX_WINDOWEDGE. To dodá oknu trochu 3D vzhledu. Styl nastavíme na WS_OVERLAPPEDWINDOW místo na WS_POPUP. WS_OVERLAPPEDWINDOW vytvoří okno s lištou, okraji, tlačítky pro minimalizaci a maximalizaci. Budeme moci měnit velikost.
dwExStyle := WS_EX_APPWINDOW or WS_EX_WINDOWEDGE;// Rozšířený styl okna
dwStyle := WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN;// Styl okna
end;
Přizpůsobíme okno podle stylu, který jsme vytvořili. Přizpůsobení udělá okno v takovém rozlišení, jaké požadujeme. Normálně by okraje překrývaly část okna. S použitím příkazu AdjustWindowRectEx žádná část OpenGL scény nebude překryta okraji, místo toho bude okno uděláno o málo větší, aby se do něj vešly všechny pixely tvořící okraj okna. Ve fullscreenu tato funkce nemá žádný efekt.
AdjustWindowRectEx(WindowRect, dwStyle, false, dwExStyle);// Přizpůsobení velikosti okna
Vytvoříme okno a zkontrolujeme, zda bylo vytvořeno správně. Použijeme funkci CreateWindowEx se všemi parametry, které vyžaduje. Rozšířený styl, který jsme se rozhodli použít, jméno třídy (musí být stejné jako to, které jste použili, když jste registrovali Window Class), titulek okna, styl okna, levá horní pozice okna (0,0 je nejjistější), šířka a výška. Nechceme mít rodičovské okno ani menu, takže nastavíme tyto parametry na 0. Zadáme instanci okna a konečně přiřadíme nil na místo posledního parametru.
// Vytvoření okna
H_wnd := CreateWindowEx(dwExStyle,// Rozšířený styl
'OpenGl',// Jméno třídy
Title,// Titulek
dwStyle,// Definovaný styl
0, 0,// Pozice
WindowRect.Right-WindowRect.Left,// Výpočet šířky
WindowRect.Bottom-WindowRect.Top,// Výpočet výšky
0,// Žádné rodičovské okno
0,// Bez menu
hinstance,// Instance
nil);// Nepředat nic do WM_CREATE
Dále zkontrolujeme, zda bylo vytvořeno. Pokud bylo, h_Wnd obsahuje handle tohoto okna. Když se vytvoření okna nepovede, kód zobrazí chybovou zprávu a program se ukončí.
if h_Wnd = 0 then// Pokud se okno nepodařilo vytvořit
begin
KillGlWindow;// Zruší okno
MessageBox(0, 'Window creation error.', 'Error', MB_OK or MB_ICONEXCLAMATION);
Result := false;// Vrátí chybu
exit;
end;
Vybereme Pixel Format, který podporuje OpenGL, dále zvolíme double buffering a RGBA (červená, zelená, modrá, průhlednost). Pokusíme se najít formát, který odpovídá tomu, pro který jsme se rozhodli (16 bitů, 24 bitů, 32 bitů). Nakonec nastavíme Z-Buffer. Ostatní parametry se nepoužívají nebo pro nás nejsou důležité.
with pfd do// Oznámíme Windows jak chceme vše nastavit
begin
nSize := SizeOf(PIXELFORMATDESCRIPTOR);// Velikost struktury
nVersion := 1;// Číslo verze
dwFlags := PFD_DRAW_TO_WINDOW// Podpora okna
or PFD_SUPPORT_OPENGL// Podpora OpenGL
or PFD_DOUBLEBUFFER;// Podpora Double Bufferingu
iPixelType := PFD_TYPE_RGBA;// RGBA Format
cColorBits := bits;// Zvolí barevnou hloubku
cRedBits := 0;// Bity barev ignorovány
cRedShift := 0;
cGreenBits := 0;
cBlueBits := 0;
cBlueShift := 0;
cAlphaBits := 0;// Žádný alpha buffer
cAlphaShift := 0;// Ignorován Shift bit
cAccumBits := 0;// Žádný akumulační buffer
cAccumRedBits := 0;// Akumulační bity ignorovány
cAccumGreenBits := 0;
cAccumBlueBits := 0;
cAccumAlphaBits := 0;
cDepthBits := 16;// 16-bitový hloubkový buffer (Z-Buffer)
cStencilBits := 0;// Žádný Stencil Buffer
cAuxBuffers := 0;// Žádný Auxiliary Buffer
iLayerType := PFD_MAIN_PLANE;// Hlavní vykreslovací vrstva
bReserved := 0;// Rezervováno
dwLayerMask := 0;// Maska vrstvy ignorována
dwVisibleMask := 0;
dwDamageMask := 0;
end;
Pokud nenastaly problémy během vytváření okna, pokusíme se připojit kontext zařízení. Pokud se to nepodaří, zobrazí se chybové hlášení a program se ukončí.
h_Dc := GetDC(h_Wnd);// Zkusí připojit kontext zařízení
if h_Dc = 0 then// Podařilo se připojit kontext zařízení?
begin
KillGLWindow;// Zavře okno
MessageBox(0, 'Cant create a GL device context.', 'Error', MB_OK or MB_ICONEXCLAMATION);
Result := false;// Ukončí program
exit;
end;
Když získáme kontext zařízení, pokusíme se najít odpovídající Pixel Format. Když ho Windows nenajde, zobrazí se chybová zpráva a program se ukončí.
PixelFormat := ChoosePixelFormat(h_Dc, @pfd);// Zkusí najít Pixel Format
if (PixelFormat = 0) then// Podařilo se najít Pixel Format?
begin
KillGLWindow;// Zavře okno
MessageBox(0, 'Cant Find A Suitable PixelFormat.', 'Error', MB_OK or MB_ICONEXCLAMATION);
Result := false;// Ukončí program
exit;
end;
Když Windows najde odpovídající formát, tak se ho pokusíme nastavit. Pokud při pokusu o nastavení nastane chyba, opět se zobrazí chybové hlášení a program se ukončí.
if (not SetPixelFormat(h_Dc, PixelFormat, @pfd)) then// Podařilo se nastavit Pixel Format?
begin
KillGLWindow;// Zavře okno
MessageBox(0, 'Cant set PixelFormat.', 'Error', MB_OK or MB_ICONEXCLAMATION);
Result := false;// Ukončí program
exit;
end;
Pokud byl nastaven Pixel Format správně, pokusíme se získat Rendering Context. Pokud ho nezískáme, program zobrazí chybovou zprávu a ukončí se.
h_Rc := wglCreateContext(h_Dc);// Podařilo se vytvořit Rendering Context?
if (h_Rc = 0) then
begin
KillGLWindow;// Zavře okno
MessageBox(0, 'Cant create a GL rendering context.', 'Error', MB_OK or MB_ICONEXCLAMATION);
Result := false;// Ukončí program
exit;
end;
Pokud nenastaly žádné chyby při vytváření jak Device Context, tak Rendering Context, vše co musíme nyní udělat je aktivovat Rendering Context. Pokud ho nebudeme moci aktivovat, zobrazí se chybová zpráva a program se ukončí.
if (not wglMakeCurrent(h_Dc, h_Rc)) then// Podařilo se aktivovat Rendering Context?
begin
KillGLWindow;// Zavře okno
MessageBox(0, 'Cant activate the GL rendering context.', 'Error', MB_OK or MB_ICONEXCLAMATION);
Result := false;// Ukončí program
exit;
end;
Pokud bylo okno vytvořeno, zobrazíme ho na obrazovce, nastavíme ho, aby bylo v popředí (vyšší priorita) a pak nastavíme zaměření na toto okno. Zavoláme funkci ResizeGLScene s parametry odpovídajícími výšce a šířce okna, abychom správně nastavili perspektivu OpenGL.
ShowWindow(h_Wnd, SW_SHOW);// Zobrazení okna
SetForegroundWindow(h_Wnd);// Do popředí
SetFOcus(h_Wnd);// Zaměří fokus
ReSizeGLScene(width, height);// Nastavení perspektivy OpenGL scény
Konečně se dostáváme k volání výše definované funkce InitGL, ve které nastavujeme osvětlení, loading textur a cokoliv jiného, co je potřeba. Ve funkci InitGL můžete vytvořit svou vlastní kontrolu chyb a vracet true, když vše proběhne bez problémů, nebo false, pokud nastanou nějaké problémy. Například, nastane-li chyba při nahrávání textur, vrátíte false, jako znamení, že něco selhalo a program se ukončí.
if not InitGL then// Inicializace okna
begin
KillGLWindow;// Zavře okno
MessageBox(0, 'Initialization failed.', 'Error', MB_OK or MB_ICONEXCLAMATION);
Result := false;// Ukončí program
exit;
end;
Pokud jsme se dostali až takhle daleko, můžeme konstatovat, že vytvoření okna proběhlo bez problémů.
Result := true;// Vše proběhlo v pořádku
end;
Nyní se vypořádáme se systémovými zprávami pro okno. Když máme zaregistrovánu naši Window Class, můžeme podstoupit k části kódu, která má na starosti zpracování zpráv.
function WndProc(hWnd: HWND;// Handle okna
message: UINT;// Zpráva pro okno
wParam: WPARAM;// Doplňkové informace
lParam: LPARAM):// Doplňkové informace
LRESULT; stdcall;
begin
Po příchodu WM_SYSCOMMAND (systémový příkaz) porovnáme wParam s možnými stavy, které mohly nastat. Když je wParam WM_SCREENSAVE nebo SC_MONITORPOWER, snaží se systém zapnout spořič obrazovky nebo přejít do úsporného režimu. Jestliže vrátíme 0 zabráníme systému, aby tyto akce provedl.
if message = WM_SYSCOMMAND then// Systémový příkaz
begin
case wParam of// Typ systémového příkazu
SC_SCREENSAVE,// Pokus o zapnutí šetřiče obrazovky
SC_MONITORPOWER:// Pokus o přechod do úsporného režimu?
begin
result := 0;// Zabrání obojímu
exit;
end;
end;
end;
Napíšeme mapu zpráv. Program se bude větvit podle proměnné message, která obsahuje jméno zprávy.
case message of// Větvení podle příchozí zprávy
Po příchodu WM_ACTIVE zkontrolujeme, zda je okno stále aktivní. Pokud bylo minimalizováno, nastavíme hodnotu active na false. Pokud je okno aktivní, proměnná active bude mít hodnotu true.
WM_ACTIVATE:// Změna aktivity okna
begin
if (Hiword(wParam) = 0) then// Zkontroluje zda není minimalizované
active := true// Program je aktivní
else
active := false;// Program není aktivní
Result := 0;// Návrat do hlavního cyklu programu
end;
Přišlo-li WM_CLOSE, bylo okno zavřeno. Pošleme tedy zprávu pro opuštění programu, která přeruší vykonávání hlavního cyklu. Proměnnou done (ve WinMain) nastavíme na true, hlavní smyčka se přeruší a program se ukončí.
WM_CLOSE:// Povel k ukončení programu
begin
PostQuitMessage(0);// Pošle zprávu o ukončení
result := 0;// Návrat do hlavního cyklu programu
end;
Pokud byla stisknuta klávesa, můžeme podle wParam zjistit, která z nich to byla. Potom přiřadíme do odpovídající buňky v poli keys[] true. Díky tomu můžeme kdykoli v programu zjistit, která klávesa je právě stisknutá. Tímto způsobem lze zkontrolovat i stisk více kláves najednou.
WM_KEYDOWN:// Stisk klávesy
begin
keys[wParam] := TRUE;// Oznámí to programu
result := 0;// Návrat do hlavního cyklu programu
end;
Pokud byla naopak klávesa uvolněna, uložíme do buňky s indexem wParam v poli keys[] hodnotu false. Tímto způsobem můžeme zjistit, zda je klávesa ještě stále stisknuta nebo již byla uvolněna. Každá klávesa je reprezentována jedním číslem od 0 do 255. Když například stisknu klávesu číslo 40, hodnota keys[40] bude true, jakmile ji pustím, její hodnota se vrátí opět na false.
WM_KEYUP:// Uvolnění klávesy
begin
keys[wParam] := FALSE;// Oznámí to programu
result := 0;// Návrat do hlavního cyklu programu
end;
Kdykoliv uživatel změní velikost okna, pošle se WM_SIZE. Přečteme LOWORD a HIWORD hodnoty lParam, abychom zjistili, jaká je nová šířka a výška okna. Předáme tyto hodnoty do funkce ReSizeGLScene a tím se perspektiva OpenGL scény změní podle nových rozměrů.
WM_SIZE:// Změna velikosti okna
begin
ReSizeGLScene(LOWORD(lParam), HIWORD(lParam));// LoWord=Šířka, HiWord=Výška
result := 0;// Návrat do hlavního cyklu programu
end
else
Zprávy, o které se nestaráme, budou předány funkci DefWindowProc, takže se s nimi za nás vypořádá operační systém.
// Předání ostatních zpráv systému
begin
Result := DefWindowProc(hWnd, message, wParam, lParam);
end;
end;
end;
Funkce WinMain je vstupní bod do aplikace, místo, odkud budeme volat funkce na otevření okna, snímaní zpráv a interakci s uživatelem.
function WinMain(hInstance: HINST;// Instance
hPrevInstance: HINST;// Předchozí instance
lpCmdLine: PChar;// Parametry příkazové řádky
nCmdShow: integer):// Stav zobrazení okna
integer; stdcall;
Deklarujeme dvě lokální proměnné. Msg bude použita na zjišťování, zda se mají zpracovávat nějaké zprávy. Proměnná done bude mít na počátku hodnotu false. To znamená, že náš program ještě nemá být ukončen. Dokud se done rovná false, program poběží. Jakmile se změní z false na true, program se ukončí.
var
msg: TMsg;// Struktura zpráv systému
done: Bool;// Proměnná pro ukončení programu
begin
done := false;
Další část kódu je volitelná. Zobrazuje zprávu, která se zeptá uživatele, zda chce spustit program ve fullscreenu. Pokud uživatel vybere možnost Ne, hodnota proměnné fullscreen se změní z výchozího true na false, a tím pádem se program spustí v okně.
// Dotaz na uživatele pro fullscreen/okno
if MessageBox(0, 'Would You Like To Run In FullScreen Mode?', 'Start FullScreen', MB_YESNO or MB_ICONQUESTION) = IDNO then
FullScreen := false// Běh v okně
else
FullScreen := true;// Fullscreen
Vytvoříme OpenGL okno. Zadáme text titulku, šířku, výšku, barevnou hloubku a true (fullscreen), nebo false (okno) jako parametry do funkce CreateGLWindow. Tak a je to! Je to pěkně lehké, že? Pokud se okno nepodaří z nějakého důvodu vytvořit, bude vráceno false a program se okamžitě ukončí.
if not CreateGLWindow('NeHes OpenGL Framework', 640, 480, 16, FullScreen) then// Vytvoření OpenGL okna
begin
Result := 0;// Konec programu při chybě
exit;
end;
Smyčka se opakuje tak dlouho, dokud se done rovná false.
while not done do// Hlavní cyklus programu
begin
První věc, kterou uděláme, je zkontrolování zpráv pro okno. Pomocí funkce PeekMessage můžeme zjistit zda nějaké zprávy čekají na zpracování bez toho, aby byl program pozastaven. Mnoho programů používá funkci GetMessage. Pracuje to skvěle, ale program nic nedělá, když nedostává žádné zprávy.
if (PeekMessage(msg, 0, 0, 0, PM_REMOVE)) then// Přišla zpráva?
begin
Zkontrolujeme, zda jsme neobdrželi zprávu pro ukončení programu. Pokud je aktuální zpráva WM_QUIT, která je způsobena voláním funkce PostQuitMessage(0), nastavíme done na true, čímž přerušíme hlavní cyklus a ukončíme program.
if msg.message = WM_QUIT then// Obdrželi jsme zprávu pro ukončení?
done := true// Konec programu
else
begin
Když zpráva nevyzývá k ukončení programu, tak předáme funkcím TranslateMessage a DispatchMessage referenci na tuto zprávu, aby ji funkce WndProc nebo Windows zpracovaly.
TranslateMessage(msg);// Přeloží zprávu
DispatchMessage(msg);// Odešle zprávu
end;
end
else// Pokud nedošla žádná zpráva
begin
Pokud zde nebudou již žádné zprávy, překreslíme OpenGL scénu. Následující řádek kontroluje, zda je okno aktivní. Naše scéna je vyrenderována a je zkontrolována vrácená hodnota. Když funkce DrawGLScene vrátí false nebo je stisknut ESC, hodnota proměnné done je nastavena na true, což ukončí běh programu.
// Je program aktivní, ale nelze kreslit? Byl stisknut ESC?
if (active and not(DrawGLScene) or keys[VK_ESCAPE]) then
done := true// Ukončíme program
else// Překreslení scény
Když všechno proběhlo bez problémů, prohodíme obsah bufferů (s použitím dvou bufferů předejdeme blikání obrazu při překreslování). Použitím dvojitého bufferingu všechno vykreslujeme do obrazovky v paměti, kterou nevidíme. Jakmile vyměníme obsah bufferů, to co je na obrazovce se přesune do této skryté obrazovky a to, co je ve skryté obrazovce se přenese na monitor. Díky tomu nevidíme probliknutí.
SwapBuffers(h_Dc);// Prohození bufferů (Double Buffering)
Při stisku klávesy F1 přepneme z fullscreenu do okna a naopak.
if keys[VK_F1] then// Byla stisknuta klávesa F1?
begin
Keys[VK_F1] := false;// Označ ji jako nestisknutou
KillGLWindow;// Zruší okno
FullScreen := not FullScreen;// Negace fullscreen
// Znovuvytvoření okna
if not CreateGLWindow('NeHes OpenGL Framework', 640, 480, 16, fullscreen) then
Result := 0;// Konec programu pokud nebylo vytvořeno
end;
end;
end;// Konec smyčky while
Pokud se proměnná done rovná true, hlavní cyklus se přeruší. Zavřeme okno a opustíme program.
KillGLWindow;// Zavře okno
result := msg.wParam;// Ukončení programu
end;
Celý program se skládá pouze z volání funkce WinMain, která se už postará o vše ostatní.
begin
WinMain(hInstance, hPrevInst, CmdLine, CmdShow);// Start programu
end.
Pokud se vám text zdál povědomý, máte pravdu. V podstatě jde o Wessanův překlad lekce 1, čímž mu tímto děkuji, jen kód je v Delphi. Pokud budete mít jakékoli dotazy nebo problémy, neváhejte mě kontaktovat.
napsal: Michal Tuček <michal_praha (zavináč) seznam.cz>, 15.08.2004