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.
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();
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);
HT_Create_Renderer renderer_generator =
(HT_Create_Renderer) HD_Load_Dynamic_Routine
("szb10.hdi", "HD_Renderer");
HD_Assert (renderer_generator != null);
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 ();
}
my_renderer = (*render_generator) (my_device); HD_Assert (my_renderer != null);
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 ( )
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));
.
}
A set of Heidi renderer options distinguishes between LSB versus MSB and On_Bit_Black versus On_Bit_White. These functions convert images from RGB32, Index8, Gray8 and bitonal format to the specified image padding, bitonal order. The routines save Heidi hardcopy devices from having to copy data and reverse bits on the fly (an expensive process), and also save each device driver from implementing similar utility routines-saving redundant code.
A new parameter bitonal_format, in the HT_Renderer:: draw_dc_bitonal_image interface, and a new renderer option with the same name, HT_Renderer_Options::bitonal_format, allows Heidi developers to call current_renderer_options().bitonal_format() and query for the driver's setting. Similarly, call set_bitonal_format with the proper HT_Bitonal_Format enum to control bitonal image padding. When dithering a true color, index 8, and gray scale image, Heidi standard routines pack the the output image based on the new flags HT_Bitional_Format::MSB and HT_Bitonal_Format::On_Bit_Black. Both bits are off by default.
Note: These standard routines will process a bitonal image of different padding to the proper format. In your device driver's draw_dc_bitonal_image, you should check to see if the new incoming format parameter, bitonal_format, matches your renderer option with the same name. If not, just punt to the HD_Standard_Draw_DC_Bitonal_Image, which will repack the image and call your draw_dc_bitonal_image again.
In addition, HT_Option_Table
organizes multiple options and option definitions into tables. A description of
the classes can be found in the class reference in appendix A, "Heidi
Class Reference."
A diagram of the relationship between the class objects is shown in
figure 1.
In a more specific example, a driver adds an option for
double-buffering. The organization of the classes for this option looks like
figure 2.

