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:
RendererInfo::DiskRenderMethod: The render plug-in can query all the render outputs which have been declared using RenderOutputDefine nodes. Each output has a target location where it is intended to be written to disk. The primary output will be loaded and displayed in the Monitor tab.
RendererInfo::PreviewRenderMethod: Preview renders are sent directly from the renderer to the Monitor tab using an inter-process communication protocol (IPC) where each bucket can be sent interactively (and progressively). Preview renders can display multiple AOVs in the Monitor tab but only one view.
RendererInfo::LiveRenderMethod: A live render follows the same principles as a preview render in terms of how the render is launched and how the rendered content is delivered to Katana. The difference is that the render process is kept alive to allow live attribute updates, which are sent to the render plug-in using IPC.
See the key stages of the render process and how they affect live rendering.
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:
The Katana recipe which is traversed using a FnScenegraphIterator, where the root iterator is retrieved using Render::RenderBase::getRootIterator. Utility classes and functions are available which assist with the scene graph traversal and attribute handling (see Processing the Katana recipe).
Render arguments consisting of the render method name which was used to launch the render process, render time (frame), etc. These arguments can be retrieved either directly as strings using Render::RenderBase::findArgument, or alternatively using explicit wrapper functions such as Render::RenderBase::getRenderMethodName and Render::RenderBase::getRenderTime which returns the render time as a float value.
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:
Render::RenderSettings: Parses renderSettings at /root.
Render::CameraSettings: Parses camera.geometry at /root/world/cam/<cameraName>.
Render::GlobalSettings: Parses <rendererName>GlobalStatements at /root (see Configuring global settings).
RenderOutputUtils: A collection of useful utility functions.
- 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:
Configure how the render outputs are handled. This includes where they are rendered to, whether they should be displayed in the Monitor tab, what pre- and post-processes are required, etc. This is done in Render::RenderBase::configureDiskRenderOutputProcess which is called for each output where the type of Render::RenderAction and its properties governs the workflow for the render output.
Loop over and process each render output in the render plug-in. The render outputs are parsed in Render::RenderSettings where they are retrieved using e.g. Render::RenderSettings::getRenderOutputs.
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.
See also
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.