Introduction
For 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 Hardware
For 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 Overview
The 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 Classes
As 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:
NumModes - Returns the number of display modes supported by the display driver.
GetModeInfo - Returns information about a specific display mode, such as display width and height in pixels and number of bits per pixel. This function should handle the number of display modes returned by the NumModes() function. The first mode entry in the list of supported modes is always the one selected for configuration when SetMode() is later called.
SetMode - Sets the display mode. This can be the most time consuming function to write in the entire driver, especially for VGA controllers, since configuration of the display device can be a fairly involved process.
AllocSurface - Allocates a surface, which is just a block of system or video memory to store pixel data. The GPESurf class can be used to represent surfaces in system memory. To represent surfaces in video memory, the GPESurf class must be derived from. The GPESurf class is discussed in more detail later.
SetPointerShape - Sets the cursor bitmap and cursor hotspot.
MovePointer - Moves the cursor.
BltPrepare - Called before the blit operation is performed. It allows the driver to setup the blit hardware for performing the operation, if it is supported. It must return the actual function to be called to perform the blit operation, which can be the default blit function provided in the GPE class.
BltComplete - Called after the blit operation has been performed. It allows the driver to do any cleanup required after the blit operation, if necessary.
Line - Called before and after a line drawing operation. When it is called before the line drawing is done, the function can setup the line drawing hardware for performing the operation, if it is supported. It must return the actual function to be called to perform the line drawing operation, which can be the default line drawing function in the GPE class. When it is called after the line drawing is done, the function can do any cleanup required after the line drawing operation, if necessary.
SetPalette - Sets the palette. This only applies for modes that support a palette, which is typically 8 bits per pixel or less.
InVBlank - Indicates if the display update is in the vertical blanking period. This is useful for reducing an animation problem known as tearing, where the display memory update is not in sync with the display refresh on the monitor.
There are other functions in the GPE class that have default implementations, but can be overridden for non-default behavior. These functions are:
IsPaletteSettable - Returns TRUE if the palette is settable or FALSE if the palette is fixed or if the mode doesn't require a palette, such as 16-bit or 24-bit per pixel modes. The default implementation checks the Bpp field of the m_pMode variable and returns TRUE if it equals 8, otherwise it returns FALSE.
GetGraphicsCaps - Indicates if the display driver supports additional graphics features. The default implementation returns 0.
ContrastControl - Receives commands related to contrast control, such as getting and setting the current contrast value. It is useful if the display is a LCD panel. The default implementation does nothing and always returns 1.
PowerHandler - Receives power on and power off notification. The function can be used to suspend or resume power to the display device. As with any driver that receives power notifications, it is important to note that this function should not make any calls that could cause the thread to block as it is the only thread running at the time it is called.
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 Classes
As 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.
NewGPE::NewGPE()
{
// Flags for hardware features
//
m_bIsVGADevice = TRUE; // default to VGA device
m_bHWCursor = FALSE; // default to software cursor
m_b555Mode = FALSE; // default to 5-6-5 mode for 16Bpp
// NOTE:
// The following data members are modified to their final values
// by the default implementation of SetMode and shouldn't need to
// modified here or in ModeInit.
//
m_pPrimarySurface = NULL; // pointer to primary display surface
m_nScreenWidth = 0; // display width
m_nScreenHeight = 0; // display height
m_pMode = NULL; // pointer to information on current mode
m_p2DVideoMemory = NULL; // pointer to video memory manager
m_pLAW = NULL; // pointer to linear access window
memset(&m_ulBitMasks[0], 0, sizeof(m_ulBitMasks)); // bit masks
// NOTE:
// The following data members MUST be modified to their final
// values by the display hardware specific function ModeInit.
//
m_nLAWPhysical = 0; // the physical address of the linear access
// window for accessing the frame buffer
m_nLAWSize = 0; // size of linear access window
m_nVideoMemorySize = 0; // size of video memory, which can be different
// than the linear access window
m_nVideoMemoryStart = 0;// offset within the linear access window to the
// start of video memory (usually is 0)
m_nScreenStride = 0; // number of bytes per display line
}
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.
int NewGPE::NumModes()
{
// count the number of entries in the mode table
BOOL bDone = FALSE;
int nIndex = 0;
while (!bDone) {
if (m_gpeModeTable[nIndex].Bpp==0) {
// no more entries in the table
bDone = TRUE;
}
else {
// count entry and go to next entry
nIndex++;
}
}
return nIndex;
}
SCODE NewGPE::GetModeInfo(
GPEMode *pMode,
int modeNo )
{
// make sure that the mode is valid (index is zero based)
if ((modeNo<0) || (modeNo>=NumModes()))
return E_INVALIDARG;
// get data from mode table
*pMode = m_gpeModeTable[modeNo];
return S_OK;
}
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:
m_nLAWPhysical - This specifies the physical address of the linear access window of the display device for accessing the frame buffer.
m_nLAWSize - The size of the linear access window.
m_nVideoMemorySize - The total amount of video memory available.
m_nVideoMemoryStart - The offset of video memory in the linear access window. This is usually 0.
m_nScreenStride - The number of bytes per display line.
m_bHWCursor - Flag to indicate support for hardware cursor, which is used to bypass existing software cursor support. The actual implementation for hardware cursors still would need to be included.
m_b555Mode - Flag to indicate that the 16-bit mode uses 5 bits each for red, green, and blue.
m_bIsVGADevice - Flag to indicate that the device is VGA based. This should actually be cleared in the constructor to prevent mapping of VGA registers, but at this point is still useful in bypassing default VGA implementations for SetPalette() and InVBlank().
Once the hardware specific initialization is completed in ModeInit(), the SetMode() function continues the hardware independent mode initialization. The m_pMode, m_nScreenWidth and m_nScreenHeight variables, used by other functions, are set for the new display mode. Then a pointer, m_pLAW, to be used later for accessing video memory, is mapped to the physical address of the linear access window of the display device. To do this, calls to the VirtualAlloc() and VirtualCopy() functions are required. See the Platform Builder help for more information on these functions. Next, a Node2D object, m_p2DVideoMemory, representing all of available video memory is created. This Node2D object manages video memory for the AllocSurface() function, which is then called to create the primary display surface, m_pPrimarySurface. Finally, the bit mask is initialized and the default palette is setup. It may not be clear within this function, but there are really two different 16-bit display modes in common use. One mode uses 5 bits each for red, green and blue, while the other mode uses 5 bits for red and blue and 6 bits for green. The mask values are used by GDI to generate the correct pixel value to represent a particular color. The flag m_b555Mode is used in the driver to determine which 16-bit display mode has been set in the hardware when initializing the mask values. For modes other than 16-bit, this flag is ignored. It is worth noting that the bit mask created here, m_ulBitMasks, is also used by the DDI function DrvGetMasks() to return pixel formatting information.
SCODE NewGPE::SetMode(
int modeId,
HPALETTE *pPaletteHandle )
{
int nModeNum = 0;
GPEMode gpeMode;
// get mode entry that matches modeId
BOOL bDone = FALSE;
int nNumModes = NumModes();
while (!bDone) {
if (nModeNum>=nNumModes) {
// failed to find matching mode entry
bDone = TRUE;
}
else if ((GetModeInfo(&gpeMode, nModeNum)==S_OK) &&
(gpeMode.modeId==modeId)) {
// found matching mode entry
bDone = TRUE;
}
else {
// check next entry
nModeNum++;
}
}
// check if mode number is valid
if (nModeNum>=nNumModes) {
return E_INVALIDARG;
}
// pass mode info to the hardware specific initialization
// function
SCODE scInit = ModeInit(&m_gpeModeTable[nModeNum]);
if (scInit!=S_OK) {
// something failed here, don't go any further
return scInit;
}
// verify parameters ModeInit is required to initialize
if ((m_nLAWPhysical==0) || (m_nLAWSize==0) || (m_nVideoMemorySize==0) ||
(m_nScreenStride==0)) {
// ERROR: ModeInit failed to properly initialize some data
// members that are required. These data members need
// to be initialized for this function to work.
return E_FAIL;
}
// continue with hardware independent initialization
//
// Using the following values initialized by ModeInit:
// * m_nLAWPhysical
// * m_nLAWSize
// * m_nVideoMemorySize
// * m_nVideoMemoryStart
// * m_nScreenStride
// * m_bHWCursor (optional)
// * m_b555Mode (optional)
//
// Initialize the remaining values:
// * m_pMode
// * m_nScreenWidth
// * m_nScreenHeight
// * m_pLAW
// * m_p2DVideoMemory
// * m_pPrimanrySurface
// * m_ulBitMasks
//
// And don't forget to initialize the palette parameter
// pPaletteHandle.
// initialize remaining values
//
m_pMode = &m_gpeModeTable[nModeNum]; // current mode
m_nScreenWidth = m_pMode->width; // current display width
m_nScreenHeight = m_pMode->height; // current display height
// generate pointer to linear access window
// (can't address physical memory directly, but need to
// create and map a pointer)
m_pLAW = (unsigned char *) VirtualAlloc(NULL, m_nLAWSize, MEM_RESERVE, PAGE_NOACCESS);
BOOL bCreateLAW;
if (m_nLAWPhysical<0x20000000) { // handle <512MB address differently
bCreateLAW = VirtualCopy(m_pLAW, (LPVOID) (m_nLAWPhysical|0x80000000), m_nLAWSize,
PAGE_READWRITE|PAGE_NOCACHE);
}
else {
bCreateLAW = VirtualCopy(m_pLAW, (LPVOID) (m_nLAWPhysical>>8), m_nLAWSize,
PAGE_READWRITE|PAGE_NOCACHE|PAGE_PHYSICAL);
}
// check for error creating LAW pointer
if (!bCreateLAW) {
return E_FAIL;
}
// create Node2D instance (for managing video memory)
m_p2DVideoMemory = new Node2D(m_nScreenWidth, m_nVideoMemorySize/m_nScreenStride,
0, 0, 32/m_pMode->Bpp);
// check for error creating Node2D instance
if (m_p2DVideoMemory==NULL) {
return E_FAIL;
}
// create primary surface
SCODE scAlloc = AllocSurface(
&m_pPrimarySurface,
m_nScreenWidth,
m_nScreenHeight,
m_pMode->format,
GPE_REQUIRE_VIDEO_MEMORY);
// check for error allocating primary surface
if (scAlloc!=S_OK) {
return E_FAIL;
}
// initialize bitmasks based on current bits per pixel
memset(&m_ulBitMasks[0], 0, sizeof(m_ulBitMasks));
if (m_pMode->Bpp==16) {
// 16Bpp has two mask options
if (m_b555Mode) { // 5-5-5 mode
m_ulBitMasks[0] = 0x7c00; // red
m_ulBitMasks[1] = 0x03e0; // green
m_ulBitMasks[2] = 0x001f; // blue
}
else { // 5-6-5 mode
m_ulBitMasks[0] = 0xf800; // red
m_ulBitMasks[1] = 0x07e0; // green
m_ulBitMasks[2] = 0x001f; // blue
}
}
// create a default palette, if necessary
if (pPaletteHandle!=NULL) {
// create palette
switch (m_pMode->Bpp) {
case 24:
case 32:
*pPaletteHandle = EngCreatePalette (PAL_BGR, 0, NULL, 0, 0, 0);
break;
case 16:
*pPaletteHandle = EngCreatePalette(PAL_BITFIELDS, 0, NULL, m_ulBitMasks[0],
m_ulBitMasks[1], m_ulBitMasks[2]);
break;
case 8:
*pPaletteHandle = EngCreatePalette(PAL_INDEXED,256,(ULONG *)m_rgbIdentityPal,0,0,0);
SetPalette(m_rgbIdentityPal, 0, 256); // palette needs to be set here
break;
case 4:
*pPaletteHandle = EngCreatePalette(PAL_INDEXED,16,(ULONG *)m_rgbIdentityPal16,0,0,0);
SetPalette(m_rgbIdentityPal16, 0, 16); // palette needs to be set
break;
default:
RETAILMSG(1,(TEXT("NewGPE::SetMode Failed to create unknown palette type.\r\n")));
break;
}
}
return S_OK;
}
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.
NewGPESurf::NewGPESurf(
int width,
int height,
void *pBits,
int stride,
EGPEFormat format,
int offset,
Node2D *pNode)
{
// call general GPESurf initialization
Init(width, height, pBits, stride, format);
m_pNode2D = pNode;
if (pNode!=NULL) {
// surface in video memory, set flags and parameters
m_fInVideoMemory = TRUE;
}
}
NewGPESurf::~NewGPESurf()
{
// free video memory if applicable
if (m_fInVideoMemory && (m_pNode2D!=NULL)) {
delete m_pNode2D;
}
}
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.
SCODE NewGPE::AllocSurface(
GPESurf **ppSurf,
int width,
int height,
EGPEFormat format,
int surfaceFlags )
{
SCODE scRet = S_OK;
*ppSurf = NULL;
// check parameters are valid:
// video memory surface must have same pixel format as display
if (surfaceFlags & GPE_REQUIRE_VIDEO_MEMORY) {
// video memory surface must have same pixel format as display
if (format!=m_pMode->format)
return E_INVALIDARG;
}
// check if video memory surface requested
if ((surfaceFlags & GPE_REQUIRE_VIDEO_MEMORY) ||
((surfaceFlags & GPE_PREFER_VIDEO_MEMORY) &&
(format==m_pMode->format))) {
// try allocating out of video memory
Node2D *pNode = m_p2DVideoMemory->Alloc(width, height);
if (pNode!=NULL) {
// get offset into video memory
DWORD dwOffset = (m_nScreenStride * pNode->Top()) +
((pNode->Left() * EGPEFormatToBpp[format]) / 8);
// now create a surface for the allocated video memory
*ppSurf = new NewGPESurf(
width,
height,
m_pLAW + dwOffset,
m_nScreenStride,
format,
dwOffset,
pNode);
if (*ppSurf!=NULL) {
// video memory surface allocated successfully
return S_OK;
}
// failed creating surface for video memory
delete pNode; // free video memory
}
// if we got here then unable to allocate out of video memory
if (surfaceFlags & GPE_REQUIRE_VIDEO_MEMORY)
return E_OUTOFMEMORY; // fail since surface requires video memory
}
// Allocate surface from system memory if not in video memory
*ppSurf = new GPESurf(width, height, format);
if (*ppSurf!=NULL) {
// make sure system memory allocated
if ((*ppSurf)->Buffer()!=NULL) {
// system memory surface allocated successfully
return S_OK;
}
// system memory surface allocation failed
delete *ppSurf;
// just fall through to fail
}
// if we got here then something failed
return E_OUTOFMEMORY;
}
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.
SCODE NewGPE::BltPrepare( GPEBltParms *pBltParms )
{
// use the default blit function
pBltParms->pBlt = EmulatedBlt;
return S_OK;
}
SCODE NewGPE::BltComplete( GPEBltParms *pBltParms )
{
// don't need to do anything here
return S_OK;
}
As with BltPrepare(), the function Line() can simply specify the default GPE line drawing function.
SCODE NewGPE::Line(
GPELineParms *pLineParms,
EGPEPhase phase /* = gpeSingle */ )
{
// use the default line function
pLineParms->pLine = EmulatedLine;
return S_OK;
}
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.
Conclusion
As 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.
References
Microsoft Developer Network
Microsoft Windows CE Platform Builder 2.11 and 2.12