To add a driver or renderer specific option, you add the option to
the current or configured option table. The GDI driver, for instance, first
creates a class, GDI_Driver_Options, to hold an option frame buffer
handle that is not used in any other drivers.
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));}
.
}
my_device.set_configure ("GDI Frame Buffer Handle", (long)
my_frame_buffer_handle);
The driver may also create a shortcut member access function to
provide quick access to commonly referenced options. These functions are
available only to a driver class. In the above example, a class called GDI_Driver_Options
was created with methods that provide quick access to a commonly used option
called frame_buffer_handle. The frame_buffer_handle
method returns the value of the option, and the set_frame_buffer method
sets the value of the option to the input parameter value.
The constructor for the GDI_Driver_Options class takes a parameter which is the Heidi driver option table and populates it with the driver options specific to the GDI driver. Now, the driver can set the driver option very easily with the following:
configured_GDI_options().set_frame_buffer (0L);Applications can also use this technique to create shortcut classes for setting driver options.
Updating the Driver Action
Table
All of the drawing actions
HT_Renderer::draw_... make use of an action table that is
stored in the HT_Renderer object and described in appendix A, "Heidi
Class Reference." There is one entry in the action table for each of the
defined actions. Each entry in the action table includes a pointer to a
function that implements the described drawing action. The actual member
function HT_Renderer::draw_... itself calls on the implementation
function referenced by the pointer in the corresponding action table entry. It
passes the defined arguments it received from the calling application to the
implementation function, plus one more argument described in the next
paragraph.
The action table is initialized by the default constructor of class HT_Renderer to point to a set of standard actions provided by the Heidi system and discussed in the section "Action Tables," of chapter 2, Heidi Architecture." Recall that each renderer in the renderer stack can provide its own implementation of any of the drawing actions. The renderer object that provides the implementation of an action contains an implied rendering context: that is, state information relevant to the implementation of the action. State information such as the currently loaded texture in the driver can be used to minimize unnecessary texture loading. Therefore, each entry in the action table contains, in addition to a pointer to a function that implements the action, a pointer to the HT_Renderer object that sets the entry. This pointer to a renderer object is the additional argument passed to the implementation function, as a first argument, preceding the list of defined arguments of the action. For a list of the default drawing actions, see class HT_Drawing_Action.
To provide access to the device, every driver must provide an implementation of at least one drawing action at the end of the graphics pipeline: an image drawing routine specified in the following section. Generally, a driver provides implementations of additional drawing actions in case that the device provides hardware acceleration at higher levels of the pipeline: for example, 2D line drawing, Gouraud interpolation for triangles, or a full 3D geometry engine.
To substitute its own implementation of an action for the default implementation, a driver uses the macro SET_ACTION, which is defined in renderer.h and uses the method HT_Renderer_Data::set_action. For example, to overwrite a standard action call, in the action table, using a call to a driver-supplied routine, call the following:
SET_ACTION ( index, driver_supplied_action )
In this call driver_supplied_action
is a pointer to a function that performs the defined action. index
is the appropriate symbolic constant from the enumerated list declared in renderer.h.
Also driver_supplied_action must be a "friend" of the
driver object sub classed from the HT_Renderer class. For a list of
actions, see appendix A, "Heidi Class
Reference."
You have to call the SET_ACTION macro from a member function of your driver object class, Driver in the GDI driver example, derived from class HT_Device_Data. Typically, it is called from the driver object class constructor from the initialization function HD_Device, but it may also be called from establish_configuration when action entries can change dynamically during the life of the driver.
At a minimum, a driver must supply implementations of either draw_dc_index8_image or draw_dc_rgb32_image, depending on whether the device is an 8-bit indexed color or 24 bit true color device. The Heidi driver must also supply the clear_drawing_buffer function which is typically used to clear the background of your display with the background_color of the rendition.
The sample GDI driver class constructor sets its action table as follows:
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);
.
}
After the driver constructor returns, the application then examines the renderer options set by the driver. At this point, the application may possibly use those renderer options in its next task, setting the renderer parameters in preparation for drawing.
The application has the responsibility of creating a context into which the renderer draws. This includes a window and a drawing context. The application stores those in the device options "Window ID" and "Context ID." This is shown in the sample application code.
The application then sets the final renderer options. The setting of these options determines how the driver must render the primitives that follow in the drawing phase¾ see the sample application code in listing 1.
The application then calls HT_Renderer::establish() to establish the current configuration by reconciling the configured options into the current configuration table. Every driver must supply the pure virtual function of class HT_Renderer::establish_configuration.
int establish_configuration( ) is called from HT_Renderer::establish ( ) after the current option table has been updated in accordance with the configured options as set by the application. The function of establish_configuration is to bring the internal state of the driver into accordance with the current option table values that have been set by the application. The return value of zero indicates that the driver has succeeded in setting up the options. Otherwise, the establish_configuration has failed to use the chosen options for this particular driver.
At this point in initialization, the driver can retrieve the values
of the driver options that the application has set. These methods of the current_device_option()
method of the HT_Device class can be used to access the following
information:
In particular, the application will have updated the window size,
extents, and possibly origin. The application, typically, keeps
window_origin set to (0,0) and
window_size = window_extents. If the application
doesn't require Z buffering, it may have reset the buffer_depth.
Note: The buffer depth value is equal to the number of bits of depth stored per pixel in the depth buffer. No depth buffering is indicated by a buffer_depth value of 0.
During this function, the driver should examine how the application has set the additional options that the driver added earlier. It can do this by using the access member functions of the HT_Option_Value class. The driver should honor the option settings that the application has chosen.
load_palette
Every driver must supply the pure virtual functions of class
HT_Physical_Renderer required of all renderers, load_palette.
While all renderer options travel downward to their sink, palette propagates
upwards. This function is for loading a palette on a mapped color device. The
Heidi establish
method invokes load_palette before consolidating the palette option in
the renderer stack.
begin_picture
Every driver must supply a
begin_picture function for the pure virtual function of class
HT_Physical_Renderer. This method is called after the driver
has been configured and the driver options have been reconciled, but before any
drawing has occurred. The driver can use this message to prepare for any
drawing activity that occurs. This routine does not imply any resulting
activity to the screen. For example, the viewport does not need to be erased,
because the drawing methods to follow perform any necessary viewport clears.
The begin_picture_all method is a convenient way to signal to the driver the beginning of a sequence of drawing commands for each subsequent renderer in the stack.
Note: Drawing is valid only between calls to begin_picture and end_picture. Moreover, the application is free to interact with the device beneath Heidi and Heidi drivers when outside of the begin/end picture loop. Therefore, it may be necessary for the driver to preserve the device state or prepare to fully define the device state at begin_picture time.
Drawing Commands
After the driver has been configured and a
begin_picture message has been received, drawing commands can
be sent to the driver. The driver has the option of drawing these commands
immediately or buffering them for later processing. If the commands are
buffered, then a synchronize message is sent later, requesting that the driver
flush all commands to the output device.
Typically, a driver performs the drawing sequence by converting the requested primitive parameters into some form suitable to either the hardware or software graphics subsystem that the driver is controlling. When you write a driver for a windowing system, such as GDI, the Heidi primitives are converted into GDI drawing commands. When you write a driver that communicates directly with hardware, register level packets may be generated and sent to the hardware.
When implementing the drawing commands, it is important to honor the rendition values that are currently in effect. Each drawing command is provided a pointer to the current rendition. The rendition contains many attributes, which are grouped into logical areas such as lines, faces, colors, and so forth. Each group has an associated incarnation value. This value is unique to a particular set of values for a given group of attributes. For example, the line rendition contains attributes controlling line style, line weight, line caps, and line joins. For a given set of line attribute values, there is a unique value. If any attribute in the line rendition changes, for example, the color, then the incarnation value for that rendition is different. The driver can use this information to determine if any values in the rendition have changed since the last drawing command. This may allow the driver to skip redundant attribute setup functions. The driver is responsible for retaining the previous incarnation value for comparison.
The following code from wopengl10.hdi shows how the
OpenGL driver draws a simple graphics primitive, a dot:
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 ();
}
Other drawing actions are implemented in similar ways. In some cases, the driver provides a drawing action for a primitive but only implements special cases of those primitives. For example, a driver draw_dc_polyline routine might choose to draw only single pixel weight lines. When it receives a line weight greater than 1, it points to the standard DC polyline routine, HD_Standard_Draw_DC_Polyline.
update
Every driver must supply the pure virtual functions of class HT_Renderer
required of all renderers,
update. update is called by
the application to signal the end of the frame after it has called all of the update
functions of the renderers that are higher in the stack. The driver's
implementation of update must do all of the driver-specific actions
needed at the end of a frame. For example, if the driver buffers drawing
commands in any way, the update call signals the driver to flush these
buffers and pass on the effect of all of the drawing calls to the device. In
particular, in the case of double-buffering, the driver's implementation of update
causes the buffer swap to occur.
The update_all method is a convenient way to allow the renderer to perform all of the work needed following all of the drawing calls comprising a complete frame, similar to update, but for all parents of the current device.
end_picture
Every driver must supply an
end_picture function for the pure virtual function of class
HT_Physical_Renderer. This method is called after a drawing
sequence has completed and all updates to renderers in the stack have been
completed. The driver can use this message to perform any cleanup it might have
to do in preparation for termination or possibly another drawing sequence. This
call is matched with a
begin_picture.
The end_picture_all method is similar to end_picture, however, it is conveniently applied to all of the preceding renderers in the stack so that each one need not have an end_picture supplied separately.
Utility Functions
Heidi provides a useful tool,HD_Triangluate_Face,
that decomposes polygons into triangles. These polygons can be concave,
convex or have multiple loops (holes).
Versioning
This section describes how to add versioning capabilities, required
by Heidi, to your driver.
Alternatively, you may "roll your own" version resource using
the following steps:
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.
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 ENDThree of the string values use predefined variables. These are ProductVersion, ModulePlatform, and FileVersion. ModuleType 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (last_line_rendition != lr.line_incarnation()) {
<set pattern>
<set cap>
<set join>
<set weight>
last_rendition = lr.line_incarnation();
}
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();
}
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.
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.