|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionIt has been a while since I have updated this series and I have found some free time to write the next version. In this article, we will take a look at how to write a simple display driver. A display driver is a special type of driver which fits into a framework that is unlike what we have talked about so far in this series. The example driver for this article will show how to write a basic display driver which does not have any hardware associated with it. Instead this display driver will implement graphics to memory and an application will be used to display those graphics. This method was demonstrated in an article I wrote for the C/C++ User's Journal however that article was about extending VMWare to support multiple monitors. This article will only be focusing on display drivers themselves and will not use VMWare but require just your local machine. Display driver architectureThe first place to start is to show the display driver architecture as it is in Windows NT. I will make a comment here that Windows Vista introduces a new display driver model known as LDDM. This is essential in supporting the new Desktop Window Manager however Windows Vista still supports the old display driver model in conjunction with the old Window Manager. This article will not be covering LDDM. The display driver model consists of two pieces, the miniport driver and the display driver. The miniport driver is loaded into system space and is responsible for enumerating devices and managing device resources. The display driver is loaded into session space and is responsible for implementing the actual GDI graphics calls. The driver is responsible for implementing these calls however it wants which can be done in software or deferred to the graphics card itself. The display driver has full control over how a line is drawn or how a transparency effect is implemented. The following diagram shows the Windows display driver architecture:
The display miniportThe miniport driver is loaded into system space and is responsible for managing display device resources and enumerating devices. This driver however uses another driver as its framework which is VIDEOPRT.SYS. This driver exports APIs which your driver will link against and use. Surprised a driver can export APIs? Don't be. Drivers use the PE format and have export and import tables. You can export APIs from your driver and allow other drivers to link against them just like a DLL. In fact all the APIs you use you are just linking against the kernel and other drivers. I will note there is a slight difference between linking against kernel and user mode drivers. If a driver links against a driver that is not currently loaded into memory, that driver will become loaded into memory however the In any case, VideoPrt.SYS exports APIs which your miniport driver will call. This driver does a few things one of which is to implement common code so that video driver writers do not need to rewrite the same code. This code includes video device enumeration between the WIN32 subsystem (WIN32K.SYS) and your miniport. The VideoPrt.SYS will also create the device objects for the display and when you call the initialization routine it will thunk your driver object's entry points to point to VideoPrt.SYS! The VideoPrt.SYS APIs all start with "VideoPort" and the first one you call is " This means that you do not need to directly deal with IRPs in a video miniport. The VideoPrt.SYS will instead handle them, break them down and then determine when you need to be informed about the data. Instead you do deal with what they call "VRP" or "Video Request Packet". This is essentially a mild, broken down version of the IRP in a different data structure. You simply need to return there is no special handling of this data structure as there is with IRPs. The documentation specifies that you should only use the "VideoPort" APIs in a miniport however since this is also just a regular system level driver you can still link against any kernel API you wish and I have done this before. This is not the case with the display driver itself as we will see later. Since we do not have any hardware our miniport driver will be pretty thin and easy. The following code shows how the video miniport /********************************************************************** * * DriverEntry * * This is the entry point for this video miniport driver * **********************************************************************/ ULONG DriverEntry(PVOID pContext1, PVOID pContext2) { VIDEO_HW_INITIALIZATION_DATA hwInitData; VP_STATUS vpStatus; /* * The Video Miniport is "technically" restricted to calling * "Video*" APIs. * There is a driver that encapsulates this driver by setting your * driver's entry points to locations in itself. It will then * handle your IRP's for you and determine which of the entry * points (provided below) into your driver that should be called. * This driver however does run in the context of system memory * unlike the GDI component. */ VideoPortZeroMemory(&hwInitData, sizeof(VIDEO_HW_INITIALIZATION_DATA)); hwInitData.HwInitDataSize = sizeof(VIDEO_HW_INITIALIZATION_DATA); hwInitData.HwFindAdapter = FakeGfxCard_FindAdapter; hwInitData.HwInitialize = FakeGfxCard_Initialize; hwInitData.HwStartIO = FakeGfxCard_StartIO; hwInitData.HwResetHw = FakeGfxCard_ResetHW; hwInitData.HwInterrupt = FakeGfxCard_VidInterrupt; hwInitData.HwGetPowerState = FakeGfxCard_GetPowerState; hwInitData.HwSetPowerState = FakeGfxCard_SetPowerState; hwInitData.HwGetVideoChildDescriptor = FakeGfxCard_GetChildDescriptor; vpStatus = VideoPortInitialize(pContext1, pContext2, &hwInitData, NULL); return vpStatus; } I mentioned before you simply pass the The following shows how I have implemented the video miniport functions: /*#pragma alloc_text(PAGE, FakeGfxCard_ResetHW) Cannot be Paged*/ /*#pragma alloc_text(PAGE, FakeGfxCard_VidInterrupt) Cannot be Paged*/ #pragma alloc_text(PAGE, FakeGfxCard_GetPowerState) #pragma alloc_text(PAGE, FakeGfxCard_SetPowerState) #pragma alloc_text(PAGE, FakeGfxCard_GetChildDescriptor) #pragma alloc_text(PAGE, FakeGfxCard_FindAdapter) #pragma alloc_text(PAGE, FakeGfxCard_Initialize) #pragma alloc_text(PAGE, FakeGfxCard_StartIO) /********************************************************************** * * FakeGfxCard_ResetHW * * This routine would reset the hardware when a soft reboot is * performed. Returning FALSE from this routine would force * the HAL to perform an INT 10h and set Mode 3 (Text). * * We are not real hardware so we will just return TRUE so the HAL * does nothing. * **********************************************************************/ BOOLEAN FakeGfxCard_ResetHW(PVOID HwDeviceExtension, ULONG Columns, ULONG Rows) { return TRUE; } /********************************************************************** * * FakeGfxCard_VidInterrupt * * Checks if it's adapter generated an interrupt and dismisses it * or returns FALSE if it did not. * **********************************************************************/ BOOLEAN FakeGfxCard_VidInterrupt(PVOID HwDeviceExtension) { return FALSE; } /********************************************************************** * * FakeGfxCard_GetPowerState * * Queries if the device can support the requested power state. * **********************************************************************/ VP_STATUS FakeGfxCard_GetPowerState(PVOID HwDeviceExtension, ULONG HwId, PVIDEO_POWER_MANAGEMENT VideoPowerControl) { return NO_ERROR; } /********************************************************************** * * FakeGfxCard_SetPowerState * * Sets the power state. * **********************************************************************/ VP_STATUS FakeGfxCard_SetPowerState(PVOID HwDeviceExtension, ULONG HwId, PVIDEO_POWER_MANAGEMENT VideoPowerControl) { return NO_ERROR; } /********************************************************************** * * FakeGfxCard_GetChildDescriptor * * Returns an identifer for any child device supported * by the miniport. * **********************************************************************/ ULONG FakeGfxCard_GetChildDescriptor (PVOID HwDeviceExtension, PVIDEO_CHILD_ENUM_INFO ChildEnumInfo, PVIDEO_CHILD_TYPE pChildType, PVOID pChildDescriptor, PULONG pUId, PULONG pUnused) { return ERROR_NO_MORE_DEVICES; } /********************************************************************** * * FakeGfxCard_FindAdapter * * This function performs initialization specific to devices * maintained by this miniport driver. * **********************************************************************/ VP_STATUS FakeGfxCard_FindAdapter(PVOID HwDeviceExtension, PVOID HwContext, PWSTR ArgumentString, PVIDEO_PORT_CONFIG_INFO ConfigInfo, PUCHAR Again) { return NO_ERROR; } /********************************************************************** * * FakeGfxCard_Initialize * * This initializes the device. * **********************************************************************/ BOOLEAN FakeGfxCard_Initialize(PVOID HwDeviceExtension) { return TRUE; } /********************************************************************** * * FakeGfxCard_StartIO * * This routine executes requests on behalf of the GDI Driver * and the system. The GDI driver is allowed to issue IOCTLs * which would then be sent to this routine to be performed * on it's behalf. * * We can add our own proprietary IOCTLs here to be processed * from the GDI driver. * **********************************************************************/ BOOLEAN FakeGfxCard_StartIO(PVOID HwDeviceExtension, PVIDEO_REQUEST_PACKET RequestPacket) { RequestPacket->StatusBlock->Status = 0; RequestPacket->StatusBlock->Information = 0; return TRUE; } Since I don't have any hardware I simply implement enough of a miniport to make the system happy. The only possible API I would intend to use would be "StartIO" if I needed to access or perform an operation on the system that the display driver is not capable of doing with its limited API set. However in this implementation there is nothing we need done. Remember, the main purpose of the miniport is to enumerate hardware devices/resources and manage them. If you don't have any then that removes everything but the necessary to keep the driver model happy. The display driverThe display driver links against WIN32K.SYS and is only allowed to call Eng* APIs. These APIs are actually found in the kernel and in user mode. Prior to NT4 the display drivers were in user mode. In any case the same API set used by display drivers is also used by printer drivers. Conforming to this API set also allows the display driver to be movable to user or kernel with minimal work. The display driver however is not loaded into system memory but instead session space. Session space is the kernel equivalent of process isolation. In user mode processes have their own virtual memory address space and in the kernel sessions have their own virtual memory address space. System space is the kernel memory which is global to all sessions. A session is an instance of a logged on user which contains its own Window Manager, Desktop(s), shell and applications. This is most notable in Windows XP "Fast User Switching" in which you can log multiple users onto a single machine. Each user is actually in a unique session with a unique range of kernel memory known as session space. This can be a problem when designing a video driver. It means you cannot simply pass random memory down to your miniport if your miniport may process that memory outside the context of the current session. This is for example passing this memory to be processed in another thread which could reside in the system process for example. If the system process is not associated with your session then you will be accessing a different memory range than you think. When this occurs you get the "A driver has not been correctly ported to Terminal Services" blue screen. The display driver is not anything like the drivers we have worked with so far. It is still in PE format but it is not like the miniport which is a normal kernel driver linking against a different frame work. This driver cannot use kernel APIs by linking directly to them and should not use them for the exact reason specified above. If the API passes the memory outside of session space then you have a blue screen unless you ensure you only pass system memory. This is another reason to only use the Eng* API set however you could request a function pointer table from the miniport driver; nothing actually prevents you from doing so. In any case the display driver behaves more like a DLL than normal drivers do and it is essentially treated as one. This driver's framework is tied to WIN32K.SYS which implements the Window Manager as well as GDI. This driver is compiled using " DrvEnableDriverThis is the initial entry point for a display driver and it is not related to This entry point is simply responsible for returning the list of your functions. You may also do any initialization you may need to do here. The following is the code from the sample display driver in this article: /* * Display Drivers provide a list of function entry points for specific GDI * tasks. These are identified by providing a pre-defined "INDEX" value (pre- * defined * by microsoft) followed by the function entry point. There are levels of * flexibility * on which ones you are REQUIRED and which ones are technically OPTIONAL. * */ DRVFN g_DrvFunctions[] = { { INDEX_DrvAssertMode, (PFN) GdiExample_DrvAssertMode }, { INDEX_DrvCompletePDEV, (PFN) GdiExample_DrvCompletePDEV }, { INDEX_DrvCreateDeviceBitmap, (PFN) GdiExample_DrvCreateDeviceBitmap }, { INDEX_DrvDeleteDeviceBitmap, (PFN) GdiExample_DrvDeleteDeviceBitmap }, { INDEX_DrvDestroyFont, (PFN) GdiExample_DrvDestroyFont }, { INDEX_DrvDisablePDEV, (PFN) GdiExample_DrvDisablePDEV }, { INDEX_DrvDisableDriver, (PFN) GdiExample_DrvDisableDriver }, { INDEX_DrvDisableSurface, (PFN) GdiExample_DrvDisableSurface }, { INDEX_DrvSaveScreenBits, (PFN) GdiExample_DrvSaveScreenBits }, { INDEX_DrvEnablePDEV, (PFN) GdiExample_DrvEnablePDEV }, { INDEX_DrvEnableSurface, (PFN) GdiExample_DrvEnableSurface }, { INDEX_DrvEscape, (PFN) GdiExample_DrvEscape }, { INDEX_DrvGetModes, (PFN) GdiExample_DrvGetModes }, { INDEX_DrvMovePointer, (PFN) GdiExample_DrvMovePointer }, { INDEX_DrvNotify, (PFN) GdiExample_DrvNotify }, // { INDEX_DrvRealizeBrush, (PFN) GdiExample_DrvRealizeBrush }, { INDEX_DrvResetPDEV, (PFN) GdiExample_DrvResetPDEV }, { INDEX_DrvSetPalette, (PFN) GdiExample_DrvSetPalette }, { INDEX_DrvSetPointerShape, (PFN) GdiExample_DrvSetPointerShape }, { INDEX_DrvStretchBlt, (PFN) GdiExample_DrvStretchBlt }, { INDEX_DrvSynchronizeSurface, (PFN) GdiExample_DrvSynchronizeSurface }, { INDEX_DrvAlphaBlend, (PFN) GdiExample_DrvAlphaBlend }, { INDEX_DrvBitBlt, (PFN) GdiExample_DrvBitBlt }, { INDEX_DrvCopyBits, (PFN) GdiExample_DrvCopyBits }, { INDEX_DrvFillPath, (PFN) GdiExample_DrvFillPath }, { INDEX_DrvGradientFill, (PFN) GdiExample_DrvGradientFill }, { INDEX_DrvLineTo, (PFN) GdiExample_DrvLineTo }, { INDEX_DrvStrokePath, (PFN) GdiExample_DrvStrokePath }, { INDEX_DrvTextOut, (PFN) GdiExample_DrvTextOut }, { INDEX_DrvTransparentBlt, (PFN) GdiExample_DrvTransparentBlt }, }; ULONG g_ulNumberOfFunctions = sizeof(g_DrvFunctions) / sizeof(DRVFN); /********************************************************************* * DrvEnableDriver * * This is the initial driver entry point. This is the "DriverEntry" * equivlent for Display and Printer drivers. This function must * return a function table that represents all the supported entry * points into this driver. * *********************************************************************/ BOOL DrvEnableDriver(ULONG ulEngineVersion, ULONG ulDataSize, DRVENABLEDATA *pDrvEnableData) { BOOL bDriverEnabled = FALSE; /* * We only want to support versions > NT 4 * */ if(HIWORD(ulEngineVersion) >= 0x3 && ulDataSize >= sizeof(DRVENABLEDATA)) { pDrvEnableData->iDriverVersion = DDI_DRIVER_VERSION; pDrvEnableData->pdrvfn = g_DrvFunctions; pDrvEnableData->c = g_ulNumberOfFunctions; bDriverEnabled = TRUE; } return bDriverEnabled; } DrvDisableDriverThis function handler is called when the display driver is being unloaded. In this handler you can perform any clean up necessary for what you have created in the /********************************************************************* * GdiExample_DrvDisableDriver * * This function is used to notify the driver when the driver is * getting ready to be unloaded. * *********************************************************************/ VOID GdiExample_DrvDisableDriver(VOID) { /* * No Clean up To Do */ } DrvGetModesThe API called after the driver is loaded and enabled is The API is generally called twice the first time it simply asks for the size required to store the modes and the second time it calls with the correct size. The following code fragment is from the sample driver which only supports 640x480x32: /********************************************************************* * GdiExample_DrvGetModes * * This API is used to enumerate display modes. * * This driver only supports 640x480x32 * *********************************************************************/ ULONG GdiExample_DrvGetModes(HANDLE hDriver, ULONG cjSize, DEVMODEW *pdm) { ULONG ulBytesWritten = 0, ulBytesNeeded = sizeof(DEVMODEW); ULONG ulReturnValue; ENGDEBUGPRINT(0, "GdiExample_DrvGetModes\r\n", NULL); if(pdm == NULL) { ulReturnValue = ulBytesNeeded; } else { ulBytesWritten = sizeof(DEVMODEW); memset(pdm, 0, sizeof(DEVMODEW)); memcpy(pdm->dmDeviceName, DLL_NAME, sizeof(DLL_NAME)); pdm->dmSpecVersion = DM_SPECVERSION; pdm->dmDriverVersion = DM_SPECVERSION; pdm->dmDriverExtra = 0; pdm->dmSize = sizeof(DEVMODEW); pdm->dmBitsPerPel = 32; pdm->dmPelsWidth = 640; pdm->dmPelsHeight = 480; pdm->dmDisplayFrequency = 75; pdm->dmDisplayFlags = 0; pdm->dmPanningWidth = pdm->dmPelsWidth; pdm->dmPanningHeight = pdm->dmPelsHeight; pdm->dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFLAGS | DM_DISPLAYFREQUENCY; ulReturnValue = ulBytesWritten; } return ulReturnValue; } DrvEnablePDEVOnce a mode is chosen this API is then called which will allow the driver to enable the "physical device". The purpose of this API is to allow the display driver to create its own private context which will be passed into the other display entry points. The reason for this private context is that a single display driver may handle multiple display devices and as such would need to distinguish one display device from another. The return value for this API is a pointer to the context or instance of the supplied display device. The selected display setting is passed into this API via the This API aside from creating an instance structure must also initialize the The /********************************************************************* * GdiExample_DrvEnablePDEV * * This function will provide a description of the Physical Device. * The data returned is a user defined data context to be used as a * handle for this display device. * * The hDriver is a handle to the miniport driver associated with * this display device. This handle can be used to communicate to * the miniport through APIs to send things like IOCTLs. * *********************************************************************/ DHPDEV GdiExample_DrvEnablePDEV(DEVMODEW *pdm, PWSTR pwszLogAddr, ULONG cPat, HSURF *phsurfPatterns, ULONG cjCaps, GDIINFO *pGdiInfo, ULONG cjDevInfo, DEVINFO *pDevInfo, HDEV hdev, PWSTR pwszDeviceName, HANDLE hDriver) { PDEVICE_DATA pDeviceData = NULL; ENGDEBUGPRINT(0, "GdiExample_DrvEnablePDEV Enter \r\n", NULL); pDeviceData = (PDEVICE_DATA) EngAllocMem(0, sizeof(DEVICE_DATA), FAKE_GFX_TAG); if(pDeviceData) { memset(pDeviceData, 0, sizeof(DEVICE_DATA)); memset(pGdiInfo, 0, cjCaps); memset(pDevInfo, 0, cjDevInfo); { pGdiInfo->ulVersion = 0x5000; pGdiInfo->ulTechnology = DT_RASDISPLAY; pGdiInfo->ulHorzSize = 0; pGdiInfo->ulVertSize = 0; pGdiInfo->ulHorzRes = RESOLUTION_X; pGdiInfo->ulVertRes = RESOLUTION_Y; pGdiInfo->ulPanningHorzRes = 0; pGdiInfo->ulPanningVertRes = 0; pGdiInfo->cBitsPixel = 8; pGdiInfo->cPlanes = 4; pGdiInfo->ulNumColors = 20; pGdiInfo->ulVRefresh = 1; pGdiInfo->ulBltAlignment = 1; pGdiInfo->ulLogPixelsX = 96; pGdiInfo->ulLogPixelsY = 96; pGdiInfo->flTextCaps = TC_RA_ABLE; pGdiInfo->flRaster = 0; pGdiInfo->ulDACRed = 8; pGdiInfo->ulDACGreen = 8; pGdiInfo->ulDACBlue = 8; pGdiInfo->ulAspectX = 0x24; pGdiInfo->ulNumPalReg = 256; pGdiInfo->ulAspectY = 0x24; pGdiInfo->ulAspectXY = 0x33; pGdiInfo->xStyleStep = 1; pGdiInfo->yStyleStep = 1; pGdiInfo->denStyleStep = 3; pGdiInfo->ptlPhysOffset.x = 0; pGdiInfo->ptlPhysOffset.y = 0; pGdiInfo->szlPhysSize.cx = 0; pGdiInfo->szlPhysSize.cy = 0; pGdiInfo->ciDevice.Red.x = 6700; pGdiInfo->ciDevice.Red.y = 3300; pGdiInfo->ciDevice.Red.Y = 0; pGdiInfo->ciDevice.Green.x = 2100; pGdiInfo->ciDevice.Green.y = 7100; pGdiInfo->ciDevice.Green.Y = 0; pGdiInfo->ciDevice.Blue.x = 1400; pGdiInfo->ciDevice.Blue.y = 800; pGdiInfo->ciDevice.Blue.Y = 0; pGdiInfo->ciDevice.AlignmentWhite.x = 3127; pGdiInfo->ciDevice.AlignmentWhite.y = 3290; pGdiInfo->ciDevice.AlignmentWhite.Y = 0; pGdiInfo->ciDevice.RedGamma = 20000; pGdiInfo->ciDevice.GreenGamma = 20000; pGdiInfo->ciDevice.BlueGamma = 20000; pGdiInfo->ciDevice.Cyan.x = 1750; pGdiInfo->ciDevice.Cyan.y = 3950; pGdiInfo->ciDevice.Cyan.Y = 0; pGdiInfo->ciDevice.Magenta.x = 4050; pGdiInfo->ciDevice.Magenta.y = 2050; pGdiInfo->ciDevice.Magenta.Y = 0; pGdiInfo->ciDevice.Yellow.x = 4400; pGdiInfo->ciDevice.Yellow.y = 5200; pGdiInfo->ciDevice.Yellow.Y = 0; pGdiInfo->ciDevice.MagentaInCyanDye = 0; pGdiInfo->ciDevice.YellowInCyanDye = 0; pGdiInfo->ciDevice.CyanInMagentaDye = 0; pGdiInfo->ciDevice.YellowInMagentaDye = 0; pGdiInfo->ciDevice.CyanInYellowDye = 0; pGdiInfo->ciDevice.MagentaInYellowDye = 0; pGdiInfo->ulDevicePelsDPI = 0; pGdiInfo->ulPrimaryOrder = PRIMARY_ORDER_CBA; pGdiInfo->ulHTPatternSize = HT_PATSIZE_4x4_M; pGdiInfo->flHTFlags = HT_FLAG_ADDITIVE_PRIMS; pGdiInfo->ulHTOutputFormat = HT_FORMAT_32BPP; *pDevInfo = gDevInfoFrameBuffer; pDevInfo->iDitherFormat = BMF_32BPP; } pDeviceData->pVideoMemory = EngMapFile(L"\\??\\c:\\video.dat", RESOLUTION_X*RESOLUTION_Y*4, &pDeviceData->pMappedFile); pDeviceData->hDriver = hDriver; pDevInfo->hpalDefault = EngCreatePalette(PAL_BITFIELDS, 0, NULL, 0xFF0000, 0xFF00, 0xFF); } ENGDEBUGPRINT(0, "GdiExample_DrvEnablePDEV Exit \r\n", NULL); return (DHPDEV)pDeviceData; } DrvCompletePDEVThis call is made after the enable to notify the display driver that the device object is now completed. The only parameters are the private data structure created in the enable call and the completed handle to the GDI device. Unless you have more initialization to do you generally can just save the GDI handle and move on. The following is the code from the sample driver: /********************************************************************* * GdiExample_DrvCompletePDEV * * This is called to complete the process of enabling the device. * * *********************************************************************/ void GdiExample_DrvCompletePDEV(DHPDEV dhpdev, HDEV hdev) { PDEVICE_DATA pDeviceData = (PDEVICE_DATA)dhpdev; ENGDEBUGPRINT(0, "GdiExample_DrvCompletePDEV Enter \r\n", NULL); pDeviceData->hdev = hdev; ENGDEBUGPRINT(0, "GdiExample_DrvCompletePDEV Exit \r\n", NULL); } DrvDisablePDEVThis API is called when the PDEV is no longer needed and will be destroyed. This is called after /********************************************************************* * GdiExample_DrvDisablePDEV * * This is called to disable the PDEV we created. * * *********************************************************************/ void GdiExample_DrvDisablePDEV(DHPDEV dhpdev) { PDEVICE_DATA pDeviceData = (PDEVICE_DATA)dhpdev; UINT dwBytesReturned = 0; ENGDEBUGPRINT(0, "GdiExample_DrvDisablePDEV\r\n", NULL); if(pDeviceData->pMappedFile) { EngUnmapFile(pDeviceData->pMappedFile); } EngFreeMem(dhpdev); } DrvEnableSurfaceThis API is called after the PDEV has completed to ask the display driver to create a surface. Also as noted in the comments below you have two choices when creating a surface. You can create a surface in which the display driver will manage it or you can create one in which GDI will manage for you. The following code chose the option of managing its own device surface. The entire purpose is to define a drawing surface in which GDI will also be able to draw onto. Display drivers have their own device surfaces and thus will generally want to manage its surface. In doing this it must describe the surface in a way which GDI can understand and be able to draw on it. This means defining the start address and even the pitch as display drivers do not generally have linear buffers for all modes. In our case we use the memory mapped file we created to be our video memory: /********************************************************************* * GdiExample_DrvEnableSurface * * This API is used to enable the physical device surface. * * You have two choices here. * * 1. Driver Manages it's own surface * EngCreateDeviceSurface - Create the handle * EngModifySurface - Let GDI Know about the object. * * 2. GDI Manages the surface * EngCreateBitmap - Create a handle in a format that * GDI Understands * EngAssociateSurface - Let GDI Know about the object. * * *********************************************************************/ HSURF GdiExample_DrvEnableSurface(DHPDEV dhpdev) { HSURF hsurf; SIZEL sizl; PDEVICE_DATA pDeviceData = (PDEVICE_DATA)dhpdev; ENGDEBUGPRINT(0, "GdiExample_DrvEnableSurface\r\n", NULL); pDeviceData->pDeviceSurface = (PDEVICE_SURFACE)EngAllocMem(FL_ZERO_MEMORY, sizeof(DEVICE_SURFACE), FAKE_GFX_TAG); sizl.cx = 800; sizl.cy = 600; hsurf = (HSURF)EngCreateDeviceSurface( (DHSURF)pDeviceData->pDeviceSurface, sizl, BMF_32BPP); EngModifySurface(hsurf, pDeviceData->hdev, HOOK_FILLPATH | HOOK_STROKEPATH | HOOK_LINETO | HOOK_TEXTOUT | HOOK_BITBLT | HOOK_COPYBITS, MS_NOTSYSTEMMEMORY, (DHSURF)pDeviceData->pDeviceSurface, pDeviceData->pVideoMemory, 800*4, NULL); return(hsurf); } DrvDisableSurfaceThis API is called to destroy the drawing surface created in the /********************************************************************* * GdiExample_DrvDisableSurface * * This API is called to disable the GDI Surface. * * *********************************************************************/ void GdiExample_DrvDisableSurface(DHPDEV dhpdev) { PDEVICE_DATA pDeviceData = (PDEVICE_DATA)dhpdev; ENGDEBUGPRINT(0, "GdiExample_DrvDisableSurface\r\n", NULL); EngDeleteSurface(pDeviceData->hsurf); pDeviceData->hsurf = NULL; EngFreeMem(pDeviceData->pDeviceSurface); pDeviceData->pDeviceSurface = NULL; } SequencingSo, let's go through this one more time for clarity.
So how does the drawing work?The "GDI Calls" are essentially handling things like " You may be wondering "how do I get to my PDEV or my surface object from these Drv* calls". Well, the Display driver escape codesIn my tutorials on device drivers we learned that it is possible to use " In user mode you can send "Escape Codes" to the display driver using one of two methods. The first is The second method is OpenGL supportOpenGL support is done through the use of an "ICD" or "Installable Client Driver". This is a concept originally created by SGI to help improve the performance of OpenGL on Windows by letting the vendor implement the graphics pipeline completely. When OpenGL32.DLL gets loaded it simply asks the video driver for it's ICD and if there is one it's loaded into the process space and OpenGL APIs are serviced by the ICD. The ICD is in full control of the graphics pipeline and thus each vendor and driver version may have a different implementation. The usual case is to buffer the OpenGL commands and flush them to the card using the The other method of supporting OpenGL is through something called a "Mini Client Driver" or "MCD". This is Microsoft's original method for OpenGL support and is similar to an ICD but the MCD lives in the kernel. This method is not used by any driver vendor that I know of and is very slow which is the reason for the ICD implementation. DirectX supportIn XPDM, Direct Draw support is done in the GDI driver. This is through the Direct3D is initialized through the What is a mirror driver?A mirror driver is a not well documented feature in which you can load a video driver that will "mirror" another display driver. That is they will receive the same calls as the display driver they are mirroring. A mirror driver is documented to not support To load a mirror driver the registry key for this device needs to set the " The mirror driver does not properly unload at mode switch and generally if there are references to a drawing surface the driver will not unload. So, in my experience to get a mirror driver to mode switch you need an application that will detect If you wish to immediately unload the mirror driver without a display change you simply need to follow the same steps as what loaded it. Set " The exampleThe example driver in this article simply shares a memory mapped file between an application and the display driver. The display driver will write graphics commands to the memory mapped file and the application simply acts as a monitor and will just refresh itself ~70 times a second. This is not efficient but it is just an example. The display driver is installed as a regular hardware driver and is seen just as an ATI or NVIDIA driver would be. To install the example you will simply need to use the "Add New Hardware" wizard in the control panel. You must select "Hardware is already installed" and "Manually select hardware from a list". The following picture shows the list of devices for which you scroll down to the bottom and select "Add a new hardware device":
Then you simply want to select "Have Disk" and find the .INF file that is provided with this project. You will then need to scroll down this new list and find "Toby Opferman Sample Video Display" as shown in the following picture:
You will see the following dialog when installing just select "Continue Anyway" unless you do not want to install the driver. The next thing you do is just enable the second monitor using the display settings and the third tab. Run the application monitor program provided with this article and you will be shown the second monitor in that application window:
HomeworkReading and seeing is a good way to learn however I believe you learn more if you actually try and do something! What I want you to do is take my example and add more display modes! This will require changes to the application and you can either attempt to make the application detect these display changes through various methods including Here is a little hint. When a new mode is selected you do not always get a ConclusionThis article showed how to create a very basic display driver to handle GDI commands. The display driver architecture mentioned in the article only covered XPDM and not the new LDDM as found in Windows Vista. This is also essentially the extreme basics of "where to get started". Even so hopefully you have learned a little something about display drivers and the Windows operating system.
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||