Už dávno jsem si chtěl naprogramovat vlastní šetřič obrazovky. Měl jsem sice třídu CScreenSaverWnd pro MFC, ale ta nepodporovala OpenGL. U 38. NeHe Tutoriálu jsem našel odkaz na šetřič obrazovky s podporou OpenGL, který napsal Brian Hunsucker. Chtěl bych mu poděkovat, protože na jeho zdrojovém kódu z větší části staví tento článek.
Začneme parametry spouštění šetřiče obrazovky. Které definují, zda se má spustit šetřič obrazovky jako takový (s) nebo pouze jeho konfigurační dialog (c). Měli bychom ho získávat z příkazové řádky testováním parametrů funkce main(), ale defakto se o něj nemusíme starat, protože vše zajistí knihovna scrnsave, která je součástí Visual C++. Jenom tak na okraj, k main() se vůbec nedostaneme, protože je předkompilovaná v scrnsave. Stejně tak se nestaráme o zavření šetřiče a podobné věci. Program vše dělá automaticky.
Jedna malá poznámka: při vývoji aplikace se po spuštění zobrazí konfigurační dialog. Abychom program spustili jako šetřič, musíme mu předat parametr s. V Project/Settings pod nabídkou Debug se musí napsat do Program arguments písmeno s.
Vygenerujeme klasický Win32 Aplication projekt a můžeme psát kód. Vložíme hlavičkové soubory a přilinkujeme potřebné knihovny.
#include <windows.h>// Hlavičkový soubor pro Windows
#include <scrnsave.h>// Hlavičkový soubor pro šetřič obrazovky
#include <GL/gl.h>// Hlavičkový soubor pro OpenGL
#include <GL/glu.h>// Hlavičkový soubor pro GLU
#include "res/resource.h"// Hlavičkový soubor pro Resource (konfigurační dialog, ikona ...)
#pragma comment (lib,"opengl32.lib")// Přilinkování OpenGL
#pragma comment (lib,"glu32.lib")// Přilinkování GLU
#pragma comment (lib,"scrnsave.lib")// Přilinkování knihovny šetřiče obrazovky
Instance aplikace je jedinou globální proměnnou.
HINSTANCE hInstance;// Ukládá instanci aplikace
V následující funkci inicializujeme okno tak, aby podporovalo OpenGL. Dá se říct, že s největší pravděpodobností tuto funkci nebudete muset nikdy změnit.
HGLRC InitOGLWindow(HWND hWnd)// Inicializace okna
{
HDC hDC = GetDC(hWnd);// Kontext zařízení
HGLRC hRC = 0;// Renderovací kontext
PIXELFORMATDESCRIPTOR pfd;
int nFormat;
ZeroMemory(&pfd, sizeof(PIXELFORMATDESCRIPTOR));
// Nastavení okna
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER;
pfd.cColorBits = 24;
pfd.cDepthBits = 24;
nFormat = ChoosePixelFormat(hDC, &pfd);
DescribePixelFormat(hDC, nFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);
SetPixelFormat(hDC, nFormat, &pfd);
hRC = wglCreateContext(hDC);
wglMakeCurrent(hDC, hRC);
ReleaseDC(hWnd, hDC);
return hRC;// Vrátí renderovací kontext
}
Do inicializace OpenGL přidáme i nastavení perspektivy, která se standardně vkládá do funkce pro změnu velikosti okna. Nic se nestane, protože parametry okna šetřiče obrazovky se nikdy nezmění. Jak také? Jakmile se pohne myší, program je ukončen.
void InitOpenGL(GLsizei width, GLsizei height)// Inicializace OpenGL
{
if (height==0)// Proti dělení nulou
{
height=1;
}
glViewport(0,0,width,height);// Reset Viewportu
glMatrixMode(GL_PROJECTION);// Zvolí projekční matici
glLoadIdentity();// Reset matice
// Perspektiva
gluPerspective(45.0f, (GLfloat)(width)/(GLfloat)(height),1.0f, 20.0f);
glMatrixMode(GL_MODELVIEW);// Zvolí matici Modelview
glLoadIdentity();
Na tomto místě si sami nastavte OpenGL, jak uznáte za vhodné. V našem případě definujeme stínování, perspektivní korekce, barvu pozadí a nastavení hloubkového bufferu.
// Uživatelská inicializace
glShadeModel(GL_SMOOTH);// Vyhlazené stínování
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);// Perspektivní korekce
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);// Černé pozadí
glClearDepth(1.0f);// Nastavení hloubkového bufferu
glDepthFunc(GL_LEQUAL);// Typ hloubkového testování
glEnable(GL_DEPTH_TEST);// Zapne test hloubky
}
Druhým místem, které se při vytváření nového šetřiče mění, je vykreslovací funkce. Abychom věděli, že program funguje, vykreslíme trojúhelník a obdélník. Nic světoborného...
void DrawGLScene()
{
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Smaže obrazovku a hloubkový buffer
glLoadIdentity();// Reset matice
glTranslatef(-1.5f,0.0f,-6.0f);// Posun doleva a do hloubky
glBegin(GL_TRIANGLES);// Začátek kreslení trojúhelníků
glVertex3f( 0.0f, 1.0f, 0.0f);// Horní bod
glVertex3f(-1.0f,-1.0f, 0.0f);// Levý dolní bod
glVertex3f( 1.0f,-1.0f, 0.0f);// Pravý dolní bod
glEnd();
glTranslatef(3.0f,0.0f,0.0f);// Posun o 3 jednotky doprava
glBegin(GL_QUADS);// Začátek kreslení obdélníků
glVertex3f(-1.0f, 1.0f, 0.0f);// Levý horní bod
glVertex3f( 1.0f, 1.0f, 0.0f);// Pravý horní bod
glVertex3f( 1.0f,-1.0f, 0.0f);// Pravý dolní bod
glVertex3f(-1.0f,-1.0f, 0.0f);// Levý dolní bod
glEnd();// Konec kreslení obdélníků
glFlush();
}
I šetřiči obrazovky posílá systém zprávy. Není jich sice mnoho, ale bez některých bychom se určitě neobešli. Deklarujeme proměnné kontextu zařízení a renderovacího kontextu a dále větvíme funkci podle došlé zprávy.
// Procedura okna šetřiče obrazovky
LRESULT WINAPI ScreenSaverProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hDC;// Kontext zařízení
static HGLRC hRC;// Renderovací kontext
switch (message)// Větví program podle došlé zprávy
{
Na zprávu WM_CREATE, kterou posílá systém ihned po vytvoření okna, inicializujeme okno. Poté pomocí funkce GetClientRect() nagrabujeme jeho šířku a výšku, kterou předáme do InitOpenGL(). Nakonec spustíme timer zajišťující periodické překreslování scény.
case WM_CREATE:// Vytvoření okna
hRC = InitOGLWindow(hWnd);// Získání kontextu zařízení
RECT WindowRect;// Pro zjištění velikosti okna
int width;// Šířka
int height;// Výška
GetClientRect(hWnd, &WindowRect);// Získá velikost okna
width = WindowRect.right - WindowRect.left;// Šířka okna
height = WindowRect.bottom - WindowRect.top;// Výška okna
InitOpenGL(width, height);// Inicializace OpenGL
SetTimer(hWnd, 1, 20, NULL);// Zapnutí timeru
break;
O kousek výše jsme spustili timer. Nyní definujeme, že po příchodu jeho zprávy se má získat DC. Poté překreslíme scénu, prohodíme buffery a opět uvolníme DC. Kód je tak krátký, že vytvářet novou funkci je úplně zbytečné.
case WM_TIMER:// Zpráva od časovače
hDC = GetDC(hWnd);// Získání kontextu zařízení
DrawGLScene();// Vykreslí scénu
SwapBuffers(hDC);// Prohodí buffery
ReleaseDC(hWnd, hDC);// Uvolní kontext zařízení
break;
Na zprávu WM_DESTROY provedeme deinicializaci.
case WM_DESTROY:// Zavření okna
KillTimer(hWnd,1); // Vypnutí timeru
wglMakeCurrent(NULL, NULL);
wglDeleteContext(hRC);// Smaže renderovací kontext
break;
}
Všechny ostatní zprávy předáme dále.
return DefScreenSaverProc(hWnd, message, wParam, lParam);// Neošetřené zprávy
}
Úplně na začátku jsme si řekli, že program má dvě cesty provádění. Klasický šetřič už máme, nyní se vrhneme na konfigurační dialog. Nejprve ho vytvoříme. Má dvě tlačítka OK a CANCEL (popř. i další). Začneme mapou zpráv a dá se říct, že i skončíme. Při WM_INITDIALOG můžeme jednotlivé prvky inicializovat na hodnoty, které získáme například z pomocného souboru nebo z registrů Windows. Určovaly by chování šetřiče, ale náš program je velmi jednoduchý, tak proč ho komplikovat. Namapováním zprávy WM_COMMAND určíme, co se má udělat po kliknutí na tlačítka.
// Procedura okna konfiguračního dialogu
BOOL WINAPI ScreenSaverConfigureDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)// Větví program podle došlé zprávy
{
case WM_INITDIALOG:// Inicializace dialogu
return TRUE;// V pořádku
case WM_COMMAND:// Příkaz (např. kliknutí na tlačítko)
switch (LOWORD(wParam))// Které tlačítko?
{
case IDOK:// OK
EndDialog(hDlg, TRUE);// Zavře dialog
return TRUE;// V pořádku
case IDCANCEL:// Cancel
EndDialog(hDlg, TRUE);// Zavře dialog
break;
}
}
return FALSE;// False - uživatel např. stiskl Cancel
}
K čemu je tato funkce, abych řekl pravdu, nevím, ale kompilátor mi bez ní hlásí chyby.
BOOL WINAPI RegisterDialogClasses(HANDLE hInst)// ???
{
return TRUE;
}
No a tím jsme i skončili. Možná se ptáte: "A opravdu to funguje?". Ano, také jsem nechápal. Když jsem tento kód viděl poprvé, bez main() a jakýchkoli jiných návazností pochyboval jsem, že svůj vlastní šetřič někdy v životě rozjedu, ale podařilo se. Člověk se nemusí skoro o nic starat vše je připraveno v knihovně scrnsave. Stáhněte si zdrojový kód a uvidíte.
Abych nezapomněl, výsledný .exe soubor je nutné přejmenovat na .scr a zkopírovat do Windows/System. Až potom budete moci v nastavení obrazovky vyměnit šetřič za nový (samozřejmě lepší).
napsal: Michal Turek - Woq <WOQ (zavináč) seznam.cz>