📄 direct.html
字号:
<html>
<head>
<title>Direct Draw Tutorial</title>
<meta name="description" content="Reliable software Win32 Tutorial">
<meta name="keywords" content="bitmap, bitblt, windows, cplusplus">
</head>
<body background="grid.gif" tppabs="http://www.relisoft.com/images/grid.gif" bgcolor="white" text="black">
<table cellpadding=10 width=100%>
<tr>
<td width=100 align="center" valign="middle">
<a href="index-11.htm" tppabs="http://www.relisoft.com/index.htm">
<img src="rsbullet.gif" tppabs="http://www.relisoft.com/images/rsbullet.gif" alt="RS" border=0 width=39 height=39>
<p>Home</a>
</td>
<td><font face="arial" color="#00a080">
<h1 align=center>Direct Draw</h1>
</font>
</td>
</tr>
</table>
<table width=100%><!-- main table -->
<tr>
<td width=10> <!-- Left margin --> </td>
<td> <!-- Middle column, there is also the right margin at the end -->
<table cellpadding=10 cellspacing=0 width=500>
<tr>
<td bgcolor=white>
<font face="Arial" size=2>
<hr>
<img src="brace-3.gif" tppabs="http://www.relisoft.com/win32/images/brace.gif" width=16 height=16 border=0 alt="">As usual, you can <a href="direct.zip" tppabs="http://www.relisoft.com/win32/source/direct.zip">download</a> the complete source code of the application that is used in this example.
<hr>
<h2>Direct Draw App</h2>
<p>If you want fast animation (games!), you have to use DirectX. Try filling the screen by drawing each pixel separately using GDI. Compare it with DirectX, which gives you practically direct access to the screen buffer. There's no competition!
<hr>
<p>The bad news is that DirectX involves dealing with COM interfaces. The good news is that you can encapsulate them nicely inside C++ classes. Let me show you first how to use these classes to write a simple application that displays an animated, rotating, bitmap. We'll use Windows timer to drive the animation. We'll have to:
<ul>
<li>Initialize the program, including the DirectX subsystem
<li>Respond to timer messages
<li>Respond to window resizing (we'll use the window mode, rather than full screen)
<li>Respond to paint messages
</ul>
<p>Let's start with the WM_PAINT message. This message is sent to us when Windows needs to repaint our window. We have to stash somewhere a complete animation frame, so that we can quickly blit it to screen in response to WM_PAINT. The storage for this frame will be called the <b>back surface</b>.
<p>Upon receiving the timer message, we have to paint a new frame on the back surface. Once we're done, we'll invalidate our window, so that WM_PAINT message will be sent to us.
<p>Upon resizing the window, we have to create a new back surface, corresponding to the new window size. Than we'll draw on it and invalidate the window.
<hr>
<p>The initialization involves creating the timer, creating the World (that's where we keep our bitmap and drive the animation), creating the View, and setting the first timer countdown.
<!--Yellow background--> <table cellpadding=10 cellspacing=0 width=100%>
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="black">
<pre>void Controller::Create (CREATESTRUCT * create)
{
_timer.Create (_h, 1);
_world = new World (create->hInstance, IDB_RS);
_view.Create (_h, _world);
_timer.Set (10);
}</pre><!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<hr>
<p>Here's our response to WM_SIZE. We let the View know about the new size (it will create a new back surface), we call it's Update method which draws the animation frame, and we invalidate the window.
<!--Yellow background--> <table cellpadding=10 cellspacing=0 width=100%>
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="black">
<pre>bool Controller::Size (int width, int height)
{
_view.Size (_h, width, height);
_view.Update ();
// force repaint
_h.Invalidate (false);
return true;
}</pre><!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<hr>
<p>In response to WM_PAINT, we let the View do the painting (blit the back surface to the screen). We also have to tell Windows that the painting is done--validate the uncovered area. This is done simply by constructing the PaintCanvas object.
<!--Yellow background--> <table cellpadding=10 cellspacing=0 width=100%>
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="black">
<pre>bool Controller::Paint (HDC hdc)
{
_view.Paint (_h);
// validate painted area
PaintCanvas canvas (_h);
return true;
}</pre><!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<hr>
<p>In response to WM_TIMER, we kill the timer, tell the World to move one frame ahead, let the View repaint the frame on the back surface, invalidate the window (so that Paint is called) and set the timeout again.
<!--Yellow background--> <table cellpadding=10 cellspacing=0 width=100%>
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="black">
<pre>bool Controller::Timer (int id, TIMERPROC * proc)
{
_timer.Kill ();
_world->Step ();
_view.Update ();
// force repaint
_h.Invalidate (false);
_timer.Set (10);
return true;
}</pre><!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<hr>
<p>There is one more thing. DirectX doesn't like when a screen saver kicks in, so we have to preempt it.
<!--Yellow background--> <table cellpadding=10 cellspacing=0 width=100%>
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="black">
<pre>bool Controller::SysCommand (int cmd)
{
// disable screen savers
if (cmd == SC_SCREENSAVE || cmd == SC_MONITORPOWER)
return true;
return false;
}</pre><!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<p>By the way, this is how SysCommand is called from the window procedure:
<!--Yellow background--> <table cellpadding=10 cellspacing=0 width=100%>
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="black">
<pre> case WM_SYSCOMMAND:
if (pCtrl->SysCommand (wParam & 0xfff0))
return 0;
break;
</pre><!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<hr>
<p>Let's now have a closer look at View, which is the class dealing with output to screen. Notice two important data members, the Direct::Draw object and the Direct::Surface object.
<!--Yellow background--> <table cellpadding=10 cellspacing=0 width=100%>
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="black">
<pre>class View
{
public:
View ();
void Create (HWND h, World * world);
void Size (HWND h, int width, int height);
void Update ();
void Paint (HWND h);
private:
<b>Direct::Draw</b> _draw;
<b>Direct::Surface</b> _backSurface;
World * _world;
int _dx;
int _dy;
};</pre><!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<p>I have encapsulated all DirectX classes inside the namespace <b>Direct</b>. This trick enforces a very nice naming convention, namely, you have to prefix all DirectX classes with the prefix <b>Direct::</b> .
<p>The Direct::Draw object's duty is to initialize the DirectX subsystem. It does it in it's constructor, so any time View is constructed (once per program instatiation), DirectX is ready to use. It's destructor, by the way, frees the resources used by DirectX.
<p>Direct::Surface _backSurface will be used by View to draw the animation upon it.
<hr>
<p>The initialization of DirectX also involves setting up the collaboration level with Windows. Here, we are telling it to work nicely with other windows, rather then taking over in the full-screen mode.
<!--Yellow background--> <table cellpadding=10 cellspacing=0 width=100%>
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="black">
<pre>void View::Create (HWND h, World * world)
{
_world = world;
// Set coopration with Windows
_draw.<b>SetCoopNormal</b> (h);
}</pre><!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<hr>
<p>Back surface is created (and re-created) in response to WM_SIZE message. Surfaces are implemented in such a way that regular assignment operation does the right thing. It deallocates the previous surface and initializes a new one. Notice that the constructor of an OffScreenSurface takes the Direct::Draw object and the dimensions of the surface in pixels.
<!--Yellow background--> <table cellpadding=10 cellspacing=0 width=100%>
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="black">
<pre>void View::Size (HWND h, int width, int height)
{
_dx = width;
_dy = height;
if (_dx == 0 || _dy == 0)
return;
_backSurface = <b>Direct::OffScreenSurface</b> (_draw, _dx, _dy);
}</pre><!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<hr>
<p>Painting is slightly tricky. We have to get access to the primary drawing surface, which is the whole screen buffer. But since we are not using the full-screen mode, we want to draw only inside the rectangular area of our window. That's why we create a Direct::Clipper, to clip anything that would fall outside of that area. The clipper figures out the correct area using the handle to the window that we are passing to it.
<p>Next, we create the primary surface and pass it the clipper. Now we can blit the image directly from our back surface to the screen. Again, since the primary surface represents the whole screen, we have to offset our blit by specifying the target rectangle. The auxillary class Direct::WinRect retrieves the coordinates of that rectangle.
<!--Yellow background--> <table cellpadding=10 cellspacing=0 width=100%>
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="black">
<pre>void View::Paint (HWND h)
{
if (_dx == 0 || _dy == 0)
return;
// Clip to window
<b>Direct::Clipper</b> clipper (_draw);
clipper.SetHWnd (h);
// Screen surface
<b>Direct::PrimarySurface</b> surf (_draw);
surf.SetClipper (clipper);
<b>Direct::WinRect</b> rect (h);
// Blit from back surface to screen
surf.<b>BltFrom</b> (_backSurface, &rect);
}</pre><!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<hr>
<p>Finally, the actual drawing is done using direct access to the back surface buffer. But first, we flood the back surface with white background. Then we construct the Direct::SurfaceBuf. This is the object that let's us set pixels directly into the buffer's memory (depending on implementation, it might be your video card memory or a chunk of your system memory). The details of our drawing algorithm are inessential here. Suffice it to say that the work of setting the pixel is done in the SetPixel method.
<!--Yellow background--> <table cellpadding=10 cellspacing=0 width=100%>
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="black">
<pre>void View::Update ()
{
if (_dx == 0 || _dy == 0)
return;
try
{
// white background
_backSurface.<b>Fill</b> (RGB (255, 255, 255));
{
// Get direct access to back surface
<b>Direct::SurfaceBuf</b> buf (_backSurface);
// draw bitmap within a centered square
int side = 100;
if (_dx < side)
side = _dx;
if (_dy < side)
side = _dy;
int xOff = (_dx - side) / 2;
int yOff = (_dy - side) / 2;
assert (xOff >= 0);
assert (yOff >= 0);
double dxInv = 1.0 / side;
double dyInv = 1.0 / side;
for (int i = 0; i < side; ++i)
{
for (int j = 0; j < side; ++j)
{
double u = dxInv * i;
double v = dyInv * j;
COLORREF color = _world->GetTexel (u, v);
// draw pixel directly
buf.<b>SetPixel</b> (xOff + i, yOff + j, color);
}
}
}
// Paint will blit the image to screen
}
catch (char const * msg)
{
::MessageBox (0, msg, "Viewer error",
MB_OK | MB_ICONERROR);
throw;
}
catch (...)
{
::MessageBox (0, "Unknown error",
"Viewer error",
MB_OK | MB_ICONERROR);
throw;
}
}</pre><!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<p>Besides being able to set individual pixels quickly, DirectX also provides ways to efficiently blit bitmaps or even use GDI functions to write to a surface.
<hr>
<h2>Implementation</h2>
<p>As mentioned before, all DirectDraw objects are enclosed in the Direct namespace. The fundamental object, Direct::Draw, is responsible for the initialization and the release of the DirectDraw subsystem. It can also be used to set up the cooperation level with Windows. It's easy to add more methods and options to this class, as the need arises.
<!--Yellow background--> <table cellpadding=10 cellspacing=0 width=100%>
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="black">
<pre>namespace Direct
{
// Direct Draw object
class Draw
{
public:
Draw ();
~Draw ()
{
_pDraw->Release ();
}
void SetCoopNormal (HWND h)
{
HRESULT res = _pDraw->SetCooperativeLevel(h,
DDSCL_NORMAL);
if(res != DD_OK)
throw "Cannot set normal cooperative level";
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -