Jak na šetřič obrazovky

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>

Zdrojové kódy