[SESI logo]

Houdini Development Toolkit - Version 6.5

Side Effects Software Inc. 2004

Render Output

Output Drivers

Generation of custom Output Drivers can be very simple, or it can be the most complex of the custom operators. In the simpler cases, the Output Driver deals with simple data structures (i.e. saving geometry from a specified SOP). In the most complex case, it by-passes the IFD API and traverses all the data structures independently. The complex case requires a full understanding of how all the operators in Houdini are hooked up together. It also requires an intimate knowledge of how parameters and channels are evaluated.

One of the simplest cases is to generate a new IFD_Output class (see IFD_SimpleRib.C or IFD_CustomRender.C). In this case, there's actually no need to write a custom Output Driver since the IFD_Output will appear automatically in the general scene renderer. However, there may be cases when you need additional information passed down to the IFD_Output class. This requires writing a custom Output Driver.

In reality, there are really two types of Output Drivers, those which use the IFD classes to do the rendering, and those which don't. The following table shows which renderers use the IFD interface and which are non-IFD renderers:

IFD Drivers Non-IFD Drivers
Mantra3
RenderMan
Object Scene
Composite
Geometry
Network


Non-IFD Output Drivers

One of the tricky things about the Output Drivers is the way that they specify parameter templates (PRM_Template). The base class (ROP_Node) has several parameters which must be defined (and defined in the same location for each and every Output Driver). This is accomplished using the PRM_TemplatePair class to simulate inheritance of parameter types.

When defining the parameters for the new Output Driver, simply build a standard PRM_Template array which defines the custom parameters for the Output Driver. For example, if we needed parameters for resolution, the PRM_Template array might look something like:

static PRM_Template customTemplate[] = { PRM_Template(PRM_INT, 2, &resolutionName, resolutionDefault), PRM_Template() }; Then, when the Output Driver operator is built, a PRM_TemplatePair class must be constructed. This is done in the following code sample. The order of construction is very very important. If the order is incorrect, all applications will core dump when different Drivers are evaluated. OP_TemplatePair * ROP_MyDriver::getCustomTemplatePair() { static OP_TemplatePair *finalTemplate = 0; if (!finalTemplate) { OP_TemplatePair *custom; custom = new OP_TemplatePair(customTemplate); finalTemplate = new OP_TemplatePair( ROP_Node::getROPbaseTemplate(), custom); } return finalTemplate; } Of course, the above method should be declared as a static method in the class definition, since this code is called before any of the nodes have been instantiated.

Unlike many other OPs, the ROP_Node class doesn't have a "cook" method. Instead, there are three virtual methods which may be defined by a sub-class:

  • startRender This is passed in the number of frames which will be rendered as well as the start and end time for rendering. This method will be called even if there is only one frame being rendered.
  • renderFrame This method must be defined by the sub-class. It is used to render a single frame.
  • endRender This method is called at the conclusion of rendering (even if the render open fails).
  • Each of these methods should return 1 on success or 0 if there was a failure. For example, the geometry renderer simply has the following for these methods:

    // don't care about parameters int ROP_Geometry::startRender(int, float, float) { myObject = getObject(OBJNAME()); mySOP = getSOP(myObject, SOPNAME()); if (!mySOP) { addError(ROP_NO_OUTPUT, (const char *)getName()); return 0; } return 1; } int ROP_Geometry::renderFrame(float time, UT_Interrupt *) { OP_Context context(time); const GU_Detail *gdp = mySOP->getCookedGeo(context); if (!gdp) { addError(...) return 0; } if (gdp->save(GETSAVEPATH(time)) < 0) { addError(...) return 0; } return 1; } int ROP_Geometry::endRender() { return 1; }

    IFD Output Drivers

    These drivers should be sub-classed from ROP_IFD.

    As it was stated before, the simplest case of an IFD Output Driver is simply to use the generalized Object Scene Output Driver. This requires no custom code what so ever. However, in some cases, additional parameters need to be passed down to the IFD classes.

    Once again, when specifying parameters through the PRM_Template mechanism, the IFD classes are quite tricky. There is a static method in ROP_Node::getROPifdTemplate(PRM_Template *command). This method must be used to generate your PRM_TemplatePair for the OP_Operator construction. It is possible to add parameters by passing in a template which contains the additional parameters. However, the first parameter in the addional template must have the PRM_Name specified by ROPcommand (in ROP_Shared.h). For example:

    static PRM_Default dialogScriptDefault(PRM_DialogRenderers, "custom_command -custom options"); static PRM_Template customTemplate[] = { PRM_Template(PRM_COMMAND, 1, &ROPcommand, &dialogScriptDefault), ... additional parameters here ... PRM_Template() }; static PRM_TemplatePair * ROP_MyIFD_Driver::getTemplatePair() { return ROP_Node:::getROPifdTemplate(customTemplate); }

    Since this class is sub-classed from ROP_IFD, the actual startRender, renderFrame, and endRender methods are actually already written for you. However, there are still a few things that need to be filled out for the class to work correctly.

    Firstly, when the OP_Operator is being built, you should register your renderer as a "special renderer". This prevents the IFD_Output from showing up in the Object Scene Driver as well as in your custom driver. So, the code might look like this:

    (static) void ROP_Custom::registerMe() { // The name passed in should be the name from the IFD_Entry ROP_IFD::registerSpecialRenderer("custom"); } void newDriverOperator(OP_OperatorTable *table) { table->addOperator(new OP_Operator("custom", "Custom Render", ROP_Custom::myConstructor, ROP_Custom::getTemplatePair(), 1, 1, 0)); ROP_Custom::registerMe(); } Then the only other thing to do is to make sure that you get the correct IFD_Entry for rendering. This can be guaranteed by overriding the ::getRenderType method. For example: IFD_Entry * ROP_Custom::getRenderType(const char *) { IFD_Table *table = IFDgetTable(); IFD_Entry *entry; for (entry = (IFD_Entry *)table->iterateInit(); entry; entry = (IFD_Entry *)table->iterateNext(entry)) { // Return once we've found the correct IFD_Entry. if (!strcmp(entry->name, "custom")) return entry; } return 0; } One last thing with a custom IFD renderer. The whole purpose was to allow the passing of additional parameters. Unfortunately, since the API is relatively closed, there's no easy mechanism for passing in these parameter values. The only way is actually a kludge and requires hacks on both the ROP_Node side and the IFD_Output side. The only way of passing in additional information is to over-load one of the existing parameters and stuff the additional information into it. For example, if the IFD_Output doesn't allow rendering to a script, simply fill in the script name with a string containing your additional information (make sure to leave the output script toggle off!). Or, if the IFD_Output doesn't use the device field, you can specify the device (by over-riding the virtual ROP_IFD::getFrameDevice());

    It's not pretty, but it does work.


    Table of Contents
    Operators | Surface Operations | Particle Operations | Composite Operators | Channel Operators
    Material & Texture | Objects | Command and Expression | Render Output |
    Mantra Shaders | Utility Classes | Geometry Library | Image Library | Clip Library
    Customizing UI | Questions & Answers

    Copyright © 2004 Side Effects Software Inc.
    477 Richmond Street West, Toronto, Ontario, Canada M5V 3E7