|
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. 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. 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. 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:
There are other functions in the GPE class that have default implementations, but can be overridden for non-default behavior. These functions 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. 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. 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:
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. 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 Notes on the CodeThe source files for the NewGPE and NewGPESurf classes can be downloaded here |