How to render 2D with DirectX? Let's try a rotozoomer.
I once wanted to show a colleague a piece of code I had written some time ago back when MS-DOS 5.0 and 486's were the big deal. It was a small assembly routine that loaded an image and rotozoomed it, like the effect seen in Future Crew's Second Reality. Only problem is that the program would no longer run under Windows XP or newer because of the custom VGA video mode that was used. So, I thought how hard could it be to write it for Windows and still be realtime? This is a classic nerd snipe. I couldn't stop working on the concept and no other work was getting done!
What is a Rotozoomer?
This classic demo effect is very simple to implement. The Flipcode archives contain a great rotozoomer how-to. Or, check out the video sample below.
Implementing Quick and Dirty Using Slow GDI Rendering
I knew GDI performance wouldn't be great, but it's astounding just how poor its rendering performance is. I couldn't get much better than 0.5 frames per second. There's got to be a better way, so I took a dive into DirectX.
There used to be a subset called DirectDraw, discontinued since DirectX 8, that is supposed to allow for direct to video 2D rendering. It turns out Direct3D can perform this functionality just as well, even if you're not rendering anything in 3D. I've included a working example in this blog post.
Implementing Using Direct3D
Linked below is a download of the source and compiled Rotozoomer example written in Visual C++ 2010 as an MFC-based app. I used C++ for direct access to DirectX COM-based API. DirectX 9 was used for simplicity, though I could've written it against 10 or 11. Rendering speed on the relatively newish 64-bit PCs I've tested will render ~60fps maximized at 1280x1024.
Here's a sample:
System requirements to compile demo solution:
- Visual Studio 2010 (Visual Studio Express edition downloads).
- Install latest DirectX SDK (June 2010 or newer).
System requirements to run demo app:
When Developing With DirectX
- DirectX is based on COM, so be sure to release any pointers created.
- DirectX 9 provides a d3dx9.lib, which simplifies much of the COM initialization and object instantiation with commonly used global functions. You don't have to use it, though.
- Enable debug mode in the DirectX Control Panel of the DirectX SDK to help trace unreleased pointers in the debugger log.
Rundown On My Implementation Strategy
- Use MFC for quick and dirty framework.
- Create CView that will be used for rendering Direct3D in a window.
- Implement PreSubclassWindow() to initialize the Direct3D context, a dummy device, and loads the texture. The dummy device is initialized to minimal resolution so we can load the texture into memory. The next resize event will adjust the device to actual resolution.
- OnDestroy() handler must release COM pointers.
- OnSize() handler resets the Direct3D device matching the size of the window.
- OnPaint() and OnEraseBkgd() handlers do nothing. Instead, idle time will be used to continuously rerender the display.
- Use SetTimer() and OnTimer() handler to smoothly animate by adjusting the rotozoomer coordinates at a steady rate.
- Override CWinApp::OnIdle() to call Render() function, then return TRUE so that OnIdle() will be called again every free chance Windows will allow.
- The Render() function will lock the offscreen surface, draw directly to its buffer, unlock, then copy the surface to the device for rendering.
Lessons Learned Writing This Demo App
- Gone are the days of speeding up computations with fixed point integer math. Double precision floating point was faster than fixed point integer math.
- Target 64-bit platforms where possible. A 64-bit build renders about 3x faster than 32-bit on the same machine.
- Due to dependencies in d3dx9.lib, the specific version of DirectX used to compile the project needs to be present on target PCs running the executable. For example, Windows 7 ships supporting DirectX 9 through 11, but these major versions have been revised since release. If an update is needed, the program will complain of missing file "d3dx9_NN.dll", where 'NN' is an arbitrary revision number. Fortunately, the DirectX runtime update doesn't typically require a reboot.
What Else Can You Do With This?
Since rotozooming is essentially translating lines scanned across a texture to the display, you could make some interesting effects just by altering how each line is scanned. For example, you could scan a sine wave across the texture to warp the rendered output.
You could create a 3D perspective correct floor or ceiling effect by modifying the scale of each scan line to a hyperbolic function.
Hopefully, I've inspired you to try something new and learn DirectX for realtime graphic applications. But, I've only just scratched the surface on what can be done. Use it for gaming, enhanced user interfaces, or attention grabbing visuals in otherwise boring applications.
|Rotozoomer sample app (x64)||2.14 MB|
|Rotozoomer sample app (32-bit)||1.92 MB|
|Rotozoomer sample source||145.01 KB|
About Shawn Poulson / Exploding Coder
Software developer and designer
I'm a software developer in the Philadelphia/Baltimore metro area that loves web technology and creating compelling and useful applications. This blog was created to showcase some of my ideas and share my passion and experiences with others in the same field. Read more...