PDRREF:Design Considerations for All Drivers

This chapter describes various design considerations involved in creating a presentation driver. The following list includes the topics covered in this chapter:
 * Angles
 * Bounds computations
 * Clipping
 * Closing figures in areas and paths
 * Coordinate values
 * Positions within text functions
 * Return codes
 * Transform matrix values
 * Allocating Memory
 * Error Strategy
 * Presentation Manager Error Codes
 * Exit List Processing
 * Per-Thread Exception Management
 * Using Macro Assembler Instructions
 * Protecting Objects or Device Contexts

Angles
Angles are passed as signed 32-bit numbers. Zero refers to the direction of the positive X-axis; 360 represents 360°. Positive values represent counterclockwise angles from the positive X-axis.

Bounds computations
All presentation drivers must accumulate bounds for unclipped primitives. Application bounds (COM_BOUND) are accumulated in model space. User bounds (COM_ALT_BOUND) are accumulated in device-coordinate space.

Clipping
The presentation driver must perform clipping for drawing and text functions, except for GreDrawLinesInPath and GrePolyShortLine. Clipping for these two functions is done by the graphics engine. The minimum requirement is to render each primitive clipped to a single rectangle and to clip each rectangle in turn. The rectangles can be enumerated as described in GreGetClipRects.


 * Note: Rectangles might not always be valid. See Drawing to Display Devices.

Closing figures in areas and paths
The graphics engine generates closure lines for figures within areas and paths unless the presentation driver has opted to hook all the path and area functions. In this case, the presentation driver is responsible for closing any figures. For details, see Area and Path Functions.

Coordinate values
All coordinates are passed to the presentation driver as 32-bit values. Unless stated otherwise, these values represent world coordinates. The graphics engine function, GreConvert, can be called to convert coordinates from one type to another. Coordinates must be converted back to world coordinates before returning to the presentation driver. Notice that screen coordinates are device coordinates to which the DC origin has been added.

Positions within text functions
When positions are used, a text function takes the position from the base line of the text box. Descenders, such as the tail of a lowercase character "y," are expressed as a negative value relative to the base line.

Return codes
The presentation driver must always return a full 32-bit (LONG) value. For example, BOOLEAN TRUE and FALSE are defined as:
 * 1) define TRUE  (1L);
 * 2) define FALSE (0L);

Transform matrix values
Transform matrix elements are represented in fixed point notation, that is, as a 16-bit signed integer and a 16-bit fractional part. These precision limits apply during graphics engine matrix multiplication for all initial, intermediate, and final matrix element values.

Allocating Memory
Presentation drivers can allocate and manage memory by using: Display drivers, or presentation drivers that share objects such as bit maps and regions, must use the SSxxx functions to allocate memory for these objects. Memory allocated through calls to these functions is "shared memory", controlled by the memory allocator component of the graphics engine. Ownership of the memory can be transferred or (when the owning device context ceases to exist) marked as having no owner.
 * A Dosxxx function such as DosAllocMem.
 * The SSxxx functions described in System Functions.

Error Strategy
Presentation drivers support the error strategy implemented by the Presentation Manager interface. When an error occurs, the driver calls WinSetErrorInfo (see WinSetErrorInfo) to log the appropriate error code and set the return code to show that an error was detected.

The component that implements a function must provide error checking for the environment, objects, and resources associated with it. The presentation driver requires the following error checking: For any defined error, the application sees the same error code regardless of whether the error was logged by the Graphics Programming Interface (GPI), graphics engine, or presentation driver.
 * Fail-safe on routines that set attributes and transformation values. Any routine that changes attributes or transformation values must be able to restore the initial values if an error occurs during the change.
 * Full error checking on symbol sets, fonts, bit maps, and regions.
 * With segment drawing, drawing primitives, and primitive attributes in draw mode, unchecked parameters are passed directly to the graphics engine or the presentation driver. When one of these functions is hooked by the presentation driver, the handling routine must perform the necessary error checking and log any errors, or reset any invalid values to their defaults, as appropriate.
 * For any function with coordinates as parameters, the presentation driver must check that the values passed are valid. When an invalid coordinate is detected, the handling routine must log an error or use a default coordinate value, as appropriate.

Severity
Four ascending severity levels are defined for error messages:
 * Warning
 * Error
 * Severe error
 * Unrecoverable error
 * Warning:The function detected a problem, took corrective action, and was able to complete processing successfully.
 * Error:The function detected a problem for which no sensible corrective action is possible. The function is not executed and the system remains at the same state as when the function was requested.
 * Severe Error:The function detected a problem from which the system cannot reestablish its state. The function has partially executed and the application must now make some corrective action to restore the system to a known state.
 * Unrecoverable Error:The function detected an error from which it is impossible for the system to reestablish the state that it held at the time that the function was called. It is also impossible for the application to restore the system to a known state.

