About DirectX
The DirectX library is one of the most evolving COM libraries Microsoft has put out to date. Almost every version iteration has significant changes to the API. Classes and methods come and go all the time. This is a side-effect of Microsoft trying to keep up with the raging 3D rendering technology and the pursuit for delivering ever-faster 3D games.
For quite some time, the DirectX library was reserved for fullscreen 3D games only. However, over
time the graphics cards have gotten so clever that they are able to accelerate most operations
on a windowed canvas as well.
The DirectDraw API was introduced in one of the early versions of DirectX and added
some capability to mix GDI with an accelerated 3D surface.
This was most notable in the GetDC method which would transform a 3D
surface into a compatible GDI Device Context and allow large parts of the GDI
functions (DrawText(), FillRect() etc) to work on it.
Unfortunately many of the operations weren't accelerated on the card and thus using the
APIs could be painfully slow.
During the succeeding versions, the DirectDraw API was slowly faded out of DirectX altogether and eventually
mixing GDI & 2D with 3D just wasn't possible.
Then, almost by magic, GetDC was re-introduced
in DirectX 9 - most likely because Microsoft needed to take up the battle with the recent developments
in the Mac OS desktop and its superb integration with OpenGL. It also turns out that by now most graphics
cards are capable of accelerating windowed operations just fine.
Using DirectX
I'm not going to present a tutorial on creating a DirectX application. You can find plenty of good tutorials on the web. Since DirectX is so burgeoning, it is important to read samples that are written for version 9, though.While setting up DirectX in windowed mode is trivial, there are several implications. Most important, you inherit the display format of the Windows desktop. You can request a more suitable format for your surfaces, but most likely you will face trouble when trying to mix it with GDI. This generally means that you can only use the scarce 3D operations, the GDI integration - but may not be able to
LockRect the surface to do pixel manipulations.
The general procedure for mixing DirectX with a GDI window in my sample is:
- Setup DirectX in windowed mode and take over a desktop window
- Create an offscreen backdrop surface with the dimensions and display format of the window
- Use
Surface::GetDCto get a GDI version of the surface and paint the entire window on the DC - Prepare the sprites that will animate on the surface
- Enter the render loop...
- Copy backdrop surface to display
- Render sprites
Creating a backdrop
We create a copy of the contents of the desktop window to a DirectX surface, because while we are in the render loop we'll continuously copy the backdrop back to the render surface. A normal DirectX application would callDevice::Clear to reset the screen
to black, but we'll give the impression that the desktop window is all intact while we're
animating sprites on the window.
With this illusion we turn a normal GDI desktop window into a 3D rendered target surface without the user even knowing. We can safely animate 3D objects on the DirectX surface and then silently restore the GDI window when we're done.
Because of various limitations on the size of surfaces in graphics cards, we'll create the backdrop surface in System Memory. This allows an arbitrary size, but adds the risk of blit accelerations being unsupported on some 2nd generation cards.
There are several approaches to making a copy of the GDI window. The reason that I mentioned
the GetDC method earlier is that without this method it gets slightly complicated and
we would have to deal with the myriads of display formats. Instead we can now call this method,
retrieve a Windows HDC and use the WM_PRINTCLIENT message
to allow the desktop window to fill out the surface.
Preparing sprites
The idea with the render engine in the sample, is that it takes elements from the window (a header text or an icon) and animates them. Animations are displayed as short page transitions (fades or zooms in and out).The DirectX 9 requires us to treat animating sprites in strictly 3D geometry. We'll create a polygon (wireframe) and put a texture on it. We can animate the sprite simply by stretching or rotating the coordinates of the polygon. DirectX will make sure to paint the texture in the correct perspective as long as we make an effort to calculate all coordinate changes on all 3 axes (x, y and z) in 3D space.
Many older generation 3D graphics cards have some nasty restrictions when it comes to creating texture bitmaps.
Some cards only allow texture dimensions of the power of 2 (32, 64, 128...), some restrict
the size to 256 pixels or less, and many only allow textures to be square in size.
To overcome these problems, each sprite may be constructed from several square textures (tiles).
This makes it slightly more complicated to manage properly, but with a little tinkering we should
be fine. Each tile contains a separate polygon and is mapped out with a 32, 64 or 128 pixel sized
square texture on it. We keep a 1:1 ratio of the backdrop image and the texture, so we can put
the sprite exactly over the original graphics without scaling the bitmap data.
Animating
Drawing the sprites are done using the standard DirectXDrawPrimitive call and with
the use of vertex buffers. Coordinate transformations and rotation is done in code, rather than allowing
the Direct3D accelerated matrix calculations. This is needed because we want to be absolutely sure that we
can place a sprite exactly over the existing graphics - without tearing or pixel-compression errors.
Each animation is described in a simple from/to state diagram. A sprite will animate from a certain position or rotation back to its original position. The animation is scheduled to take a number of milliseconds. During this period each frame animated will smoothly interpolate transformations (horizontal and vertical movement) and rotations from one end-point to another. When no more animations are scheduled, the DirectX layer is shut down and normal GDI processing takes over.
It is important to note a severe limitation of this model: that while we're animating, the normal
WM_PAINT processing is short-circuited and Windows is not given the chance to update
the Window canvas. The immediate effect is that any text caret blinking or keypress will not display
on the screen. Therefore, we cancel the animations straight away if the user presses a mouse-button
or key.
It would be possible, but probably slow, to repeat the process of generating the backdrop
surface (background) from time to time, allowing GDI and WM_PAINT messages
to shine through.
The frame rate is currently controlled by Windows and its message pump. While normal DirectX
animations attempt to animate frames whenever the message pump is idle, this sample simply
uses an InvalidateRect call to have Windows generate WM_PAINT
messages at a fast pace. Seems to work satisfactory on the Windows versions I've tested so far.
Notes
UIAnim.cpp. The code is being called from
the WM_PAINT message handler located in the UIControls.cpp
file.To run the sample you need to have DirectX 9 installed. If no animations show up, the application has deemed your graphics card too slow to provide smooth animations.
Source Code Dependencies
Microsoft Visual C++ 6.0Microsoft DirectX 9 SDK
Download Files
![]() | Sample (92 Kb) Source Code (127 Kb) |
