Chapter 3
GDI, all you need..
Go back
Main point in this chapter are device contexts. You can't access hardware directly
in windows (at least not in the scope of this tutorial). All you have is this
device context thing.
Obvious bad side of device context use is the overhead of converting data
into required form. Good side is that when we do things like this, you
don't have to care whether the user is viewing your program via EGA,
Matrox Millennium, VR-gear, hologram projector, rapid laser printer or even
a plotter.
Device contexts are one of these resources that are always being used up.
I don't know the exact story, but in windows 3.x program, you could crash
windows by allocating some 16 device contexts or so. Quite probably with
much less. So, device contexts should be allocated, used, and freed as
soon as they are not needed anymore.
But since I'm also a victim of this democoder attitude, we'll allocate
our framebuffer DC at start and free it at closing time.. This works fine
since we aren't using any other DC:s in this program, but in slightly larger
windows application you might be allocating and deallocating DC:s all the
time, and might run out of them pretty fast.
So, here's the code.
#include <windows.h>
#include <math.h>
char progname[]="Cute plasma";
char SINTAB[256];
// forward declaration:
LRESULT CALLBACK winproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{
WNDCLASSEX winclass;
HWND hWnd;
MSG msg;
int i;
for (i=0;i<256;i++)
SINTAB[i]=sin(((i+1)*3.14159265359)/128)*127+128;
winclass.cbSize=sizeof(WNDCLASSEX);
winclass.style=CS_DBLCLKS;
winclass.lpfnWndProc=&winproc;
winclass.cbClsExtra=0;
winclass.cbWndExtra=0;
winclass.hInstance=hInst;
winclass.hIcon=LoadIcon(NULL,IDI_WINLOGO);
winclass.hCursor=LoadCursor(NULL,IDC_NO);
winclass.hbrBackground=NULL;
winclass.lpszMenuName=NULL;
winclass.lpszClassName=progname;
winclass.hIconSm=NULL;
if (!RegisterClassEx(&winclass))
return 0;
hWnd=CreateWindow(
progname,
progname,
WS_SYSMENU|WS_CAPTION|WS_BORDER|WS_OVERLAPPED|WS_VISIBLE|WS_MINIMIZEBOX,
CW_USEDEFAULT,
0,
320+2,
200+16+2,
NULL,
NULL,
hInst,
NULL);
ShowWindow(hWnd,nCmdShow);
UpdateWindow(hWnd);
while (GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (msg.wParam);
}
HDC pDC;
HBITMAP old;
HBITMAP ourbitmap;
int * framebuf;
void render_effect(int tick,int * framebuf)
{
int i,j,k;
tick/=4;
for (k=0,i=0;i<200;i++)
for (j=0;j<320;j++,k++)
*(framebuf+k)=RGB(SINTAB[(i+tick)&0xff],
SINTAB[(j-tick)&0xff],
SINTAB[(SINTAB[tick&0xff]+(k>>6))&0xff]);
}
void render(HDC hDC)
{
render_effect(GetTickCount(),framebuf);
BitBlt(hDC,0,0,320,200,pDC,0,0,SRCCOPY);
}
void deinit_framebuf(void)
{
SelectObject(pDC,old);
DeleteDC(pDC);
DeleteObject(ourbitmap);
}
void init_framebuf(void)
{
HDC hDC;
BITMAPINFO bitmapinfo;
hDC=CreateCompatibleDC(NULL);
bitmapinfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
bitmapinfo.bmiHeader.biWidth=320;
bitmapinfo.bmiHeader.biHeight=-200; /* top-down */
bitmapinfo.bmiHeader.biPlanes=1;
bitmapinfo.bmiHeader.biBitCount=32;
bitmapinfo.bmiHeader.biCompression=BI_RGB;
bitmapinfo.bmiHeader.biSizeImage=0;
bitmapinfo.bmiHeader.biClrUsed=256;
bitmapinfo.bmiHeader.biClrImportant=256;
ourbitmap=CreateDIBSection(hDC,&bitmapinfo,DIB_RGB_COLORS,&framebuf,0,0);
pDC=CreateCompatibleDC(NULL);
old=SelectObject(pDC,ourbitmap);
DeleteDC(hDC);
}
LRESULT CALLBACK winproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HDC hDC;
PAINTSTRUCT PtStr;
switch (uMsg) {
case WM_DESTROY:
deinit_framebuf();
PostQuitMessage(0);
KillTimer (hWnd, 1);
break;
case WM_CREATE:
SetTimer (hWnd, 1, 1, NULL);
init_framebuf();
break;
case WM_TIMER:
InvalidateRgn(hWnd,0,0);
UpdateWindow (hWnd);
break;
case WM_PAINT:
hDC=BeginPaint(hWnd,&PtStr);
render(hDC);
EndPaint(hWnd,&PtStr);
break;
default:
return DefWindowProc (hWnd, uMsg, wParam, lParam);
break;
}
return 0;
}
If you go and compile it, it'll give you a window with a very simple RGB plasma.
Slow, too. But it does what it's meant to do: now you have a RGB framebuffer.
void init_framebuf(void)
{
HDC hDC;
BITMAPINFO bitmapinfo;
hDC=CreateCompatibleDC(NULL);
bitmapinfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
bitmapinfo.bmiHeader.biWidth=320;
bitmapinfo.bmiHeader.biHeight=-200; /* top-down */
bitmapinfo.bmiHeader.biPlanes=1;
bitmapinfo.bmiHeader.biBitCount=32;
bitmapinfo.bmiHeader.biCompression=BI_RGB;
bitmapinfo.bmiHeader.biSizeImage=0;
bitmapinfo.bmiHeader.biClrUsed=256;
bitmapinfo.bmiHeader.biClrImportant=256;
ourbitmap=CreateDIBSection(hDC,&bitmapinfo,DIB_RGB_COLORS,&framebuf,0,0);
pDC=CreateCompatibleDC(NULL);
old=SelectObject(pDC,ourbitmap);
DeleteDC(hDC);
}
Let's start from the logical top. First we request a compatible DC of the
default device, ie. screen. Next we create a Device Independent Bitmap section,
which will become our framebuffer, which will be initialized to be used with
hDC like output device. Our DIB will be 320x200, 32bit ARGB, top down (default
would be bottoms-up). Next we create a new compatible DC and place our DIB into
this DC, saving its (possible) old DIB so that we don't waste resources.
void render(HDC hDC)
{
render_effect(GetTickCount(),framebuf);
BitBlt(hDC,0,0,320,200,pDC,0,0,SRCCOPY);
}
When rendering, we give our effect the target framebuffer and timer tick
(one msec resolution). Then we'll just blit our framebuffer into the screen
device context. Please note that the BitBlt will only move data into dirty
areas, so nothing is wasted by using larger blitting sizes. SRCCOPY is just
copy-over.. windows can do lots of other kinds of blits as well.
I'm not going to explain how the plasma rendering function works; it should
be quite obvious.
case WM_DESTROY:
deinit_framebuf();
PostQuitMessage(0);
KillTimer (hWnd, 1);
break;
case WM_CREATE:
SetTimer (hWnd, 1, 1, NULL);
init_framebuf();
break;
case WM_TIMER:
InvalidateRgn(hWnd,0,0);
UpdateWindow (hWnd);
break;
case WM_PAINT:
hDC=BeginPaint(hWnd,&PtStr);
render(hDC);
EndPaint(hWnd,&PtStr);
break;
Most interesting changes have happened at the window procedure. At creating time
we allocate a timer, which will send us WM_TIMER messages at about one millisecond
intervals, give or take 10 milliseconds =). This is not a "multimedia timer", so
it isn't all that accurate.
Next we call our framebuffer initializer. In closing time we deinitialize the
framebuffer first, kill off our allocated timer and remember to post that
quit message!.
So the timer throws us messages all the time. When we get one, we'll invalidate
the whole client area of our window, and request the window update. And when the
WM_PAINT arrives, we first start painting, call our own rendering function, then
end the painting, and wait for the next WM_TIMER message.
void deinit_framebuf(void)
{
SelectObject(pDC,old);
DeleteDC(pDC);
DeleteObject(ourbitmap);
}
And when deinitializing, we first put the old DIB back to our framebuffer hDC,
kill the framebuffer hDC off, and finish by killing our DIB off.
That's practically it!
Next up: resources, menus and dialog boxes.
Journey onwards
Sol's win32 coding tutorials
(c)1998 Jari Komppa aka Sol/Trauma