Presentation Manager Error Codes
Error codes are defined in the header file. These codes fall into general and specific groups. General error codes that are appropriate to many Grexxx functions include: Specific error codes listed in the descriptions of each Grexxxfunction are found in Mandatory and Simulated Graphics Engine Function Reference.

To set an error code and the error's severity, the presentation driver must call WinSetErrorInfo (see WinSetErrorInfo). All error codes are listed and explained in the Presentation Manager Programming Reference.

Exit List Processing
An exit list is a list of routines that are given control when the current process ends, either normally or abnormally. The following is an example of exit list processing: #define ROUTINE_ORDER 0x1000 usResult = DosExitList (EXLST_ADD | ROUTINE_ORDER, (PFNEXITLIST)MyExitProc); This adds the function MyExitProc to the list of functions that are called when this process terminates (either normally or because of some error such as a General Protection fault). usResult = DosExitList (EXLST_EXIT, (PFNEXITLIST)MyExitProc); This allows the operating system to transfer control to the next function in the list of exit list processing functions for the process that has terminated. For more information, refer to DosExitList in the OS/2 Control Program Programming Reference and OS/2 Programming Guide.
 * When the presentation driver's Enable Subfunction 01H - FillLogicalDeviceBlock is called, the driver can call function DosExitList:
 * When MyExitProc is called, the presentation driver can perform any necessary cleanup such as releasing any semaphores. The last call in MyExitProc is another call to DosExitList:

At Enable time, the presentation driver must place an entry in the exit list for the application or process that opens the DC. This entry is a pointer identifying the routine in the presentation driver that releases all resources owned by the DC.

Note: When writing a presentation driver, consider what would happen if another thread of the process were to terminate.

Per-Thread Exception Management
Per-thread exception management is available to 32-bit EXEs, DLLs, and presentation drivers. The per-thread exception management model is a superset of DosExitList processing found in 16-bit OS/2 programming.

Under 16-bit OS/2 architecture, a process cannot handle access violations. In the event of an access violation, the system terminates the process. Given that the trapping process had registered an exit-list function, it had one final chance to do clean up just prior to termination. Sixteen-bit drivers and DLLs usually add an exitlist. At process termination, 16-bit drivers perform per-process clean up in exit-list functions.

Programmers of 32-bit drivers have a choice concerning per-process clean up. They may use the 32-bit version of DosExitList, or they may add code for the DLL termination case in the driver function _DLL_InitTerm. The _DLL_InitTerm function, if present and exported from the driver, will be called by OS/2 with parameter 0 on DLL instantiation and parameter 1 on DLL termination or unload. The _DLL_InitTerm function provides process-granular clean up.

Furthermore, 32-bit OS/2 has exception handlers that are thread-granular. OS/2 lets each thread in a process handle exceptions that arise on that thread. Some of these exceptions are: The goals of a 32-bit driver that uses exception management are:
 * Accessing invalid or uncommitted memory
 * Stack overruns
 * Thread terminations
 * Floating point errors
 * Asynchronous (external) signals
 * User-raised exceptions (synchronous signals)
 * Invalid opcodes
 * Bounds
 * Divide by zero errors
 * Overflow errors
 * When passed bad parameters that cause exceptions, drivers can call WinSetErrorInfo and return suitable result codes for the application to inspect.
 * When threads are terminated while executing driver code, the driver can clean up before the thread exits.

The Exception Handler
A misconception about exception handlers is that they must be able to fix every bad situation and resume execution. This is not a realistic expectation.

Assume that the handler function will not resume every exception. Instead, it will perform clean up, report errors, and return an appropriate result code. In this case, one exception handling function can handle all exceptions.

At exception time, the handler's main activity is to jump out of the handler function back into the function that caused the trap. The handler does an inter-function jump using ANSI C setjmp and longjmp.

After the inter-function jump, the thread is now executing at a known point in the function that had a problem, and doing clean up there is far easier than in the handler.

Sample Code
The code fragment below shows how to set a handler while the calling thread is executing driver code. In the event of an exception, OS/2 calls the handler function, which jumps back to the point of setjmp. 
 * 1) define INCL_DOS
 * 2) include 
 * 3) include 

