Overview

The Renderer API allows the integration of renderers into Katana.

What is a render plug-in?

A render plug-in is a collection of modules which provide Katana with renderer-specific information that can be configured and declared to the renderer in the UI as well as handling the corresponding Katana recipes at render time. The modules used to implement a render plug-in are the following:

  • Render::RenderBase: Translates a Katana recipe into the renderer’s language using a scene graph iterator (FnScenegraphIterator), where the rendered content is typically visualised in Katana’s Monitor tab. A render plug-in represents a single render process, which is instantiated with the Katana recipe and render arguments when a render is launched, and which is destroyed when the render is complete or cancelled. The render plug-in can delegate the handling of scene graph location types to delegate plug-ins (see Render::ScenegraphLocationDelegate). This enables the registering and handling of new renderer-specific location types without changing the render plug-in. See Recipe utility classes.

  • RendererInfo::RendererInfoBase: Provides Katana with renderer-specific information needed to configure a renderable scene, e.g. shaders, render outputs. If applicable, this plug-in also advertises and configures nodes such as <RendererName>ShadingNode and <RendererName>OutputChannelDefine. It is also responsible for advertising supported render methods such as preview and disk renders.

  • Configuration nodes: Renderer-specific nodes are useful for configuring renderable properties such as global and object settings.

Advertising render methods to start a render process

A render plug-in must advertise one or more supported render methods which become available in the UI (unless explicitly hidden) and script mode. Render methods are created by adding instances of render method classes to the vector passed into RendererInfo::RendererInfoBase::fillRenderMethods. The base render method classes represent different ways of handling a render process in Katana:

Each render method can advertise and write render debug outputs to a file which are displayed in an external editor.

Render methods can be hidden from the UI using RendererInfo::RenderMethod::setVisible where it can still be used in script mode when launching a render process with StartRender.

A render method can customise the way a render is launched in Katana. By default, a render process will create a catalog item, register the render so that it may be cancelled etc., and report any render messages in the render log. These properties can be configured to launch a detached render where Katana will absolve itself from any involvement beyond launching the render process with the recipe. This is done by passing false into the following functions:

Batch rendering

Katana requires a batch render method so the RendererInfoBase automatically creates a DiskRenderMethod with default parameters which will be used for batch rendering. The parameters can be overridden in RendererInfoBase::configureBatchRenderMethod.

Launching render methods in the UI

The render methods are grouped in Katana’s Render menu based on their type. The method name is passed as an argument to the render process where the render plug-in can retrieve it using Render::RenderBase::getRenderMethodName.

Tip

It is useful to use the default name and label provided by the render method classes for the most common cases as the render menu collapses items whose corresponding methods share the same name and label. This is done to allow simplifying the render menu where multiple renderers have equivalent render methods. Furthermore, standardising the user experience across renderers makes it easier for users to find the render method they want to launch.

Starting a render

A render plug-in must implement the Render::RenderBase::start function which is called at the start of Katana’s render process. The plug-in has immediate access to:

Key stages of the render process

Implementing only the start function is sufficient for most renders. More flexibility is needed for live renders where the render process is kept alive and updates/commands can be sent to the render plug-in until the render is either stopped by the user, or the Katana session ends. This flexibility is provided by supporting optional functions which are called at different points during the render process:

  • Render::RenderBase::start: Called to start the render process. This is the only function that must be implemented by the render plug-in.

  • Render::RenderBase::pause: Pause the render/live render process. Currently it is only called during a live render when updating regions of interest.

  • Render::RenderBase::stop: Always called at the end of the render process.

  • Render::RenderBase::queueDataUpdates: Called asynchronously in a separate thread during a live render with the attributes that have changed. The updates can either be applied directly or queued here and applied later in the main thread. When and how often this function is called depends on the 3D Update Mode:

    • Manual: The queueDataUpdates() function is called when the user explicitly clicks the Trigger 3D Update button.

    • Pen-Up: The queueDataUpdates() function is called after an attribute value has changed anywhere in the scene graph.

    • Continuous: The queueDataUpdates() function is called with a fixed frequency (every 10ms) if attribute values have changed. Currently this only applies to camera and light transform updates in the viewer.

  • Render::RenderBase::hasPendingDataUpdates: Called after live update data or command has been processed asynchronously to inform the render process of whether some or all of the data updates have not been applied in the queueDataUpdates function.

  • Render::RenderBase::applyPendingDataUpdates: Called if hasPendingDataUpdates returns true. This is used to facilitate a workflow where a set of updates are queued in the update thread and then flushed in the main thread.

  • Render::RenderBase::processControlCommand: Called when a custom live render command is executed.

  • Render::RenderBase::stopLiveEditing: Called when a live render is stopped by the user.

