From BlenderWiki

Jump to: navigation, search

This is a simple tutorial which introduces all of the basics needed to write a plugin to integrate an external renderer into blender.

[edit] A Few Starting Notes

I'm sorry if I forgot to mention--the Render API is a work-in-progress and currently available in this branch of blender: https://svn.blender.org/svnroot/bf-blender/branches/soc-2007-mosani/ Since none of this code is in official blender you won't have much luck trying to get it to work there.

I'm currently working on getting the system running under windows, but it's not quite there yet. Sorry.

This tutorial introduces all of the tools that will be needed to create plugins, however there will be some reorganization of the files and the build system. I would like to implement a system in which plugins may be detected by the build system via the existence of their folders within a directory, built along with blender, and installed in the default scan-directory for render plugins. However, this is not in place yet, so some of this may be subject to some change. However, the changes won't necessitate code changes within the plugins.

[edit] Includes & Files

Render API plugins require a set of .h files from a few different places. Two files are included directly in plugins, and these have a few dependencies.

  • PLU_api.h
    • PLU_types.h
    • PLU_util.h
    • PLU_gui.h
    • PLU_register.h
    • PLU_sequencer.h
    • PLU_audio.h
    • PLU_imbuf.h
    • PLU_renderapi.h
  • RenderAPI.h
    • RND_types.h
    • DNA_listBase.h

Of course, you'll also need to include whatever headers you need for interfacing with the particular external renderer. In the example of a simple RIB exporter, this additional include is simple <stdio.h>, because the export is saved in a file.

For the moment, place all plugin files within /render/render_api/plugins/{plugin-name}. I've written a build script "/render/render_api/aqsis/SConstruct" for the current aqsis RIB exporter plugin. For this tutorial I will assume you've copied this and modified the library and source file names, although you could write your own build script.

[edit] Getting Access to the API

