▷▷▷▷▷▷ Follow My Threads Account ◁◁◁◁◁◁
手机版
OpenGL Tutorial (2)
- by Changhai Lu -
Abstract: In this lesson, we will learn some basic
concepts about the Windows' Graphics Device Interface. This is the last pre-OpenGL
lesson of the tutorial.
<< Prev Tutorial
1. Return of the "Hello world!"
Our "Hello world!" example is only half-way done, the window we created so far is a blank one.
To finish the example, it is necessary to introduce
some basic concepts about the Windows
Graphics Device Interface (GDI). Such an introduction will also
serve as a good take-off point for entering the OpenGL space.
Windows GDI is a dynamic-link library (DLL library) that processes graphics function
calls from a Windows-based application and passes those calls to the appropriate device driver.
GDI is a software layer between programmers and the display devices such as a video display or a printer, it
hides the complexity of these devices from the programmers, and makes graphic
programming device-independent.
One of the most important data structure in GDI is the so called Device Context
structure - DC. Device context is a data structure
internally maintained by GDI. What "internally" means is you can't access data
members of a device context directly.
A device context is associated with a particular display device, in the case the device is the
video display, the device context is usually associated with a particular window on the display.
Every GDI drawing function requires a handle to a device context to work.
The device context determines how GDI drawing functions work, for instance, what color should be used
in drawing, what font should be used for text, which area should be painted, etc.
Device context is a system resource, therefore
you should always release device context to the system once you are done with it - similar to what
you would do for memory in normal C/C++ programming.
Win32 uses the teminology of "painting" an area when something needs to be displayed. Whenever a part of the
client area of a window needs to be painted (becaused user resized or moved a window for instance),
the operating system will send a WM_PAINT message
to the program that owns the window, the program's window procedure will
handle the message and do the necessary painting in the handler.
So we will place our drawing code in the
WM_PAINT handler of the window procedure. The area or region that needs to be
painted is called the invalid region or update region.
As a program runs, the operating system keeps track of many paint information for each window the program
created, invalid region is one such piece of information. If, for some reason, you need to manually add an area
into the invalid region (so a painting will be done for that region), you can use the
InvalidateRect function.
Another thing that is very important about the invalid region is
you must validate the invalid region in the
WM_PAINT handler.
Windows does not place multiple WM_PAINT messages to the message queue,
but once a WM_PAINT message is retrived from the message queue,
Windows will send another WM_PAINT message to the queue
(and keep doing so) until the program validates the invalid region (which basically tells Windows
that the invalid area is updated). To validate an area, one usually uses the
ValidateRect function.
In summerize, the steps required to finish our "Hello world!" example (namely to draw the
"Hello world!" string on the window) are the following:
- Get a handle to the device context
- Draw the text string
- Validate the invalid region
- Release the device context
The following code is the new window procedure that - in addition to what we had before -
does the drawing in the WM_PAINT handler.
The old code is gray out so we can focus on the new part.
The comments in the code are in one-to-one correpondence with the steps listed above.
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
switch (msg) {
case WM_PAINT:
// Get a handle to the device context & Validate the invalid region
hdc = BeginPaint(hwnd, &ps);
// Draw the text string
TextOut(hdc, 50, 50, "Hello world!", 12);
// Release the device context
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
The WinMain function - which we didn't include here - remains the same as in
Lesson One. We use the function BeginPaint to get the device context
associated with the window whose handle - hwnd - is passed as the
first argument. BeginPaint takes a second argument
- &ps - which is a reference
to a PAINTSTRUCT structure. As we mentioned
before, the operating system keeps track of
many paint information for the window, these information will be stored in
ps. For most simple tasks, including our example,
the information stored in ps is not used (but is required to keep the function signature correct).
Before returning a handle to the device context, the BeginPaint
function does the following:
-
Sets the clipping region of the device context to exclude any area outside the update region
(all the painting controlled by a device context is restricted to the clipping region of that device context).
- Validates the entire client area.
From here we see that the BeginPaint function alone accomplishes two steps
for the drawing task (Gets a handle and validates the region). Notice that we haven't done any
painting yet, it might seem strange that the validation is done so early.
Logically one would think that painting should be done before the validation
(after all validating an area is to tell Windows that the area is painted!).
But in reality it is not very important to keep the order since validation is nothing but
an announcement to the operating system.
Normally you would announce the finishing of a task after it's done,
but sometimes you can announce it in advance, so long as you do the task right after
the announcement and finish it before your boss gets a chance to check it!
Once the device context is ready,
we then use the TextOut function to draw
the text string. The TextOut function takes - in addition to the device context
that determines the color, font, etc (all in system default values)
- the x, y coordinates
at which the string is to be drawn, the string itself, and the total number of characters in the string
(instead of counting the number yourself and passing it directly as we did in the example, you can use
the strlen() function to do the counting).
Finally we use the EndPaint function to release the device context.
EndPaint takes the same arguments as BeginPaint
(you may wonder that in order to release the device context pointed by hdc,
why didn't we pass hdc to the EndPaint function?
This is because &ps - the pointer to a PAINTSTRUCT - that we pass
to EndPaint already has hdc as a data field).
BeginPaint and EndPaint should always be used in pairs
(similar to the new and free pair in C/C++).
If a window procedure doesn't handle the WM_PAINT message manually, the default handler
will simply call the BeginPaint and EndPaint
pair in succession which does nothing by validate the invalid region (so Windows will not keep sending
WM_PAINT messages).
2. Another method
There is another popular function people often use to get a device context handle.
That is the GetDC function.
To get a handle to the device context associated with a window,
GetDC takes the handle to that window as argument (and that's all it takes).
The following code demonstrates the using of the GetDC
function in our "Hello world!" example:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
switch (msg) {
case WM_PAINT:
// Get a handle to the device context
hdc = GetDC(hwnd);
// Draw the text string
TextOut(hdc, 50, 50, "Hello world!", 12);
// Validate the invalid region
ValidateRect(hwnd, NULL);
// Release the device context
ReleaseDC(hwnd, hdc);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
Notice that we used the ValidateRect function
in this code, because the GetDC function - unlike
BeginPaint - will NOT validate the invalid region therefore it is
necessary that we do the validation ourselves. Another feature that is different from the
BeginPaint function is that GetDC
sets the clipping region of the device context equal to the whole client area, therefore
any painting using the device context returned by GetDC
will not be restricted to the invalid region (but will still be restricted to the client area
unless you do what described in the next paragraph).
One thing interesting about the GetDC function is that if -
instead of passing a handle to the window - you pass a NULL pointer
to GetDC, you will get the device context for the whole
primary display, which means you can draw anywhere on the screen with this device context. To test this, replace
the line hdc = GetDC(hwnd); by hdc = GetDC(NULL); in the code,
and change the coordinates in the TextOut function from
50, 50 to 500, 500.
Run the modified program you will see a "Hello world!" string painted at screen
coordinate 500, 500, which is far beyond the application window (of size 200 × 200).
Similar to the using of the EndPaint function to release
the device context obtained by BeginPaint,
There is a ReleaseDC function one should always use to release
the device context obtained by GetDC. The ReleaseDC
function takes two arguments: the handle to the window - hwnd and
the handle to the device context - hdc (GetDC
-unlike EndPaint - doesn't take &ps, therefore
hdc is needed).
3. Change device context - an example
As we said before, the device context is maintained by GDI internally and we don't have a direct access to
its data members. On the other hand, device context controls the appearance of the painting therefore it's
quite obvious that users will constantly need to change its properties (i.e. data members). To fullfill
such demand, Windows GDI provides many functions to
perform those changes. All these functions take the handle to the device context and manipulate it.
For instance, if you want to draw the text in red as oppose to the default black color,
you can use the SetTextColor function to set the color property for the device context
before calling the TextOut function. The code segment is as follows:
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
SetTextColor(hdc, RGB(255, 0, 0));
TextOut(hdc, 50, 50, "Hello world!", 12);
EndPaint(hwnd, &ps);
break;
What the SetTextColor function takes are a handle to the device context and
the new color. What it does is set the color of that device context to the new color. The return value
of SetTextColor (which we discarded) is the previous color used in
the device context. You should save that value if you ever want
to restore the original color later on.
Ok, Let's end our Windows GDI tour here, many tasks done by Windows GDI can also be done using
OpenGL.
What's next?
OpenGL (finally)!
>> Next Tutorial
ValidateRect function - The
ValidateRect function validates the client area within a rectangle by removing the rectangle
from the update region of the specified window. The arguments this function takes are:
-
hWnd is a handle to the window the validation applies.
If this parameter is NULL, the system invalidates and redraws all windows.
-
lpRect is a pointer to a rectangle -
a RECT structure - that is to be removed.
If this parameter is NULL, the entire client window will be validated.
Once the rectangle region is removed, any queued WM_PAINT
message (if exists) whose invalid region is within the rectangle will be removed from
the queue, and no further WM_PAINT message will be sent until
a new invalid region occurs.
-
Microsoft Corporation, MSDN Library, comes with Visual Studio 6.0, also available
on the Microsoft website.
-
Charles Petzold, Programming Windows (5th edition), Microsoft Press, 1998.
-
André Lamothe, Tricks of the Windows Game Programming Gurus,
Sams, 1999.
posted on September 19, 2002 https://www.changhai.org/
|