Writing a Drawing Engine
This section shows how to write a new drawing engine and add it to the QuickDraw 3D Acceleration Layer.
To develop a new drawing engine and add it to the QuickDraw 3D Acceleration Layer, you need to perform these seven steps:
- IMPORTANT
- You need to read this section only if you are developing custom 3D acceleration hardware or software. If you simply want to create a draw context and draw into it using low-level drawing functions, see "Using QuickDraw 3D RAVE," beginning on page 1-13.
![]()
The following sections describe some of these steps in more detail. The section "Supporting OpenGL Hardware," beginning on page 1-30 contains information that is useful if you are implementing a drawing engine to support hardware that is based on an OpenGL rasterization model.
- Write methods for the public functions pointed to by the fields of a draw context structure (for example,
setInt). These methods are described in detail in the section "Public Draw Context Methods," beginning on page 1-115.- Write methods for the
TQADrawPrivateNewandTQADrawPrivateDeletefunction prototypes. These functions are called internally by theQADrawContextNewandQADrawContextDeletefunctions, respectively. You use these methods to allocate and release any private data (such as state variables) maintained by your drawing engine. These methods are described in detail in the section "Private Draw Context Methods," beginning on page 1-136.- Write methods for any texture and bitmap functions supported by your drawing engine (
TQATextureNew,TQATextureDetach,TQATextureDelete,TQABitmapNew,TQABitmapDetach, andTQABitmapDelete). These functions are called by their public counterparts (for example,QABitmapNew). These methods are described in detail in the section "Texture and Bitmap Methods," beginning on page 1-141.- Write a method to handle the
QAEngineGestaltfunction when your drawing engine is the target engine. This method is described in detail on (page 1-138).- Write a method to handle the
QAEngineCheckDevicefunction when your drawing engine is the target engine. QuickDraw 3D RAVE calls this method to determine which devices your drawing engine supports. This method is described in detail on (page 1-138).- Write a method for the
TQAEngineGetMethodfunction prototype. QuickDraw 3D RAVE calls this method to get some of your engine's methods during engine registration. This method is described in detail on (page 1-147).- Build your code as a shared library. The initialization routine of the shared library should register your drawing engine with QuickDraw 3D RAVE by calling the
QARegisterEnginefunction.
Writing Public Draw Context Methods
As you've seen, the draw context structure (of typeTQADrawContext) contains function pointers to the public draw context methods supported by your drawing engine. These methods are called whenever an application calls one of the public functions provided by QuickDraw 3D RAVE. For example, when an application calls theQADrawPointfunction for a draw context associated with your drawing engine, your engine'sTQADrawPointmethod (pointed to by thedrawPointfield) is called. TheTQADrawPointmethod is declared like this:
typedef void (*TQADrawPoint) ( const TQADrawContext *drawContext, const TQAVGouraud *v);A draw context structure is passed as the first parameter to all the public draw context methods you need to define. This allows your methods to find the private data associated with the draw context (which is pointed to by thedrawPrivatefield).Notice that the function prototype for a point-drawing method passes the draw context as a
constparameter. This indicates that your method should not alter any of the fields of the draw context structure passed to it. Only three draw context methods (namelyTQASetInt,TQASetFloat, andTQASetPtr) are allowed to alter the draw context.Listing 1-7 shows a sample definition for a point-drawing method.
Listing 1-7 A
TQADrawPointmethod
void MyDrawPoint (const TQADrawContext *drawContext, const TQAVGouraud *v) { MyPrivateData *myData; /*our actual private data type*/ /*Cast generic drawPrivate pointer to our actual private data type.*/ myData = (MyPrivateData *) drawContext->drawPrivate; /*Call our z-buffered pixel drawing function with xyz and argb, and also pass it the current zfunction, which is stored in the private draw context data structure. Note that this isn't a complete implementation! (We should be using kQATag_Width, for example.)*/ MyDrawPixelWithZ(v->x, v->y, v->z, v->a, v->r, v->g, v->b, myData->stateVariable[kQATag_ZFunction]); }Once you've defined the necessary public draw context methods, you need to insert pointers to those methods into a draw context structure. You accomplish this step in your
- Note
- See "Public Draw Context Methods," beginning on page 1-115 for complete information on the public draw context methods your drawing engine must define.
![]()
TQADrawPrivateNewmethod, described in the next section.Writing Private Draw Context Methods
Once you've written the public draw context methods supported by your drawing engine, you need to write several private draw context methods. In particular, you need to write aTQADrawPrivateNewmethod to initialize a draw context and aTQADrawPrivateDeletemethod to delete a draw context. TheTQADrawPrivateNewmethod is called whenever an application creates a new draw context by calling theQADrawContextNewfunction. Listing 1-8 illustrates a sampleTQADrawPrivateNewmethod.Listing 1-8 A
TQADrawPrivateNewmethod
TQAError MyDrawPrivateNew ( TQADrawContext *drawContext, const TQADevice*device, const TQARect *rect, const TQAClip *clip, unsigned long flags) { MyPrivateData *myData; /*Allocate a new MyPrivateData structure and store it in draw context.*/ myData = MyDataNew(...); drawContext->drawPrivate = (TQADrawPrivate *) myData; if (!myData) return (kQAOutOfMemory); /*Set the method pointers of drawContext to point to our draw methods.*/ newDrawContext->setFloat = MySetFloat; newDrawContext->setInt = MySetInt; ... return(kQANoErr); }As you can see, theMyDrawPrivateNewfunction defined in Listing 1-8 allocates space for its private data, installs a pointer to that data in thedrawPrivatefield of the draw context structure, and then installs pointers to all the public draw context methods supported by the drawing engine into the draw context structure.Your
TQADrawPrivateDeletemethod should simply undo any work done by yourTQADrawPrivateNewmethod. In this case, the delete method just needs to release the private storage allocated by theTQADrawPrivateNewmethod. Listing 1-9 shows a sampleTQADrawPrivateDeletemethod.Listing 1-9 A
TQADrawPrivateDeletemethod
void MyDrawPrivateDelete (TQADrawPrivate *drawPrivate) { MyDataDelete((MyPrivateData *) drawPrivate); }You register your private draw context methods with QuickDraw 3D RAVE using another private method, theTQAEngineGetMethodmethod. See "Registering a Drawing Engine," beginning on page 1-29 for details.Handling Gestalt Selectors
To support calls to the public functionQAEngineGestalt, your drawing engine must define aTQAEngineGestaltmethod. This method returns information about the capabilities of your drawing engine. For example, suppose that your drawing engine supports texture mapping and accelerates both Gouraud shading and line drawing. Suppose further that you have been assigned a vendor ID of 5, and that the engine ID of your engine is 1001. In that case, you could define a method like the one shown in Listing 1-10.Listing 1-10 A
TQAEngineGestaltmethod
TQAError MyEngineGestalt (TQAGestaltSelector selector, void *response) { const static char *myEngineName = "SurfDraw 3D"; switch (selector) { case kQAGestalt_OptionalFeatures: *((unsigned long *) response) = kQAOptional_Texture; break; case kQAGestalt_FastFeatures: *((unsigned long *) response) = kQAFast_Line | kQAFast_Gouraud; break; case kQAGestalt_VendorID: *((long *) response) = 5; break; case kQAGestalt_EngineID: *((long *) response) = 1001; break; case kQAGestalt_Revision: *((long *) response) = 0; break; case kQAGestalt_ASCIINameLength: *((long *) response) = strlen(myEngineName); break; case kQAGestalt_ASCIIName: strcpy(response, myEngineName); break; default: /*must flag unrecognized selectors*/ return (kQAParamErr); } return (kQANoErr); }If two different drawing engines should return identical vendor and engine IDs, QuickDraw 3D RAVE chooses the one that returns the most recent revision number (that is, the value returned for thekQAGestalt_Revisionselector). The larger number is considered newer.You register your
TQAEngineGestaltmethod with QuickDraw 3D RAVE using theTQAEngineGetMethodmethod, described in the next section.Registering a Drawing Engine
Once you written all the necessary public and private draw context methods, as well as methods to handle textures and bitmaps, you must write aTQAEngineGetMethodmethod that reports the addresses of some of those methods to QuickDraw 3D RAVE. Listing 1-11 shows a sampleTQAEngineGetMethodmethod. Notice that this method returns the addresses only of the private draw context methods and the methods to handle textures and bitmaps. The pointers for the public draw context methods are assigned directly to the fields of a draw context structure by yourTQADrawPrivateNewmethod (as shown in Listing 1-8).Listing 1-11 A
TQAEngineGetMethodmethod
TQAError MyEngineGetMethod (TQAEngineMethodTag methodTag, TQAEngineMethod *method) { switch (methodTag) { case kQADrawPrivateNew: method->drawPrivateNew = MyDrawPrivateNew; break; case kQADrawPrivateDelete: method->drawPrivateDelete = MyDrawPrivateDelete; break; case kQAEngineCheckDevice: method->engineCheckDevice = MyEngineCheckDevice; break; case kQAEngineGestalt: method->engineGestalt = MyEngineGestalt; break; case kQABitmapNew: method->bitmapNew = MyBitmapNew; break; case kQABitmapDetach: method->bitmapDetach = MyBitmapDetach; break; case kQABitmapDelete: method->bitmapDelete = MyBitmapDelete; break; default: return(kQANotSupported); } return(kQANoErr); }Finally, you register your drawing engine by passing the address of yourTQAEngineGetMethodmethod to theQARegisterEnginefunction:
QARegisterEngine(&MyEngineGetMethod);You can callQARegisterEnginein two ways. During product development, you can link your drawing engine code directly with a test application, in which case you should callQARegisterEnginefrom your application's initialization code. Alternatively, once you've completed development, you should build your engine's code into a shared library of type'tnsl'. In this case, you should callQARegisterEnginefrom the initialization routine of the shared library. When the shared library containing QuickDraw 3D RAVE is loaded, it searches for and loads any drawing engines contained in shared libraries in the current folder or in the Extensions folder.Supporting OpenGL Hardware
This section contains information that is useful if you are implementing a drawing engine to support hardware that is based on an OpenGL rasterization model. It describes special considerations for handling transparency and texture mapping.Transparency
QuickDraw 3D RAVE supports three transparency models: the premultiplied, interpolated, and OpenGL transparency models. Support for the OpenGL transparency model (indicated by thekQABlend_OpenGLconstant) should be automatic for hardware that is based on the OpenGL rasterization model. The other two models, indicated by thekQABlend_PreMultiplyandkQABlend_Interpolateconstants) may require emulation by your drawing engine.For example, consider the premultiplied blending function, specified by these equations:
(Here, the factors as, rs, gs, and bs represent the alpha, red, green and blue components of a source pixel; the factors ad, rd, gd, and bd represent the alpha, red, green and blue components of a destination pixel.)
OpenGL directly supports the premultiplied transparency blending function (and the interpolated transparency blending function) for the RGB components only. In other words, the alpha channel component (which is the same for both blending operations) cannot be directly implemented in OpenGL-compliant hardware. It is possible, however, to emulate these two transparency modes on OpenGL hardware, using several different methods. You can blend the RGB values only, or you can blend the ARGB values using a multipass algorithm. Which of these emulations you use depends on whether your drawing engine is associated with a frame buffer that stores an alpha channel or not.
- Note
- A complete description of how transparent objects are blended together with each of these models is provided in "Blending Operations" (page 1-49).
![]()
If your drawing engine is associated with a frame buffer that doesn't store an alpha channel value, you can implement the premultiplied and interpolated blending functions by simply ignoring the alpha channel component. These functions are then equivalent to OpenGL blending modes. The premulitplied blending function, with its alpha channel ignored, can be emulated by this function:
gBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);Similarly, the interpolated blending function, with its alpha channel ignored, can be emulated by this function:
gBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);To achieve a more complete blending, you can have your drawing engine rasterize each transparent object more than once, altering in each pass the blending mode, object alpha channel, and buffer write masks. The first pass should perform RGB blending. Accordingly, you should disable writing any alpha channel or z buffer data during this pass.
- IMPORTANT
- A drawing engine that uses this method of emulating the QuickDraw 3D RAVE blending functions on OpenGL hardware should not set the
kQAOptional_BlendAlphaflag of thekQAGestalt_OptionalFeaturesselector to theQAEngineGestaltfunction.![]()
/*first pass*/ glColorMask(TRUE, TRUE, TRUE, FALSE);/*disable alpha channel*/ glDepthMask(FALSE); /*disable Z buffer*/ if (premultpliedTransparency) glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); else glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); /*render the object here*/On the second pass, you should set the frame buffer alpha channel value to (1-as)(1-ad). To do this, you need to render the object again, with a different alpha value, as follows:
/*second pass*/ glColorMask(FALSE, FALSE, FALSE, TRUE);/*enable alpha channel*/ glDepthMask(FALSE); /*disable Z buffer*/ glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ZERO); /*render the object with alpha replaced with 1-a*/Finally, the third pass should replace the value in the alpha channel with the final value 1-((1-as)(1-ad)). To do this, you need to render the object again, with its alpha value set to 1, as follows:
/*third pass*/ glColorMask(FALSE, FALSE, FALSE, TRUE);/*enable alpha channel*/ glDepthMask(TRUE); /*enable Z buffer*/ glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ZERO); /*render the object with alpha replaced with 1*/After the third pass, the frame buffer contains the correctly blended object.Texture Mapping
QuickDraw 3D RAVE supports several texture mapping operations, which are controlled by the flags in thekQATag_TextureOpstate variable. Currently these flags are defined:
#define kQATextureOp_Modulate (1 << 0) #define kQATextureOp_Highlight (1 << 1) #define kQATextureOp_Decal (1 << 2) #define kQATextureOp_Shrink (1 << 3)To support the
- Note
- A complete description of texture mapping operations is provided in "Texture Operations" (page 1-51).
![]()
kQATextureOp_Modulatemode on an OpenGL-compliant rasterizer, you can use theGL_MODULATEmode, where thekd_r,kd_g, andkd_bfields of a texture vertex specify the modulating color. Note, however, thatGL_MODULATEdoes not allow these color values to be greater than 1.0, whereas QuickDraw 3D RAVE does allow them to be greater than 1.0. Values greater than 1.0 can provide improved image realism, and new hardware should support them. A more reasonable maximum modulation amplitude is 2.0.You can support the
kQATextureOp_Highlightmode by performing two rendering passes. The first pass should render the texture-mapped object (possibly also with modulation, as just described), and the second pass should add the specular highlight value.
/*first pass*/ glDepthMask(FALSE); /*disable Z buffer*/ /*render the texture-mapped object here*/ /*second pass*/ glDepthMask(TRUE); /*enable Z buffer*/ glBlendFunc(GL_ONE, GL_ONE); /*add highlight color*/ /*render the highlight color as a Gouraud-shaded object here*/On the second pass, you should render the highlight color, using theks_r,ks_g, andks_bfields of a texture vertex, as a Gouraud-shaded object.If the
kQATextureOp_Modulateflag is clear (that is, is no texture map color modulation is to be performed), you can support thekQATextureOp_Decalmode using the OpenGLGL_DECALmode. If, in addition, thekQATextureOp_Highlightflag is set, you need to perform two rendering passes, as just described.
- IMPORTANT
- There is currently no known method of accurately rendering to OpenGL-compliant hardware when both the
kQATextureOp_Decaland thekQATextureOp_Modulateflags are set. You should determine the best method of implementing this mode correctly on your hardware. If your hardware cannot handle both modes at once, you should ignore thekQATextureOp_Modulatemode wheneverkQATextureOp_Decalis set.![]()