Introduction to Writing Windows CE Display Drivers |
作者:ntelligraphics 來源:intelligraphics.com 時間:2004年12月18日 6:38 |
IntroductionFor many developers, writing display drivers can be an intimidating task. Windows CE, Microsoft's operating system targeted toward embedded devices, is no exception. Fortunately, Microsoft provides C++ classes that can be used to simplify writing display drivers. As convenient as these classes are, there are improvements than can be made to them that can further simplify display driver development and make display drivers more portable across Windows CE devices. Increased portability provides additional value for display drivers as Windows CE moves to different platforms with various display requirements, such as Internet appliances, video conferencing systems, and game consoles. Even within a product family, such as HPCs, this portability is valuable as each generation of product adds more features and functionality and moves to different display hardware. This article reviews the fundamentals of graphics, display hardware, and Windows CE display driver development, including the display driver C++ classes. It then explores improvements to the C++ classes that will simplify display driver development. At the end, you will have a set of improved display driver classes that should be simple enough to quickly and easily get running on any device, yet complete enough to support any additional hardware features. Of course, this doesn't prevent you from making your own improvements as well. Fundamentals of Graphics and Display HardwareFor those developers not familiar with the principles of graphics hardware, it is worth providing a quick overview of the fundamentals of graphics and display technology in the context of a typical Windows CE display device. Graphics hardware is primarily composed of a display controller, a frame buffer and a DAC. The display controller handles access to the frame buffer by the CPU, generates the video timing for the current display mode, and fetches the display data from the frame buffer to be displayed. The frame buffer stores the display data. The DAC, or digital to analog converter, converts the digital display data to an analog format to be sent to the monitor. This is necessary since most monitors expect analog voltage levels that represent the intensity of each color channel. The diagram below demonstrates the relationship among these components. It should be noted that as a result of the high level of integration achieved in today's silicon, all three components can be found in a single chip from some manufacturers.Because most monitors cannot maintain the display image on their own, the display must be continuously refreshed. For the standard non-interlaced display, this is done one line at a time in sequence. Interlaced displays, such as televisions, refresh all the even lines first, then all the odd lines, sometimes resulting in display flicker. The time after one display line is completed and before the next one is started is called the horizontal blanking period. The time after a complete display refresh is done and until the next display refresh is started is called the vertical blanking period. These blanking periods are the result of monitor technology requirements. Cathode ray tubes, or CRTs, used in monitors direct a beam along the face of the tube. This beam causes phosphors on the tube to emit light, which you see as the display. After the beam traces a line across the tube, it needs to retrace to the beginning of the next line. While it is retracing, the beam is shut off, or blanked. The same applies for a complete display with vertical retracing and blanking. The display itself is organized in rows and columns. Each element, or pixel, in the display is stored in the frame buffer, where between one and thirty-two bits are used per pixel. The color of a pixel is represented by the intensity of each of the red, green, and blue color channels. These intensities are represented directly by the pixel data in modes where a pixel is 16 bits or more. When a pixel is 8 bits or less, the pixel data usually represents an index into a color look-up table. This look-up table stores the red, green, and blue intensities for each index in the palette of currently available colors. In this case, the look-up table values are passed to the DAC. In Windows, bitmaps are used to represent graphical images. A bitmap typically contains the pixel data for the image, the dimensions and color depth of the image, and possibly the palette for the image, among other things. Information in a Windows bitmap is not directly available, but is abstracted and only accessible through calls to Win32 APIs. A Windows CE display driver typically sees bitmaps in a format called a surface. These surfaces contain the information of the bitmap, but in a format that is directly accessible by the display driver. If you require additional background information before getting started on display driver development, there are plenty of good video and graphics books available to cover the basics of these topics in more detail. Display Driver OverviewThe Graphics Device Interface, or GDI, is the system component that loads and calls the display driver. It is also where bit block transfers, or blits, and drawing related Win32 APIs are handled. In Windows CE, GDI is contained in the Graphic, Windowing, and Event Subsystem, or GWES. An interesting thing to note is that in Windows CE, GWES is an optional component, making the display driver optional as well. It is therefore possible to make Windows CE devices that are "headless", that is, with no graphic display interface. This is useful to be aware of when writing other device drivers since you may not be able to depend on having access to graphical objects, such as dialog boxes, to communicate with the user. The display driver is a native driver in Windows CE. This means that it has a custom interface that provides a standard set of functionality for display devices. As a matter of fact, this display device driver interface, or DDI, is a subset of the Windows NT DDI. One of the major differences is that all Windows CE display drivers have the same level of functionality, so there is no "punting" of complex operations back to GDI. A windows CE display driver has only one function that it is required to export, DrvEnableDriver(). This function is called by GDI when the driver is loaded and is responsible for returning pointers to the other DDI functions. Writing display drivers using the DDI directly can involve a lot of duplication of code from one display driver to the next. To greatly simplify and speed up development, Microsoft has provided a set of C++ classes that contain most of the base code that is required from any display driver. What this means for the display driver developer in Windows CE is that code only needs to be added for functionality that is specific to the device, such as initialization and mode setting, hardware cursors, and accelerated blits and line drawing. You can really appreciate how much time these classes save if you have ever done display driver development for other Windows operating systems. In a typical Windows CE display driver, the most time consuming tasks usually involve device initialization and mode setting. This is due to the fact that Windows CE does not support executing a video BIOS, where initialization and mode setting would normally be handled. The one exception to this is the CEPC platform, where the bootloader, LOADCEPC, does make video BIOS calls to initialize and set the mode for the display device. This is possible since LOADCEPC is actually a DOS application. However, for the typical Windows CE system this is not an option. Microsoft makes several recommendations for display hardware, intended to improve driver performance and simplify development. The most important of these is that the display hardware uses a linear frame buffer. What this means is that the display memory needs to be contiguous, with this entire block of memory being directly accessible by the CPU. Therefore, access to display memory through bank selection, should not be used. Another important recommendation is not to use bit planes. This is when each color channel or intensity component is in a separate buffer. Display memory bank selection and bit planes exist as part of legacy VGA support, with most newer VGA controllers now also including support for a linear frame buffer as well as for packed pixel, or non-planar, display modes. It is important to follow these recommendations if you wish to take advantage of the default blit and line drawing functions provided in the C++ classes. Graphics Primitive Engine ClassesAs previously mentioned, Microsoft provides a set of C++ classes called the Graphics Primitive Engine, or GPE, that simplify display driver development in Windows CE. The most important of these is the GPE class, which represents the display device. It is a pure virtual class, which means that it must be derived from and that certain base class functions must be implemented. Since source code is not provided in Platform Builder for this class or how the DDI functions interface with it, it is useful to review these functions that require implementation. They are:
Besides these, there is a set of functions in the GPE class that are described as being required for DDHAL support. The implication is that these functions are part of the DirectDraw hardware abstraction layer, or HAL. Since the display driver model for version 2.0 was developed well ahead of the release of DirectDraw for Windows CE, these functions were probably included as a best "guess" of the display driver requirements for DirectDraw support. As it stands, these functions are only part of the story for DirectDraw support and are not required for a standard display driver so I will not specifically discuss them here. I will add, however, that these functions do provide additional capabilities that, if properly exported, could be used by applications to bypass GDI for improved performance. If your display device is VGA based, you may choose to derive your device class from the GPEVGA class rather than GPE. GPEVGA is itself derived from GPE, and is still pure virtual. It contains function implementations for SetPalette() and InVBlank(), using standard VGA accesses. It also contains I/O mappings to standard VGA registers. Beyond this, you will still need to implement the remaining functionality required in the GPE base class. There is a variation of the GPE based driver, called a dirty rectangle driver. Its purpose is to allow a display driver based on the GPE class to work with a banked memory display device. An example of this is the sample driver VGA8BPP. It is a very inefficient driver from both a system resource and performance standpoint and should really only be used if a linear frame buffer is not available. It works by creating a frame buffer in system memory that is the size of the display. This is very inefficient from a system memory standpoint since memory is usually a critical resource in a Windows CE device. All drawing by the GPE default drawing functions is done into this system memory frame buffer. These areas where drawing is done, or dirty rectangles, are then copied to the display memory on a bank by bank basis. The inefficiency here, of having to do the drawing in two steps, is obvious. Another important class is GPESurf, which is used to represent surfaces located in system memory. If the driver supports the creation of surfaces in video memory, then the GPESurf class will need to be derived from. However, these changes are fairly minor and mostly require capturing of the Node2D information, mentioned later, and setting the flag to indicate that this surface is located in video memory. The last class I'm going to mention is Node2D. This class is used for managing video memory surfaces. An instance of a Node2D object is initially created that contains the pixel dimensions of the total video memory. Allocation of video memory for surfaces, including the primary display surface, is then handled through this Node2D instance. Within the class itself, the video memory is managed as a list of free and allocated rectangles, with all coordinates handled in pixel dimensions. The limitations of this class are that video memory surfaces cannot be created that are wider than the pixel dimensions supplied when the instance of the class was created and that video memory surfaces must be the same color depth as the display. It is important to note that the GPE classes haven't changed very much since version 2.0, so the information presented here should be useful and accurate across many different versions of Windows CE. Improving the Display Driver ClassesAs useful as these C++ classes are for writing display drivers in Windows CE, it is clear that additional base functionality can be included to further simplify display driver development. The two classes to be improved upon are GPE and GPESurf. Since the source for these classes is not provided, we need to derive classes from them in order to improve them. These new classes are called NewGPE and NewGPESurf. A good starting point for building the NewGPE class is configuration and initialization. The NewGPE class is designed to require as little modification or overriding of functions as possible. The class constructor, NewGPE(), provides default initialization for the variables. Currently, the only variable that would need to be changed here is m_bIsVGADevice, if a non-VGA device is being used. This variable is used to enable mapping of the VGA registers as well as selection of some default VGA functionality, done elsewhere in the driver. To allow these functions to be hardware independent, mode information used by NumModes() and GetModeInfo() is taken from the variable m_gpeModeTable, a table of the supported display modes. This variable needs to be initialized by the driver developer based on the modes that will be supported by the driver. It is statically defined in the provided source file NEWGPE.CPP. However, this could be changed so that the table is instead loaded from the registry or a file when the driver is first run. The SetMode() function has been written to require no additional modification. It handles all the hardware independent mode initialization. The first step is to verify the selected mode from the list of supported modes. Once this is complete, the hardware dependent mode initialization function, ModeInit(), is called. The ModeInit() function must be overridden or modified to initialize the specific hardware and setup certain variables used elsewhere in the initialization process. These variables are:
The base surface class, GPESurf, must be derived from in order to support the creation of video memory surfaces. Since default management of video memory is already defined within the NewGPE class through the Node2D object m_p2DVideoMemory, it makes sense to include support for the creation of video memory surfaces in our derived surface class, NewGPESurf. This is accomplished by providing a constructor that sets the video memory flag and maintains the Node2D instance for the video memory of this surface. The destructor is then responsible for deleting the saved Node2D instance, thus freeing the video memory occupied by this surface. This is all that needs to be done for the NewGPESurf class. The AllocSurface() function in the NewGPE class is responsible for handling the creation of system and video memory surfaces. It uses m_p2DVideoMemory, the Node2D object previously created in the SetMode() function, for allocating video memory. The surface itself is then created using our derived NewGPESurf class. As was previously noted, video memory surfaces can only be created in the same pixel format as the current display mode due to limitations in Node2D. System memory surfaces are created using the base GPESurf class. The most straightforward places to provide default implementations are the line drawing and blit functions. For BltPrepare(), all that needs to be done is to specify the default GPE blit function. For BltComplete(), nothing needs to be done. As with BltPrepare(), the function Line() can simply specify the default GPE line drawing function. For display devices that handle cursors in hardware, the cursor functions SetPointerShape() and MovePointer() need to be changed or overridden to include this support. Since this functionality is not available in all display hardware or only in certain display modes, I have provided software cursor support in the base implementation of these functions. See the provided source file CURSOR.CPP for the details of software cursor support. It should be noted that software cursor management required changes in other sections of the driver as well. Searching the provided source files for the variable m_bHWCursor, the flag for selecting between software and hardware cursor, will highlight these additional changes. For SetPalette() and InVBlank(), the VGA implementations are well defined and are even provided in the GPEVGA class. Therefore, default VGA support is included in the NewGPE class as well. For non-VGA display devices, support will have to be added to these functions. For reference, see the implementation of these functions in the provided source file MISC.CPP. As you have seen, the NewGPE class requires only the ModeInit() function to be modified or overridden when using a VGA display device that supports a linear frame buffer. For non-VGA display devices, the only additional functions that need to be modified are SetPalette() and InVBlank(). In addition, for any display device type, the m_gpeModeTable variable needs to be initialized with the modes supported by the driver. It would be difficult to make driver development any simpler than this. ConclusionAs you have seen, the Windows CE display driver model and the Microsoft provided C++ classes are a good starting point for display driver development. The improvements made here to these C++ classes can further simplify this development process, while still allowing for the same level of flexibility as provided in the original classes. As well, these changes should make the classes more portable as you go from project to project. However, it is important to keep in mind that this discussion is by no means comprehensive in its coverage of display driver related development in Windows CE, and that there are plenty of other topics for you still to explore.ReferencesMicrosoft Developer Network Microsoft Windows CE Platform Builder 2.11 and 2.12 Notes on the CodeThe source files for the NewGPE and NewGPESurf classes can be downloaded here |