Chapter 4. Implementing a New Application
This chapter details the minimal steps that are required to develop an application using Heidi. It outlines the construction of a renderer stack, its configuration for drawing, and the commands issued for drawing purposes.
Constructing the Renderer
Stack
Before using Heidi in your application, you must construct the
renderer stack from the device up to the top-most renderer. Of course, there
may not be any renderer stacked on top of your driver. Building only one
renderer (driver) may be sufficient.
Each Heidi device supplies the HD_Device entry point. Likewise, each Heidi renderer supplies the HD_Renderer entry point. These functions return a Heidi device or renderer object, respectively. These are subsequently used in driver configuration and drawing. Construction of the renderer stack is shown in the following example.
HT_Create_Device device_generator = (HT_Create_Device) HD_Load_Dynamic_Routine
("gdi10.hdi", "HD_Device");
HD_Assert (device_generator != null); // make sure we find the 10.hdi.hdi
HT_Create_Renderer renderer_generator = (HT_Create_Renderer) HD_Load_Dynamic_Routine
("szb10.hdi", "HD_Renderer");
HD_Assert (renderer_generator != null); // make sure we find the szb10.hdi
HT_Device m_device = (*device_generator)(); // create the device.
// use the device as the sink of the renderer szb10.hdi
HT_Renderer m_renderer = (*renderer_generator) (m_device);
Note: The renderer_generator function takes a renderer: this is the sink of the renderer in question. In other words, the stack must be constructed from the device up.
Configuring the Renderer
Stack
The next step in preparing your application for using Heidi is
to configure the renderers in your renderer stack. This involves setting the
various renderer options used to facilitate the renderer’s interaction with
your program, the operating system, and the hardware. For instance, you need to
indicate to the GDI driver which window handle and device context to use in its
drawing operations. Similarly, the software Z-buffer renderer needs to know
which Z-buffer depth to use. There is a set of options that all renderers
inherit. Renderers may also extend this set with custom options.
Here is an example of configuring the renderer stack from the file heidi_glue.cpp, in the sample SimpleNoMFC, located under the heididk\samples directory. The local variable window holds the application window handle.
// set window id
m_device.set_configure ("Window ID", HT_Option_Value((long)window));
//.get the window dc from the system and set it to the device contex id.
m_device.set_configure ("Context ID", HT_Option_Value((long) GetDC (window)));
// typically the window origin is at 0,0
m_device.set_configure ("Window Origin", HT_Option_Value(0, 0));
RECT client;
int rx,ry;
// find the window.size
GetClientRect (window, &client);
rx = client.right - client.left +1;
ry = client.bottom - client.top +1;
// set the window size
m_device.set_configure ("Window Size", HT_Option_Value(rx, ry));
// create a palette containing a color cube
stack HT_Color_Cube cube;
stack HT_Palette pal;
// typically Win95 and NT reserves 20 system colors so we usually use
// 216 colors as the Heidi color cube
cube.set_resolution (HT_Int_RGB (6, 6, 6));
// are we on an 8-bit device?
int has_palette = GetDeviceCaps(GetDC(window), RASTERCAPS) & RC_PALETTE;
if (has_palette) {
pal = m_device.physical ("Palette").as_palette (0);
cube.set_base_index (pal.first_available_block (cube.total()));
pal.install (cube);
m_device.set_configure ("Palette", HT_Option_Value (pal));
m_device.set_configure ("Color System", HT_Option_Value
(HT_Color_System::Mapped_RGB));
}
else
m_device.set_configure ("Color System", HT_Option_Value
(HT_Color_System::Direct_RGB));
m_device.establish ();
// get the z buffer going
m_render = (*mx_render) (m_device);
HD_Assert (m_render != null);
m_render.establish ();
After all options have been set, the establish() call is used to instruct the renderer to reconfigure itself using the new options. Here, the order of establish() calls made to the renderer stack is significant. First call establish() on the device, and then call it up through the renderer stack. This order makes sure that each renderer inherits (by default) the established options from its sink.
Your driver does not need to update renderer options until establish() is called. Furthermore, the Heidi application never calls establish()in the midst of drawing operations¾ within the scope of the begin_picture() <-> end_picture() drawing cycle. Consequently, you can safely assume that the renderer options are valid through out your drawing cycle.
Issuing Drawing Commands
After the renderer stack configuration has been completed, it is
time to start drawing. First, inform the device that you are going to begin
issuing draw calls with
begin_picture(). Next, ensure that you have a
rendition available. The rendition encapsulates all of the various
properties of your draw state (such as line color, line patterns, and so forth)
and is used as the first parameter to all draw calls. Configure the rendition
as needed. Next, issue the draw calls to the appropriate renderer.
Often, you communicate to the renderer at the top of the stack. In some cases, however, you may wish to communicate directly to a renderer in the middle of the stack or to the device at the bottom of the stack. When you’re finished drawing, use the update() method to instruct each renderer to send its output to its sink. As shown in previous sections, the typical use would be to call update() for each renderer from the top of the stack down to the device. Finally, call the device’s end_picture() method to end your drawing cycle.
Following is an example of issuing drawing calls, also from the sample SimpleNoMFC under your heididk\samples directory.
stack HT_Rendition rend(m_render);
stack HT_DC_Point points[3];
m_device.begin_picture();
m_render.begin_picture();
m_render.clear_z_buffer (rend);
rend.set_background_color(HT_RGB32 (0x23f, 0x3f, 0x0F));
m_render.clear_drawing_buffer (rend);
rend.set_color (HT_RGB32(0X4F, 0XaF, 0XFF));
points[0] = HT_DC_Point (300.0f, 100.0f, 0.0f);
points[1] = HT_DC_Point (700.0f, 100.0f, 0.0f);
for (int i = 0; i < 8; i++) {
points[0].y +=(i+50);
points[1].y +=(i+50);
m_render.draw_2d_polyline (rend,2, points);
}
m_render.update();
m_render.end_picture();
m_device.update();
m_device.end_picture();
Developing Heidi Applications
with MFC
The MFC (Microsoft Foundation Class) applications framework
provides a method for the rapid development of Windows applications. When the
functionality of Heidi is combined with this framework, the development of
highly interactive graphics software becomes much simpler.
To use Heidi in an MFC application, the MFC framework application must first be created. The easiest way to do this is to use the MFC AppWizard. After the AppWizard has created an MFC template application with your specific options, there are only two steps required to support Heidi. First, the Heidi header files are included in any Heidi relevant modules. (The main Heidi header is heidi/heidi.h.) Second, the heidi9.lib import/link library is included in the library file list. Heidi is then used in the same manner as previously outlined with the following recommendations:
Application Writing Tips
The application writer cannot issue the
establish() command inside the drawing loop. You must also
refrain from issuing the
set_configure() command inside the drawing loop as it does
not produce any results without issuing an establish()
command.
Another tip is to reuse the rendition when possible. Besides avoiding context switching, this also prevents unnecessary memory allocation.