Processing the Katana recipe and utility classes

Interpreting the Katana recipe into the renderer’s language involves a deferred evaluation of the scene graph. This is done by traversing the scene graph using a FnScenegraphIterator starting from the root. The render plug-in can get the root iterator using Render::RenderBase::getRootIterator. Each iterator corresponds to a scene graph location which contains a set of corresponding attributes. Scene graph location types and attributes are based on conventions where the render plug-in can selectively handle location types and attributes that are applicable to the renderer.

Scene graph delegates

The handling of scene graph locations and their attributes can be dynamically delegated to Render::ScenegraphLocationDelegate plug-ins. Given an arbitrary scene graph iterator (e.g. FnScenegraphIterator sgi), we can check if a corresponding delegate plug-in exists and invoke it using RenderOutputUtils::processLocation

bool pluginFound = RenderOutputUtils::processLocation(sgi, "myrenderer", sgi.getType(), 0x0, 0x0);

Scene graph utility classes

Utility classes are provided to parse standardised attribute conventions where they can be extended to provide renderer-specific behaviour through overrides:

Tip

Avoid using the iterator function getByPath to get an iterator for a location that has already been traversed as it will traverse the scene graph again from the root. It is generally preferable to use a cached iterator class unless the memory footprint is a critical issue.

Disk Render: Rendering one or more render outputs to disk

Disk renders can only be launched from a render node and the renderer has to provide a target filename for each render output (port). The rendered target file for the primary render output is read and displayed in the Catalog and Monitor tabs when the render is complete (if the file type is supported).

There are two steps required to configure and perform a disk render:

Following these steps, a simple workflow for a color pass which renders to a temporary location where the file is then copied to the target location could be as follows (note that the following code snippet omits the Foundry::Katana namespace for simplicity):

configureDiskRenderOutputProcess

// Get the render outputs from the render settings
RenderSettings renderSettings(getRootIterator());
RenderSettings::RenderOutput output = renderSettings.getRenderOutputByName(outputName);

// Generate a unique temporary local output location used by the renderer
std::string tempRenderLocation = RenderOutputUtils::buildTempRenderLocation(
                                     rootIterator, outputName, "render", "exr", frameTime);

// Use the path from the location plug-in as a final target location which
// is displayed in the Monitor tab
std::string targetRenderLocation = outputPath;

// Declare the render action used for this render output
std::auto_ptr<Render::RenderAction> renderAction;

// Determine the rendering behaviour based on the output type
if (output.type == "color")
{
    // Here we render to a temporary location and then convert and copy it to the final location
    renderAction.reset(new Render::CopyAndConvertRenderAction(targetRenderLocation,
                                                              tempRenderLocation,
                                                              output.clampOutput,
                                                              output.colorConvert,
                                                              output.computeStats,
                                                              output.convertSettings));
}

The attributes for each output are found at the root of the scene graph under renderSettings.outputs. The easiest way to get the attribute values for a particular render output is to use Render::RenderSettings::getRenderOutputByName.

start (or any function which processes the render outputs)

...
// Get all render outputs for a disk render
RenderOutputs outputs = renderSettings.getRenderOutputs();

// Iterate through and process each output
...
if (output.type == "color")
{
    // Get the render location which in this case is a temporary location
    // as defined by the render action
    std::string renderLocation = output.renderLocation;
    ...
}

Note

Disk renders do not support displaying arbitrary output variables (AOVs) in the Monitor tab.

Note

Katana’s Monitor tab currently only supports images of type EXR, CIN, RLA, and OIIO.

Preview Render: Rendering directly to Katana’s Monitor tab

A preview render can be launched from any node in the UI where the renderer sends the rendered content (e.g. buckets) interactively to the Monitor tab as soon as they are ready using the Display Driver API.

Katana starts a listener when the UI is launched which retrieves image and ID data over a communication protocol. Preview renders have no concept of render outputs but instead use channel buffers that contain the names of the selected outputs and the corresponding buffer IDs. The IDs represent buffers that are reserved in Katana’s Catalog tab and they are used by the display driver to make sure the image channel data goes to the right buffer.

The following exmample demonstrates how the channel buffers are queried from the render settings (note that the following code snippet omits the Foundry::Katana namespace for simplicity):

RenderSettings::ChannelBuffers buffers;
renderSettings.getChannelBuffers( buffers );

RenderSettings::ChannelBuffers::const_iterator it;
for( it = buffers.begin(); it != buffers.end(); ++it )
{
    RenderSettings::ChannelBuffer buffer = it->second;
    // Use buffer.bufferId and buffer.channelName with the Display Driver API
    ...
}