In order to get access to the API functions, you have to import them using the plugin API IMPORT macros. (Note, if you have more than one file--which for complex exporters you ought to--you'll need to use the EXTERN macros). These import the API functions via structures filled with function pointers. I've arranged the naming such that all that will change in syntax is:

RND_{object}_{operation}( {args} ) ...changes to:
RND_{object}->{operation}( {args} )

and in the case when the object is "scene" or "result":

RND_{operation}( {args} ) ...changes to:
RND->{operation}( {args} )

In the basic case, the macros you'll need to use are these:

/* in the main file */

PLU_IMPORT_API(register)
RND_IMPORT_FULL_API

/* in any additional files */
RND_EXTERN_FULL_API

These macros create the global structures needed to access the API functions.

[edit] Where The Code Goes

The new render pipeline has three callbacks embedded in it:

  • void setup( RNDScene scene ) - executed at the start of the entire render job.
  • void render_frame( RNDScene scene, RNDResult result ) - executed for each frame.
  • void finish( RNDScene scene ) - executed at the end of the entire render job.

The setup callback is used for the configuration of settings that persist across all frames. Obviously, this includes image settings: width, height, pixel filter... Also, eventually this will be where instanced objects are declared. You declare the geometry in this callback, then instance it in render_frame when it is used.

/* Example Setup Callback */

static void setup( RNDScene scene )
{
	FILE *aqsis_rib = fopen( "aqsis.rib", "w" );
	fprintf( aqsis_rib, "# Generated by Aqsis Plugin - Aaron Moore\n\n" );
	fprintf( aqsis_rib, "Display \"aqsis.jpg\" \"framebuffer\" \"rgb\"\n\n" );
	fprintf( aqsis_rib, "Format %d %d 1\n",
		RND_get_image_width( scene ),
		RND_get_image_height( scene ) );
	fprintf( aqsis_rib, "Projection \"%s\" \"fov\" %f\n",
		RND_is_perspective( scene ) ? "perspective" : "orthagonal",
		RND_get_lens( scene ) );
	fprintf( aqsis_rib, "Scale 1 1 -1\n" );
	fclose( aqsis_rib );
}

The render_frame callback is where the meat of the export goes. All the information needed to create an image for a single frame must be exported here, and unless the export is creating an archive (such as a RIB file), image data is also sent back into blender.

In the case of external renderers, you must use the particular protocol for mid-render progress updates. I'm afraid I haven't had enough time to look into one of these to give an example, the only experience I have is blender internal.

Example of Render API Loop

/* Export Meshes */
object = RND->get_objects( scene, RND_GEOMETRY );
for( ; RND_object->exists( object ); RND_object->next( object ) )
{
	if( RND_object->get_type( object ) == RND_GEOMETRY_POLYGON_MESH ){
		fprintf( aqsis_rib, "\tAttributeBegin\n" );
		fprintf( aqsis_rib, "\t\tSurface \"plastic\"\n" );
		fprintf( aqsis_rib, "\t\tSides 2\n" );
		transform_object( object, aqsis_rib );
		export_polygons( object, aqsis_rib );
		fprintf( aqsis_rib, "\tAttributeEnd\n" );
	}
}

An export consists of looping through various things. Above is an example of what a standard loop looks like. Also, /render/render_api/includes/RenderAPI.h is meant to be used as a reference during coding. It's a very short but informative read: I'd advise reading over it b It defines all prototypes and enums that are used in the API. Remember though, that the versions of these functions accessible to plugins are of the form RND_{object}->{operation}( {args} ). I've tried to keep the naming conventions consistent to make learning the API quick and painless.

For lack of a better example, I'll show you the code used by the internal renderer to send data back into blender for saving and drawing.

static void send_image_results( RNDResult result, RenderPart *part )
{
	RNDPass pass;
	int bounds[4]; /* region of overall image sent */
	pass = RND_get_passes( result );
	for( ; RND_pass_exists( pass ); RND_pass_next( pass ) )
		if( RND_pass_get_type( pass ) == RND_PASS_RGBA ){
			bounds[0] = part->disprect.xmin;
			bounds[1] = part->disprect.xmax;
			bounds[2] = part->disprect.ymin;
			bounds[3] = part->disprect.ymax;
			RND_pass_send_data( pass, bounds, part->result->renlay->rectf );
		}
}

Currently the RND_pass_send_data function assumes it's receiving the RND_PASS_RGBA data: this will be fixed soon. The part->result->renlay->rectf is the buffer for the piece sent.

[edit] Hooking Up the Plugin

Finally, in order for any of this to work it must be registered. This is done through the implementation of one final callback: plugin_query(). This is the only function called when the shared libraries are opened by blender, so it is responsible for all plugin registration.

For renderer plugins, the function used is for registration is PLU_register->render_output. Here is the prototype:

plugin_descriptor_t * (*render_output)(
	const char* name,

	const char* menu_entry,

	const char* author,
	const char* copyright,
	const char* help,
	const char* date,
		
	unsigned long long capabilities,
	
	plugin_instance_t* (*init)(plugin_control_t * c),
	plugin_instance_t* (*load)(plugin_control_t * c),
	void (*free)(plugin_instance_t* This),
		
	void (*setup)( RNDScene scene ),
	void (*render_frame)( RNDScene scene, RNDResult ),
	void (*finish)( RNDScene scene ) );

And a simple usage example:

plugin_instance_t* init( plugin_control_t *c ){ return NULL; }
void free_renderer( plugin_instance_t *This ){}

void plugin_query()
{
	PLU_register->render_output(
		"Aqsis Plugin",
		"Aqsis",
		"mosani, and others?",
		"GPL",
		"Help.",
		"14 July 2007",
		0,
		init, init, free_renderer,
		setup, render_frame, finish );
}

Honestly, not all of the features provided in registration are implemented yet. The plugin_instance_t structure will eventually be used for plugins to store data that will persist between callback calls. For example, in an archive, the file pointer could be stored here, and simply retrieve in each render_frame call. However, as this is not implemented yet, a temporary stand-in is necessary to make registration work.

Currently the important pieces are:

  • name -- must be unique over all plugins.
  • menu_item -- what appears in the "Render Engine" menu in the render panel.
  • setup, render_frame, finish -- the callbacks.

[edit] Building & Scan Directory

If you're using a modification of my SConstruct file, open a terminal in the /render_api/plugins/{plugin-name} directory, and type "scons install". This will build the plugin and then copy the shared library into the /render_api/plugins directory.

VERY IMPORTANT!

In order for any of this to do anything, you must set the scan directory to the directory with the plugin shared libraries. Go into the user-defined file paths: there is a new one for render plugins. Direct this into the source/blender/render/render_api/plugins folder, and save the user defaults. When you restart blender, the plugin API will scan this folder, register your plugin, and a menu item will appear in the "Render Engine" menu. In order to use it, toggle on the "Render API" button, and hit render.

[edit] Last Notes

I hope to make this more complete and helpful in the future. For the moment, I *think* I've provided all the basic information needed to create a basic plugin: however, if I've missed something or parts don't make sense, please let me know:

  • two.a.ron [at] gmail [dot] com
  • IRC: mosani, I'm often on #blendercoders (on freenode)