// this exception registration record extends the OS/2-defined one in BSEXCPT.H // by adding a C jmp_buf at the end struct _regrec { PULONG   pNext; PFN      pfnHandler; jmp_buf  jmp; } typedef struct _regrec REGREC, *PREGREC;

// - // an exception handler function that jumps back to setjmp

ULONG _System DriverHandler( PEXCEPTIONREPORTRECORD p1,                            PREGREC p2,                             PCONTEXTRECORD p3,                             PVOID pv ) { // handle interesting exceptions switch( p1->ExceptionNum ) { case XCPT_ACCESS_VIOLATION: case XCPT_INTEGER_DIVIDE_BY_ZERO: case XCPT_INTEGER_OVERFLOW: case XCPT_PROCESS_TERMINATE: case XCPT_ASYNC_PROCESS_TERMINATE: // setjmp will return p1->ExceptionNum longjmp(p2)->jmp, p1->ExceptionNum ); }  // not interested; system should continue search  return XCPT_CONTINUE_SEARCH; }

// - // example of an exported driver function; return TRUE upon success LONG _System ExportedDriverFunction( PVOID p ) { REGREC        regrec; ULONG        ulException; LONG         lResult;

regrec.pfnHandler = (PFN)DriverHandler; DosSetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&regrec );

// get thread state; normally rets 0, rets non-zero after longjmp ulException = setjmp( regrec.jmp );

if( ulException ) { // an exception occurred // clean up here: release memory, semaphores // must check for exiting thread case switch( ulException ) { case XCPT_PROCESS_TERMINATE: case XCPT_ASYNC_PROCESS_TERMINATE: // thread is ending or being terminated DosUnsetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&regrec ); DosExit( EXIT_THREAD, 0 ); }   WinSetErrorInfo( ... ); lResult = FALSE; goto depart; }

// do the work of the exported function here

lResult = TRUE;

depart: DosUnsetExceptionHandler( (PEXCEPTIONREGISTRATIONRECORD)&regrec ); return lResult; } 

Using Macro Assembler Instructions
Presentation drivers never use the CLI and STI macro assembler instructions because these instructions can interfere with some of the base OS/2 system operations.

Protecting Objects or Device Contexts
A process that attempts to use a locked object will return an error such as PMERR_HDC_BUSY. Although a device context is owned by a single application or process, the owner can access the device context (DC) through multiple threads. The presentation driver must provide a mechanism whereby a DC can register that it is busy and block access from other threads. In its simplest form, this is performed by the EnterDriver and LeaveDriver routines, which are called at the start and end of each function-handling routine in the presentation driver.

An example of a typical EnterDriver routine for a display driver is as follows:  /***************** Typical EnterDriver Routine ***********************/

enter_driver { do {               /* Lock DC for exclusive use of the current thread */ SemEnter(Device); /* Some functions do not pass a Device Context (DC)*/ /* handle */ if (hdc == NULL) return(SUCCESS); /* Check validity of the passed DC handle */

if (hdc == ERROR) { WinSetErrorInfo (SEVERITY_ERROR, PMERR_INV_HDC); SemLeave(Device); return(ERROR); } /* DC region must be validated before */ /* driver draws into it. */

if (hdc_is_not_dirty) /* Test the HDC_IS_DIRTY flag. */                         /* If the flag is set, the DC         */ return(SUCCESS);  /* must be recalculated by the system */

SemLeave(Device);     /* Unlock DC. Call back to engine   */ /* to force DC calculation. */

VisRegionNotify(hdc); /* Loop back to reset lock and recheck*/ } while (TRUE); } 

Changes to the Graphics Engine
In the Presentation Manager for OS/2 2.1 and previous versions, a presentation driver had to support more than 70 functions. In addition, it was also mandatory that the presentation driver keep device-contextual state information and, in the case of display presentation drivers, run some functions at interrupt time.

The design of the graphics engine in OS/2 Warp, Version 3 focuses on taking the complexity out of developing a presentation driver while keeping the expandability and flexibility it was originally designed to have.

This graphics engine has two major changes from OS/2 2.1 and previous versions: This design is intended to do the following: The following table identifies the support for the Presentation Driver Model that is available in various versions of OS/2.
 * Device-independent functions merged into the graphics engine
 * System rasterization support (referred to as SOFTDRAW)
 * Make presentation drivers simple and easy to develop
 * Allow developers to add functionality in increments
 * Move contextual and state management out of the presentation drivers
 * Move common rasterization management out of the presentation drivers

Note:


 * 16BPD : Presentation Driver compiled as 16 bit
 * 32BPD : Presentation Driver compiled as 32 bit
 * NMPD : New model of Presentation Driver