Note

Katana’s Monitor tab currently only supports images of type EXR, CIN, RLA, and OIIO.

Generating a render debug output

There are two steps involved in creating a render debug output:

  • Advertise that a render method supports debug outputs. This creates the necessary UI hooks to allow the user to ask for a debug output.

  • Check for specific render arguments in the render plug-in which indicate that the render process was triggered with the expectation that a render debug output file is created.

Advertising debug output support

The most common way of requesting render debug output in Katana is through the Debug submenu in a node’s context menu. In order to add a menu item for the renderer to the Debug submenu, a batch render method has to be defined with debug output supported. This is done using RendererInfo::RenderMethod::setDebugOutputSupported. The output file extension is set using RendererInfo::RenderMethod::setDebugOutputFileType. The text of the menu item is formatted as Open <rendererName> .[fileExtension] Output in External Editor, for example Open dl .nsi Output in External Editor. The menu item launches a render process with a render argument which specifies a target location for the render debug output (this is discussed more in the next section).

Disk/batch renders do not support a partial scene graph, so a preview render method is used if Render Only Selected Objects is turned on in the Scene Graph tab. A partial scene graph here will still include the /root and /root/world locations, so it is conceptually identical to a full scene graph.

A different type of partial scene graph output can be obtained using a scene graph location’s context menu, where a Debug submenu contains menu items that follow the same signature as the one above. These menu items can be made available using RendererInfo::RenderMethod::setSceneGraphDebugOutputSupported and RendererInfo::RenderMethod::setDebugOutputFileType as before. A current limitation is that only one render method can support this feature.

Debug output can be created from Python using the following functions in the NodeDebugOutput module:

NodeDebugOutput.WriteRenderOutput(node, renderer, filename=None, expandProcedural=True,
                                  openInEditor=False, customEditor=None, log=None)

NodeDebugOutput.WriteRenderOutputForRenderMethod(renderMethodName, node, renderer,
                                                 filename=None, expandProcedural=True,
                                                 log=None, openInEditor=True,
                                                 customEditor=None, sceneGraphPath=None,
                                                 printToConsole=False)

The former method always uses the batch render method and runs synchronously (blocking). The latter one supports any render method and runs asynchronously (non-blocking).

Creating a debug output in the render plug-in

We know that our render plug-in should create a debug output if renderOutputFile was passed as an argument to the render process (renderboot). The argument’s value can be retrieved using Render::RenderBase::getRenderOutputFile where the value specifies the target location of the debug output. Katana will by default open the debug output file when the render process has finished.

Another render argument is passed which indicates whether the bounded procedurals should be expanded which is queried using Render::RenderBase::isExpandProceduralActive.

Note

A debug output will not cancel a preview render which is in progress. However, a non-concurrent preview render will cancel a debug output process. A full debug output runs a synchronous disk render by default and therefore blocks the UI. A partial debug output using Render Only Selected Objects runs an asynchronous preview render which does not block the UI.

Setting the plug-in as the default renderer in Katana

Setting the default renderer in Katana is done using the environment variable DEFAULT_RENDERER=<rendererName>.

Linking the plug-in to its corresponding renderer info plug-in

The name used to register the render plug-in should be returned in the renderer info plug-in through RendererInfoBase::getRegisteredRendererName.

This tells Katana how to namespace attribute data provided by the renderer info plug-in (e.g. shaders), in such as way that it can be easily retrieved by the render plug-in.

Adding configuration nodes

GenericAssign nodes are useful to expose a set of scoped attributes which can be used to specify renderer-specific properties such as global settings. These nodes are created using an XML file which contains the attributes and corresponding UI hints, as well as the scene graph scope. The XML file has to reside in a directory called GenericAssign where it will be picked up and used to automatically register a node whose name matches the XML filename.

Configuring global settings (<rendererName>GlobalStatements)

A typical use for a configuration node is to declare global options which apply during the render process. The convention in Katana is to scope these global settings at the root of the scene graph under a group attribute named <rendererName>GlobalStatements. A fixed CEL statement is used to prevent users from being able to scope the global attributes arbitrarily. The following example shows how the GenericAssign node is configured to get this behaviour, as well as providing a simple attribute setup which utilises widget hints and conditional visibility:

<args format='1.0' scope='/root' fixedCEL='/root'>
  <group name='myrendererGlobalStatements' hideTitle='True' closed='True' groupInherit='False'>
    <group name='bucket' closed='True'>
      <string name='order' default='horizontal' widget='popup'>
        <hintlist name="options">
          <string value="horizontal"/>
          <string value="vertical"/>
          <string value="spiral"/>
        </hintlist>
        <help>Some help text</help>
      </string>

      <int name='orderorigin' default='0, 0' size='2' tupleSize='2'
          conditionalVisOp='equalTo' conditionalVisPath='../order' conditionalVisValue='spiral'/>
    </group>
    ...
  </group>
</args>

Here, we declared a closed group which encapsulates all bucket settings where a popup list offers a choice of three bucket orders and a conditional property is shown if the spiral value is selected.

The Render::GlobalSettings utility class can be extended where it retrieves the global statements based on Katana’s naming convention. The class can then freely be used to parse the attribute data and encapsulate global settings related functions.

Configuring object settings (<rendererName>Statements)

GenericAssign nodes can also be used to configure attributes for arbitrary scene graph locations based on a CEL statement. This allows assigning attribute data on a per object basis while traversing the scene graph. By convention, these attributes can be assigned to any scene graph location below (and including) the world location, where the attributes are grouped under <rendererName>Statements and can be inherited. The following example shows how to configure the scope as well as setting up a page which encloses a set of attributes (differently to a group attribute):

<args format='1.0' scope='/root/world//\* /root/world'>
  <group name='myrendererStatements' hideTitle='True' closed='True'>
    <page name="Geometry">
      <int name='invert_normals' default='0' widget='checkBox'>
        <help>
          Some help text.
        </help>
      </int>
      ...
    </page>
    ...
  </group>
</args>

Sending ID pass data to Katana

There are two aspects in terms of getting ID pass data to Katana:

  • Assign an integer value to a renderable scene graph location and advertise the mapping between the two to Katana.

  • Render the ID pass and send the single channel image to Katana.

The latter is naturally expressed as a channel in the Display Driver API so this section will focus on the former.

Render::IdSenderInterface is a utility class which handles the ID communication with Katana. It is used to both get a unique integer ID from Katana as well as sending the ID values and their corresponding scene graph location names to Katana.

Example:

int64_t nextId;
int64_t maxId;

// Get an instance of the ID sender interface from the factory
Render::IdSenderInterface* idSender;
idSender = Render::IdSenderFactory::getNewInstance(getKatanaHost(), frameID);

// Get the next unique ID integer value
idSender->getIds(&nextId, &maxId);

// Use nextId with the scene graph iterator (sgi) location

// Send the ID and scene graph location path
idSender->send(nextId, sgi.getFullPath().c_str());

A frameID as used above can be retrieved from a RenderSettings::ChannelBuffer object, e.g.:

RenderSettings::ChannelBuffers channelBuffers;
renderSettings.getChannelBuffers(channelBuffers);

// Here we assume the first channel buffer is valid and used
int64_t frameID = convertToInt(channelBuffers[0].bufferId);

Setting the number of render threads

There are two conventions used in Katana to declare the number of render threads for a launched render process:

  • Using a renderSettings.renderThreads attribute. The function Render::RenderSettings::applyRenderThreads can be used to apply the value if it has been set.

  • UI preferences and batch command line argument. Both override values are sent to the render process as a threads argument where it can be applied using Render::RenderBase::applyRenderThreadsOverride. Note that this value should always override every other thread value as the user has explicitly requested it.

    • UI mode: The interactiveRenderThreads3D value is sent to the render process if interactiveRenderThreadOverride is set to Yes in the Preferences dialog.

    • Batch mode: The thread count is set using the --threads3d argument when launching Katana where the value is sent to the render process.

Customizing the Network Material Group Context Layered Menu

A user, while viewing the node graph from within a NetworkMaterialCreate context, can press the Tab key to open a layered menu with an entry for each shading node type of the currently selected render plug-in. The user can switch between which of the render plug-ins is currently selected by pressing Shift+Tab which opens an additional layered menu populated with one entry per installed render plugin.

By default, the Tab menu is populated with the node types of the selected render plug-in and a subset of Katana node types.

Customization of the entries in the Tab menu can be achieved by first instantiating and then registering a LayeredMenu as described in the LayeredMenuAPI.

Functions have been provided to get and set the display name and color for a render plug-in.

The name and color can be set using:

RenderingAPI.RenderPlugins.SetRendererPluginDisplayName() RenderingAPI.RenderPlugins.SetRendererPluginDisplayColor()

The name and color can be retrieved using:

RenderingAPI.RenderPlugins.GetRendererPluginDisplayName() RenderingAPI.RenderPlugins.GetRendererPluginDisplayColor()

An example script is included in the Katana distribution:

plugins/Src/Resources/Examples/UIPlugins/RenderPluginsExamples.py