Chapter 3. Implementing a New Driver

This chapter gives a commented, step-by-step list of routines that Heidi drivers need to provide. It also gives some suggestions for testing and using Heidi drivers.

For programmers new to writing Heidi drivers, it is most intuitive to look at a Heidi application before delving into writing driver code. Looking at an application clarifies some of the abstract calling sequence and interaction that a Heidi driver should provide. A sample application source steps you through the process.


    In the sample application, the my_initialize_and_draw () method looks like the following code.
    Listing 1: Example Application Source
void CSimpleView::my_initialize_and_draw(
    HWND             window)

{
/* 

Given a GDI window handle, this sample application code performs the
following tasks: load the GDI driver and software Z buffer renderer,
configure the driver options including establishing a palette on color
mapped devices, and draw a DC line on screen.

*/

HT_Create_Device         device_generator = 
        (HT_Create_Device)HD_Load_Dynamic_Routine
      ("gdi10.hdi","HD_Device");

HD_Assert (device_generator != null);

HT_Create_Renderer renderer_generator = 
        (HT_Create_Renderer) HD_Load_Dynamic_Routine
       ("szb10.hdi", "HD_Renderer");

HD_Assert (renderer_generator != null);

// Make a new device
HT_Device my_device = (*device_generator) ();
HD_Assert (my_device != null);

// Configure all parameters

my_device.set_configure ("Window ID",HT_Option_Value((long)window));

my_device.set_configure ("Context ID",HT_Option_Value((long) GetDC (window)));


RECT             client;

GetClientRect (&client);

// Set Window size.

my_device.set_configure ("Window Size",HT_Option_Value 
        (client.right - client.left + 1, 
         client.bottom - client.top + 1));

// GDI driver does not have a z buffer.

my_device.set_configure ("Buffer Depth",
        HT_Option_Value (0));

// Determine if we are in palletized mode.
// create a palette containing a color cube


stack     HT_Color_Cube     cube;

stack     HT_Palette        pal;

cube.set_resolution (HT_Int_RGB (6, 6, 6));

stack     int has_palette = GetDeviceCaps(GetDC(hWnd),
    RASTERCAPS) & RC_PALETTE;

if (has_palette) {

      // Set a default 6x6x6 palette on the display

    pal = my_device.physical ("Palette").as_palette (0);

    cube.set_base_index (pal.first_available_block
        (cube.total()));

    pal.install (cube);

    my_device.set_configure ("Palette",HT_Option_Value 
        (pal));

    my_device.set_configure ("Color System",
        HT_Option_Value (HT_Color_System::Mapped_RGB));

}

else 
    my_device.set_configure ("Color System",
        HT_Option_Value (HT_Color_System::Direct_RGB));

// Establish after options are set


if (my_device.establish () != 0) {
      // establish fails
      return;

}

// get the z buffer going and stack on top of GDI driver

my_renderer = (*render_generator) (my_device);

HD_Assert (my_renderer != null);

//set z buffer depth to 16.

my_renderer.set_configure ("Buffer Depth",
      HT_Option_Value (16));

if (my_renderer.establish () != 0) {
     // establish fails
     return;
}

my_device.begin_picture();

my_renderer.begin_picture();

HT_Rendition rend (my_renderer);

my_renderer.clear_z_buffer (rend);

// set foreground to blue; background to red.

rend.set_color(HT_RGB32 (0x00, 0x00, 0xff));

rend.set_background_color(HT_RGB32 (0xff, 0x00, 0x00));

my_renderer.clear_drawing_buffer (rend);

HT_Point lines[2] = { 10.0f, 10.0f, 0.0f,

                      100.0f, 100.0f, 0.0f};

my_renderer.draw_dc_polyline (rend, 2, lines);

my_renderer.update();

my_renderer.end_picture();

my_device.update();

my_device.end_picture();
    In the example, the software Z buffer is used as the renderer; the GDI driver the sink (or the device). The driver class is called class Driver, which encapsulates the functions of the GDI device driver that makes use of some of the protected methods of class HT_Device and its base classes HT_Physical_Renderer and HT_Renderer.

    The application loads the driver by calling the platform dependent utility routine HD_Load_Dynamic_Routine, as in the following example. HD_Load_Dynamic_Routine now accepts a user defined search path, absolute path, in either a regular character string or multi-byte format ¾ for an additional run-time DLL search path. The run-time DLL's have to come from exactly the same Heidi build for an application to load Heidi .hdi (DLL) files. If you do not supply a search path, HD_Load_Dynamic_Routine will assume the operating system convention.
     

      HT_Create_Device device_generator = 
              (HT_Create_Device)HD_Load_Dynamic_Routine 
              ("gdi10.hdi","HD_Device");
      HD_Assert (device_generator != null);
    The first argument is the name of the driver executable on disk. In this case, the driver executable is called gdi10.hdi on disk. The second argument is the name of the routine that the driver must export, HD_Device. The routine returns a pointer to the entry point of the driver initialization routine, HD_Device.  Similarly, the software Z buffer needs szb10.hdi and HD_Renderer as its second and third parameters.
      HT_Create_Renderer renderer_generator =
              (HT_Create_Renderer) HD_Load_Dynamic_Routine
             ("szb10.hdi", "HD_Renderer");
      
      HD_Assert (renderer_generator  != null);
    Note: The application unloads the driver by calling the HD_Unload_Dynamic_Routine utility using the same arguments.  You must supply the same user defined search path to unload the driver or renderer that  you used to load them.

    The application then creates a new driver object by calling the driver initialization routine.

      // Make a new device
      HT_Device       my_device = (*device_generator) ();
      HD_Assert (my_device != null);


    This calls the HD_Device routine. For the GDI driver, the HD_Device function looks like this:

      HEIDI_LOADABLE HT_Device_Data alter * HD_Device () {
          return new Driver ();
      }
    The HEIDI_LOADABLE specifier is a macro that converts into a declaration, which is dependent on the platform under which you are compiling. For Microsoft Win32, the declaration specifies that the HD_Device routine is a C callable DLL export routine. This causes the compiler to automatically generate an externally accessible reference called HD_Device for this DLL.  HD_Device routine, the driver object is created and a pointer to it is returned. This driver object is how the application accesses the hardware through Heidi draw calls. As a renderer, the software Z buffer needs a device or another renderer to begin.
      my_renderer = (*render_generator)  (my_device);
      
      HD_Assert (my_renderer != null);
    Setting Renderer Options
    Among the tasks of the driver's class object constructor is the initialization of the renderer options to the values appropriate to the device. The Heidi system maintains certain options for all drivers. The driver should set these options with valid values for the currently configured display mode. The options include the following:
     


    The driver initializes these options by calling the set_... methods of the HT_Renderer_Options object returned by the protected method. These are of the base class HT_Physical_Renderer, as in

       
      HT_Renderer_Options & 
      HT_Physical_Renderer::physical_renderer_options  ( )
    The GDI driver, for instance, performs its physical renderer option initialization like this:
Driver::Driver ()

    : .

    , HT_Device_Data () 

    , m_gdi_configured_options(m_configured_option_table)

    , m_gdi_current_options (m_current_option_table)

{

.

    physical_renderer_options().set_buffer_depth (0);

    physical_renderer_options().set_pixel_aspect (1.0f);

    physical_renderer_options().set_window_origin
        (HT_Int_XY (0, 0));

    //restrict to 16 bit GDI max

    physical_renderer_options().set_window_size
        (HT_Int_XY (0x7FFF,0x7FFF));
.

}
    Installing Driver Specific Options
    Like any renderer, a specific driver may involve further options. For example, if the device supports double-buffering, the driver object may include an option that controls whether double buffering is in effect. The constructor for the driver object has the responsibility of adding entries for these driver-specific options to the option tables. To set up options, three different classes are necessary:
Figure 2. Example, organization of classes
class GDI_Driver_Options {
protected:
.
    HT_Option alter * m_frame_buffer_handle;
.
public:
.
    GDI_Driver_Options (HT_Option_Table & option_table) {
         m_frame_buffer_handle =  
option_table.add (            HT_Option ("GDI Frame
Buffer Handle",          
HT_Option_Enum::Global,
0L)); .        long frame_buffer_handle (void) const
{ return         m_frame_buffer_handle->value().as_long (0);
}     void set_frame_buffer_handle (long val) alter
{         m_frame_buffer_handle->set(
         HT_Option_Value (val));}
.
}
Driver::Driver ()

    : .

{

.

    // Set actions which are invariant to configuration 

SET_ACTION (Draw_DC_Dot, gdi_draw_dc_dot);

SET_ACTION (Draw_DCI_Dot, gdi_draw_dci_dot);

SET_ACTION (Draw_DC_Polyline, gdi_draw_dc_polyline);

SET_ACTION (Draw_DCI_Polyline, gdi_draw_dci_polyline);

SET_ACTION (Draw_DC_Polytriangle,
    gdi_draw_dc_polytriangle);

SET_ACTION (Draw_DCI_Polytriangle,
    gdi_draw_dci_polytriangle);

SET_ACTION (Draw_DC_Polygon, gdi_draw_dc_polygon);

SET_ACTION (Draw_DCI_Polygon, gdi_draw_dci_polygon);

SET_ACTION (Draw_2D_Polyline, gdi_draw_2d_polyline);

SET_ACTION (Draw_2DI_Polyline, gdi_draw_2di_polyline);

SET_ACTION (Draw_2D_Polygon, gdi_draw_2d_polygon);

SET_ACTION (Draw_2DI_Polygon, gdi_draw_2di_polygon);

SET_ACTION (Draw_2D_Polytriangle,
    gdi_draw_2d_polytriangle);

SET_ACTION (Draw_2DI_Polytriangle,
    gdi_draw_2di_polytriangle);

SET_ACTION (Clear_Drawing_Buffer,
    gdi_clear_drawing_buffer);

.

}
 void opengl_draw_2d_dot
(     stack     WOpenGL_Driver alter *       
rc,     stack     HT_Rendition const *         
hr,     register  HT_DC_Point const *           where)
{     register  HT_Int_Rectangle const

&            hard_clip=

     hr->hard_clip();        // Ensure the point
is within thehard clip limits      
if (where->x<  hard_clip.left   ||
         where->x> hard_clip.right  ||         
where->y <hard_clip.bottom  ||         

where->y > hard_clip.top) return;     // Ensure the
transform rendition hasn't changed.        //
If so, then establish     // the
new transformation inOpenGL.     if
(rc->m_tr_incarnation !=
INDC      (hr->transform_incarnation()))        rc->set_dc_xform

(hr, true);     // Set
drawing color    rc->ensure_rgb32_color(hr->color());

    // Draw the point
    glBegin (GL_POINTS);
      glVertex3fv ((GLfloat CONST *)where);
    glEnd ();
}
      Implementation Details¾version.rc
      The simplest way to implement proper versioning is to add the version.rc file from one of the samples (such as \heididdk\source\driver\gdi\version.rc) to your project. First copy the file into your project's working directory. Then, using MSDEV, choose Insert / Files Into Project ... and add version.rc. Recompile, and you have fulfilled your renderer's versioning requirements.

       Alternatively, you may "roll your own" version resource using the following steps:
       

        1. Insert a version resource into your project's resource script (if your project has no resource script, you must first create a resource script).

        2. Populate the version resource¾ set the ProductVersion to 0,HEIDI_MAJOR_VERSION,0,HEIDI_MINOR_VERSION.

        3. Add the following lines to the resource (includes read-only symbol directives):
        #include "heidi/version.h" and #include <winres.h>

        4. Manually edit your resource script (in MSDEV, select File / Open, and set the "Open As:" list box to text). Find the newly added version resource and change the line with PRODUCTVERSION to read PRODUCTVERSION 0,HEIDI_MAJOR_VERSION,0,HEIDI_MINOR_VERSION.

        5. Fill in the custom section of the resource file as elaborated in the next section.
         

      Custom strings¾Driver Resource File
      It is very important to establish a resource file for your drivers so that applications, such as AutoCAD or 3DStudio, can  locate the driver for essential tasks, such as, for example, plotter configuration.

      The resource file, version.rc, contains a custom section, StringFileInfo, shown following from the gdi sample, in which  you can define your own comments, company name, file description, internal name, legal copyright, legal trade marks, original file name and product name. Simply replace the defaults with your own.
       

          BLOCK "StringFileInfo"
          BEGIN
              BLOCK "040904b0"
              BEGIN
                  VALUE "Comments", "$Header: /heidi/heidi_help_public/help/Chapter3/Chapter3.html 24    3/06/03 2:34p Mcb $\0"
                  VALUE "CompanyName", "Autodesk, Inc.\0"
                  VALUE "FileDescription", "Heidi\256 GDI Driver\0"
                  VALUE "FileVersion", HEIDI_PRODUCT_VERSION_STRING
                  VALUE "ModulePlatform", HEIDI_PLATFORM_STRING
                  VALUE "InternalName", "GDI\0"
                  VALUE "LegalCopyright", "Copyright \251 1995-1997 Autodesk, Inc.\0"
                  VALUE "LegalTrademarks", "Heidi\256 is a registered trademark of Autodesk, Inc.\0"
                  VALUE "ModuleType", "00000003\0"            
                  VALUE "OriginalFilename", "GDI10.hdi\0"
                  VALUE "ProductName", "Heidi\256\0"
                  VALUE "ProductVersion", HEIDI_PRODUCT_VERSION_STRING
      
              END
          END
      Three of the string values use predefined variables. These are ProductVersion, ModulePlatform, and FileVersionModuleType is one of several predefined variables which you must determine.  It tells the application the type of device, renderer, font engine, selector, or plotter; thus, providing the means for applications to make a system call to find out the type and load the specific driver.  Table 1, following, shows which bit is used for which driver type.

      Table 1. ModuleType bits

      BIT
      TYPE
      1
      Device
      2
      Renderer
      3
      Font engine
      4
      Selector
      5
      Plotter
      6
      Shape Engine
      7
      Video
    Driver Writing Tips
    Following are tips for developers using Heidi to write drivers:
The original code looks like this:
if (last_line_rendition != lr.line_incarnation())  {
        <set pattern>
        <set cap>
        <set join>
        <set weight>
        last_rendition = lr.line_incarnation();
}
The following code is rewritten showing the improvements:
if (state.last_line_rendition != lr.line_incarnation()) {
        if (state.last_pattern != lr.line_pattern()) {
            <set pattern>
            state.last_pattern = lr.line_pattern();
        }
        if (state.last_cap != lr.line_cap()) {
            <set cap>
            state.last_cap = lr.line_cap();
        }
        if (state.last_join != lr.line_join()) {
            <set join>
            state.last_join = lr.line_join();
        }
        if (state.last_weight != lr.line_weight()) {
            <set weight>
            state.last_weight = lr.line_weight();
        }
        state.last_rendition = lr.line_incarnation();
}
In the improved code example, the parts of the line rendition are reset only if they are actually different. While it is true that one of the attributes needs to be reset (because the incarnation number has changed), it is very likely that most of the attributes have not changed.

Regardless of the mechanism you use to handle Heidi rendition and attribute changes, you should be consistent throughout all drawing routines. A mismatch in this area leads to attribute handling problems. These occur only under special drawing conditions, which are difficult to test for and hard to unravel.

Testing Your Driver
Compile and run sample demo programs from the \sample subdirectory using the supplied GDI driver and compare the results with your driver. These programs should act as a test bed for your driver.

The Unittest sample program should be run on a periodic basis to check the performance of your driver, as new functionality is implemented. This monitors performance progress between revisions of your driver.

Notes on Heidi Drivers
Following are some helpful notes for making use of drivers supplied in the kit.
 

OpenGL and Multi-threading
The Heidi OpenGL driver is not multi-threaded. However, it is "thread safe" and you can use it successfully with a multi-threaded application as long as you draw between a begin_picture and end_picture. In this case the driver locks and unlocks a mutex so that each thread is locked as it is drawing and then unlocks for the next thread and so forth.

Advanced 3D Options
A new rendition method, advanced_options, allows 3D options to be available for the OpenGL driver. When HT_Advanced_Options  is set to a value, certain hardware capability of specific drivers can be utilized, that is not normally available in Heidi. For example, the OpenGL driver anti-aliasing option can be set in the rendition which tells the driver to use the options, while Heidi itself will continue to draw normally. There is also no effect on Heidi if the driver is not in use and the option is set. The OpenGL anti-aliasing option will work for 3D and 2D plus Z. Also, the stereo option allows stereo for OpenGL, in 3D mode only. As this is a hardware option, it has no effect on Heidi.