|
|
|
|
This article was originally written for NT Developer (for whom I used to frequently contribute). Alas they are now defunct, and since they paid me for the article, I figure somebody might as well get to read it. My next book will have an entire chapter on printing OpenGL graphics, with coverage for NT and Windows 95. This is the second part of the article OpenGL, Workstation Graphics does Windows. Click here to download the samples.
OpenGL, Metafiles, and Printing Last month, I explained why rumors of OpenGLs death have been greatly exaggerated. This month, I shall explore some of the more advanced supporting features of OpenGL in the operating system. Features present in Windows NT, but not Windows 95, and certainly not available under Direct 3D or any other toy 3D APIs. My first experience with computers was in Mr. Deckers eight grade Algebra I class in 1978. We had an old terminal of some sort that had a huge acoustic coupler that the telephone handset would fit into. Wed place a call to the Board of Education in Louisville, and then yacky-yack the word "ready" would be printed on a huge roll of paper. After an initial program that counted ducks (up to ten!), we had a real Algebra problem; a paramutual betting game. It made for great fun just before Derby Day. We had a lot of fun on that old terminal, and I still have some of that paper with print outs exclaiming that the horse "mammas combat boots" had placed first. From then on, I was hooked on computers, a fever for which I believe there is no cure (although having children can certainly temper it a bit!). I left paper behind and "graduated" to "Trash-80s", the TI-99 4A, and various Commodore and Atari flavors before getting through high school. It wasnt long before I really started to miss something important from that eight grade class room. Hardcopy. I feel pretty confident in saying that with few exceptions any truly useful computer program or application, has the capability to print its results to the printer. At the very least, you can save something in a file, that you can later reformat and dump to a printer. Computers are tools, and these tools often solve problems that are expressed in some form of output. How do you communicate this output to others? Why you invite them all to come and see what your wonderful computer has produced on the monitor . Not! Applications that make use of 3D graphics arent terribly different from applications that perform extensive number crunching or database searching. With the possible exception of games, 3D graphics are typically used to clarify some relationship, or display the output of a great deal of design effort. Just like any other application that produces tangible results, you will often need to produce a hardcopy of these results for distribution, or archival purposes. Asking the user to press "print-screen", then open Word, select paste, then print is a pretty good way to ensure poor reviews of your product. Applications that produce graphics using any API other than GDI under Windows 95, have very few options for printing their display. The usual method is to render to a bitmap (as opposed to the window), and then print the bitmap. A robust approach requires banding, and a lot of memory to get output of reasonable fidelity. An awkward and kludgey approach at best. If your using OpenGL under Windows 95, another approach to consider is to use OpenGLs feedback mechanism to produce transformed 2D coordinates that you can then feed to GDI. This actually works for very few general purpose applications, unless your only drawing wireframe images. With Windows NT (starting with 4.0), the situation when using OpenGL is vastly improved. Not only can you render directly into a printers device context, but you can also capture OpenGL commands in an enhanced metafile. Furthermore, color images will be automagically converted to greyscale if your destination printer only supports black and white, such as most common laser printers. Even my old 9 pin Epson produces recognizable output. These two capabilities are actually closely related, as only printer drivers that support enhanced metafile spooling will support OpenGL rendering. Fortunately, this is true for most printers. To check for metafile support, bring up the properties page for your printer. You should see a button labeled "Print Processor", which brings up the dialog shown in Figure 1 below.
Figure 1 - the Print Processor Dialog Some printers, such as my HP Deskjet, will automatically use EMF spooling even when RAW is selected, others such as my older Epson T-750 will require you to bring up this dialog and manually select EMF as the default datatype.
Printing with OpenGL Lets get right to the code for printing with OpenGL. Figure 2 shows the output of our first sample program "metademo". This program takes a texture map of the world (more of a relief map really), and applies it to a sphere. You can use the arrow keys to rotate the globe horizontally and vertically. The globe rotates rather slowly because we are loading the texture map from disk every time we render. The reason for this will be clear when we get to the metafile recording and playback later. The file menu contains the commands, "Record", "Playback", and "Print". Right now, were just going to examine the print option.
Figure 2 - The Metafile Demo Program When you select "Print", the Windows common dialog for printing is displayed and you can select any attached or networked printer. When this dialog is dismissed, the globe is rendered to the printer. Note, I chose a white background instead of black as this usually looks better when printed. The code for metademo is quite lengthy, and I wont go into all the details of the program, or the OpenGL code that draws a texture mapped sphere. For the purposes of this article, I have to assume you already know how to use OpenGL (that would be a series of articles by itself!), and just want the meat of how to render to a printer. The function called when "print" is selected does several things. First, it displays the Windows common dialog to get a destination printer and create a device context for that printer. Once a destination device context is established, GetDeviceCaps() is called to get the dimensions of the page (in device coordinates). A pixel format is then selected for the printer context in much the same way it is for a window device context. One caveat is that you should not try to set the pixel format until after you call the StartDoc/StartPage sequence that is necessary for all Windows printing. Once you have set the pixel format, you create the OpenGL rendering context and initialize it just as you would the window rendering context. This means enabling texture mapping, establishing culling modes, etc. Its good practice to actually use the same function for both the window setup and the printer setup. You also need to initialize your projection, which in this case is orthographic. The function you use for this, ChangeSize() in this sample, can take a width and height argument that you can pass either the window size or page size as appropriate. Once this work has been done, you simply issue your OpenGL rendering commands and they are performed on the printers device context. When finished, be sure and delete the OpenGL rendering context established for the printer, and restore the windows rendering context as the currently active one. Finally dont forget to call EndPage/EndDoc just as you would for any Windows print job, and be sure to free the printers device context. The complete listing for the function PrintGL is listed below.
This function is a bit more generalized than the printing function. To select a pixelformat, we first get a description of the windows pixelformat, then use ChoosePixelFormat() to allow Windows to select a pixelformat for the metafile that most closely matches the pixelformat used for the window. We dont need to worry about Windows selecting a double buffered pixelformat for a metafile. Just as in printing, we call the same OpenGL functions to setup our rendering context, setup our projection (this time with window size instead of page size), and finally call our rendering code. The final step is to delete the OpenGL rendering context we created for the metafile, and restore the windows rendering context. Naturally, we must also close the metafile and delete its handle. The actual OpenGL code the creates the globe is in Render(). This function is fairly short, and is listed below:
We didnt go into the details of this function for printing because for printing the output is captured immediately. When rendering to a metafile, you are storing OpenGL commands and state information for playback later, and there are a few important issues to consider. Most importantly for this sample is the texture state. When rendering to a printer, you are capturing the actual rasterization of the image to hardcopy, which is influenced by whatever values any state variables may contain at the time. When rendering to a metafile it is important to play into the metafile any OpenGL commands that create these initial state conditions. With texturing for example, you will need to capture the actual loading of the texture, not just the use of the texture (which you can get away with when printing). This is why our rendering code here goes through the trouble of actually loading the texture from disk each time it is called. Youd get up to 10 times the speed by loading the texture only once, but then the code would only have worked for the printing example. The ooglTexture2D class by the way is an very useful C++ class I came up with for quickly loading textures from .bmp files. If youve had to write this code yourself in the past, youll probably appreciate this little freebie. How to do texturing is also out of the scope of this article, so well leave that to another episode as well. The code to playback the metafile is virtually trivial. The function PlaybackGL() is listed below. For playback, we dont have to create a rendering context at all, since we already have one active (the windows). All we have to do is open the metafile, and play it into the windows device context. We will need to do a buffer swap since the windows OpenGL pixelformat is double buffered, but that is all. The size of the window, stored in rect, is required. On playback metafiles are automatically scaled to the size of the window they are being played into.
OpenGL "Clipart" If your at all like me, youve jumped ahead and already tried to load the record.emf file into Word, or some other program that supports metafiles as a form of clipart, or native data. Youve also probably been disappointed to find that your "metafiles" seemed to be blank. Unfortunately few Windows applications that support metafiles, also support metafiles that contain OpenGL (you also have to consider that this support is only in NT 4.0 or later, and isnt even planned for Windows 98). Essentially, a metafile is nothing more than a list of GDI (and now OpenGL) commands that can be "played" into a device context. If that device context does not have a valid pixelformat and OpenGL rendering context selected and created, then any OpenGL commands will be ignored. Thus, unless an application specifically checks the metafile for OpenGL and then accommodates by creating a rendering context, OpenGL will not be supported by the application, even though it is supported by the OS. Since enhanced metafiles are supported natively by the clipboard, this would be an ideal way to cut and paste OpenGL images from one application to another. Until more applications take OpenGL into consideration, this isnt likely to be pervasive anytime soon. To make it easier to include OpenGL aware metafile support, Ive included an OCX called metagl that you can embed in your applications. The metagl OCX will load any enhanced metafile, including those that contain OpenGL. The metagl OCX was created with Visual C++s Appwizard, and I will spare you the details of creating a skeleton OCX. The most important method of the OCX, LoadMetaFile() is listed below. The function takes the filename of a metafile to load as its only parameter. First is checks the value of a member variable m_hMetafile to see if a metafile is already loaded. If not, it loads the metafile and checks to see if the metafile has a pixelformat. If it does, then it contains OpenGL calls. The metafiles Pixelformat is used to select a close matching one for the window, and an OpenGL rendering context is created. Finally, the window of the OCX is invalidated to force a repaint, which will now be based on the loaded metafile. The OnDraw() function, listed below, just simply plays the enhanced metafile if it is loaded. If the metafile does not contain any OpenGL calls, it is still played, but the call to SwapBuffers() is ignored if there is no OpenGL rendering context.
Final Demo You can load the metagl.ocx into the control test container the comes with Visual C++, and manually call the LoadMetaFile method to see the output. Frankly thats about as much fun as debugging someone elses code. To make things easy on you (and myself when testing this stuff), I put together a simple dialog based program that contains the metagl OCX, a Load button, and a text field that displays the metafile filename and path. The Load button displays the file selection common dialog and you can select any enhanced metafile (.emf file) for loading. The message handler is short and sweet.
Figure 3 shows the MetaView program in action. In this instance, I selected the record.emf file that was created by MetaDemo. You can also load any enhanced metafile, with or without OpenGL.
Figure 3 - MetaView program output.
Another problem I came up against was a lack of pre-existing enhanced metafiles on my machine to test this with. Microsoft Office ships with hundreds of windows metafiles (.wmf extension) for use as clipart, but these are not the same as enhanced metafiles. One valuable resource I found on the Internet was a handy little utility called "Metafile Companion", which can be found at: http://www.companionsoftware.com/Products/MetafileCompanion/index.html. This little dodad will load any windows metafile (.wmf), and will convert it to an enhanced metafile (.emf). I turned lots of the clip art that comes with Office into enhanced metafiles with ease. It will also load enhanced metafiles for viewing or conversion, but unfortunately doesnt yet support metafiles with OpenGL. Conclusion OpenGL is well supported by Microsoft for delivering technical applications on the Windows NT platform. Although OpenGL is portable, any application ported to Windows must take into consideration the many Windows specific idiosyncrasies. Printing can be one of the most daunting aspects of porting code to Windows, as well as one of the most complex portions of a Windows application. With native support for OpenGL printing, Microsoft has made it very easy to write full featured and robust OpenGL based applications for Windows NT. Support for OpenGL in metafiles lays the ground work for 3D graphics programs that are fully integrated with other operating system features such as clipboard and OLE support. As Windows 95/98 and NT 5.0 gradually evolve to a single operating system based on NT, we can only expect that these features will become more pervasive in the future. |