GPU Shader Plugins for RenderMan

June 2006


1 Introduction

Film-quality rendering is a voracious consumer of computing power. This computational horsepower traditionally comes from mainstream CPUs such as Intel Pentiums. Nearly all modern desktop computers, however, contain a very powerful second processor that goes largely unused by most applications--the graphics processor (GPU). Many GPUs are more than 10x more powerful than CPUs. This PRMan plugin gives users the ability to leverage this power to accelerate PRMan shading by replacing "hot spots" in RSL shaders with GPU replacements.

The PRMan GPU shading plugin executes user-defined GPU shaders inside the PRMan shading engine. The GPU shader executes on exactly the same shading samples as the CPU shading system, so results from GPU-shaded renders will be the same quality as CPU-based shading. The GPU shaders are compiled to PRMan shader plugin and called from RSL in the same way that C/C++ plugin functions are called.

Please note that this plugin is currently experimental and is supported on only a subset of PRMan platforms. We are providing this plugin as a tool for our customers to evaluate the benefit of GPU-accelerated shading in their preview or final frame renders.

2 Intended Audience

This document assumes readers are familiar with the RenderMan shading language, PhotoRealistic RenderMan's shader plugin function mechanism. We do not expect readers to know GPU shading languages such as Cg, GLSL, HLSL, but a familiarity with them will help.

Users intending to write GPU shaders will need to learn the Cg shading language. There are a large number of Cg tutorial resources available online and in print. The free Cg compiler documentation (available from developer.nvidia.com) comes with a tutorial book in PDF form. For a printed reference, see "The Cg Tutorial," "GPU Gems," or "GPU Gems 2.". Lastly, a large number of Cg shaders are available online at www.shadertech.com.

3 Quickstart Usage Example

We begin with a simple usage overview and example, which consists of the following steps:
  1. Set up your environment.
  2. Write GPU shader in the Cg shading language.
  3. Compile Cg shader with the "gshader" compiler. This creates a shader plugin (DSO).
  4. Call the Cg shader from RSL shader just like any other plugin function call.

Simple Example

  1. Set up your environment to specify the location of RenderMan's built-in plugins.

  2. Write Cg shader "myCgShader.cg"
        void myCgShader( uniform float a,// uniform float input
                                 float3 b, // varying float3 input
    		     out     float3 c )// varying float3 output
        {
            c = a * b;
        }
    

  3. Compile Cg shader, which generates a plugin ("myCgShader.so")
        gshader myCgShader.cg
    

  4. In an RSL shader, declare the plugin and call the Cg shader.
        plugin "myCgShader";
        
        surface myRslShader ( uniform float a = .5;
                              uniform color b = (1,1,1) )
        {
            myCgShader( a, b, Ci );
        }
    

  5. Compile RSL shader
      shader myRslShader.sl
    

  6. Construct a test scene "test.rib".
        Display "test.tiff" "tiff" "rgb"
        Projection "perspective" "fov" 40
        Rotate -90 1 0 0
        Translate 0 -5 0
        WorldBegin
            Surface "myRslShader"
            Sphere 1 -1 1 360
        WorldEnd
    
  7. Render in single-threaded mode:
        prman -t:1 test.rib
    


4 PRMan Cg Shader Writing Guidelines

The PRMan GPU shading system supports most of the Cg shading language. Clean integration with the RSL shading engine, however, does require some restrictions. The following is a list of guidelines for writing Cg shaders that are compatible with the PRMan GPU system (i.e., shaders that will compile with gshader).

  1. The top-level Cg shader function must have a void return type. Shader output parameters must be specified with the "out" keyword.

  2. The top-level Cg entry function name is used to name the generated PRMan shader plugin (.so) and the RSL plugin function. If the top-level Cg entry point is not the basename of the Cg shader file, the Cg entry name must be passed to gshader via -entry. For example, a Cg entry of myShader should be in a file called myShader.cg, gshader will produce myShader.so, and the RSL plugin function will be called myShader. If the Cg file were instead called someOtherShader.cg, you would call gshader with "gshader -entry myShader someOtherShader.cg".

  3. Cg shader arguments that have type "float3" are represented by color arguments in the compiled RSL plugin function. Passing a point or vector requires an explicit cast (as shown in the next example).

  4. Cg textures are declared according to standard Cg syntax. The RSL call-site, however, passes the name of the texture (a string) as the argument for a sampler shader parameter. For example:
        // Cg Example Texture Use:
        void myCgShader( uniform sampler2D tex,
                         float3 coord,
                         out float3 result )
        {
            result = tex2D(tex, coord.xy).xyz;
        }
    
        // RSL Example Texture Use.  Note that point must be cast to color.
        myCgShader( "someTexture", color P, Ci );
    

    If someTexture is a PRMan texture, the GPU runtime automatically loads it into GPU memory. Alternatively, users may manually load and configure GPU textures at startup time. This procedure is described in Section 6.2.

    Note that the PRMan software shading engine is capable of handling much larger textures than can be used with a GPU. For this reason, the intended uses of GPU textures include lookup tables (e.g. for noise), pre-rendered shadow maps, small volumes (e.g., volume rendering or light transport approximations), etc.

NOTE: Cg parameter names beginning with _GSHADER_ are reserved by gshader. You should not name any shader parameters beginning with this string.

Cg and RSL Type Equivalence

Cg Type RSL Type
float float
float3 color
float4x4 matrix
samplerTD (T = 1D, 2D, 3D, CUBE) string

Supported Cg Parameter Types

input uniform float
  float3
  float4x4
  sampler1D
  sampler2D
  sampler3D
  samplerCUBE
  RxShadow
input varying float
  float3
output varying float
  float3

The system also supports arrays of uniform and varying inputs. Varying output arrays are not supported.


Cg Shader Resource Limits (NV4x)

Resource Limit
Textures 14
Uniform and Varying Inputs 254 total
Outputs 4 total

NOTE: These limits apply to the RenderMan GPU plugin used with an NVIDIA NV4x GPU.

Using PRMan textures with Cg shaders

The GPU co-shading system supports automatic loading of PRMan textures for use with GPU shaders. The system automatically resolves file paths in the same way that PRMan does and derives the correct OpenGL texture format for the texture. The system also automatically detects and loads shadow maps and the associated viewing matrices.

To use a PRman texture in a GPU shader, simply declare the corresponding Cg texture type (e.g., sampler2D or sampleCUBE) and call the GPU plugin function from RSL with the name of the texture.

For example, the following Cg shader use a 2D texture:

    void gpuTexShader( uniform sampler2D tex,
                       float s, float t,
                       out float3 result )
    {
       result = tex2D(tex, float2(s,t)).xyz;
    }

and the following RSL shader calls the above shader:

    plugin "gpuTexShader";
    surface myRslShader (uniform string texName = "";)
    {
        gpuTexShader( texName, s, t, Ci );
    }

Similarly, a cube map GPU lookup is performed as:

    void gpuCubeShader( uniform samplerCUBE tex,
                        float3 coord,
                        out float3 result )
    {
       result = texCUBE(tex, coord).xyz;
    }

GPU shaders can use mipmapped PRMan textures if texture coordinate derivatives are computed in PRMan. The following Cg shader demonstrates this usage:

    void gpuMipShader( uniform sampler2D mipTex,
                       float s, float t, 
                       float ds, float dt,
    		       out float3 result )
    {
      result = tex2D(mipTex, float2(s,t), ds, dt).xyz;
    }

and is called by the following RSL shader:

    plugin "gpuMipShade";
    
    surface mipRslShader( uniform string texName = "" )
    {
       gpuMipShader( texName, s, t, Du(s), Dv(t), Ci );
    }

Note that GPU mipmap lookups are approximate and may produce artifacts relative to PRMan mipmapping.

The GPU shading system handles PRMan shadow maps differently than other textures in order to facilitate shadow lookups. The system automatically loads the shadow matrices, their inverse matrices, and provides convenience shadow lookup functions. To use a PRMan shadow map in a Cg shader:

    #include "rmanGpuStdLib.h"
    
    void gpuShadow( uniform RxShadow shadow,
                    float3 ptInWorldSpace,
                    out float isInLight )
    {
       isInLight = shadow.vTex2D_D24(ptInWorldSpace);
    }
When compiling, specify the following include path:
    gshader -I$RMANTREE/include gpuShadow.cg

Note that we declare the shadow map with the Cg structure "RxShadow" instead of "sampler2D." The RxShadow struct and other utility functions are defined in the Cg header "rmanGpuStdLib.h". This file is included in the RenderMan Pro Server developer kit. The RxShadow lookup is point sampled. Percentage closer filtering may be implemented by manually multisampling the shadow for higher-quality shadows.

If the shadow map is a GL_DEPTH_COMPONENT texture, use the "vTex2D_24" method to perform a shadow map lookup based on a point in world space. If the shadow map is a 1-component (alpha) fp32 texture (not a depth texture), use the vTex2D_FP32 method. (This makes the z-values linear, not perspective-corrected. It is easier to apply a shadow map bias in this space than in the OpenGL-style perspective-corrected shadow map.) This method takes a "bias" argument that works identically to the bias used in PRMan shadow calls.

The PRMan-to-GPU texture system currently has the following limitations:

Example Usage of all supported Cg types

  void myCgShader( uniform float       a,   // uniform float input
                   uniform float3      b,   // uniform vector input
                   uniform float4x4    c,   // uniform matrix input
                   uniform sampler1D   texA,// 1D texture
                   uniform sampler2D   texB,// 2D texture
                   uniform sampler3D   texC,// 3D texture
                   uniform samplerRECT texD,// RECT texture
                   uniform samplerCUBE texE,// Cube map texture
                   uniform RxShadow    texF,// PRMan shadow map
                           float       d,   // varying float input
                           float3      e,   // varying vector input
                       out float       f,   // varying float output
                       out float3      g )  // varying float3 output
  {
  }

5 Advanced Features

The PRMan GPU system provides a number of advanced features for users looking to leverage advanced GPU features or obtain higher performance. This section describes how to perform multipass GPU shading, allocate varying temporary GPU variables, define GPU textures, write custom plugin initialization functions, and compile your own plugin against the PRMan GPU API.


6.1 Include a user-defined initialization function

Like RSL plugins, GPU shader plugins may optionally define a custom initialization function. This routine is called once when the plugin is first loaded. This initialization routine can be used to initialize GPU textures or perform any other one-time operation specific to the shader.

Initialization routines for GPU plugins must be first compiled in a separate object file, and then included in the gshader compilation of the GPU shader. The complete instructions for this process are below:

  1. Write a C++ file with a plugin initialization function (with extern "C" linkage). (See the RSL Plugin application note for more information.)

        #include "RslPlugin.h"
        extern "C" {
          void myInitFunction(RixContext* context) {  ...  }
        }
    

  2. Compile the C++ file to a relocatable object file (or a library)
        g++ -c -fPIC -O3 -I$RMANTREE/include myInitFunction.cpp

  3. Pass options to gshader that specify the name of the initialization function and the object file (or library):
          gshader myCgShader.cg -init myInitFunction -ldArgs "myInitFunction.o"
    


6.2 Defining GPU textures

The GPU co-shading system enables users to define and configure their own OpenGL textures, then register them with the PRMan GPU runtime for use during rendering. This feature is useful when defining procedurally generated textures (esp. lookup tables), when using GPU texture types not supported by PRMan (e.g., 3D textures), or for using advanced OpenGL texture state options.

To use an arbitrary OpenGL texture with the GPU shading system, create an OpenGL texture in a user-defined initialization function (Section 6.1). Configure the OpenGL texture object, then associate it with a name string and register it by making the following API call:

    void AddTextureGPU( const char* texNameStr, GLenum oglTexType,
			GLenum oglInternalFormat, GLuint oglTexId );
The texture can then be used like a native PRMan texture (e.g., by passing "textureName" as the RSL argument for a GPU shader plugin expecting a corresponding texture).

6.3 Multipass GPU Shading : Allocating Temporary GPU memory

The GPU is a separate processor from the CPU that has its own memory address space. This means that data must first be copied to and from the processor before it can be used. By default, the PRMan GPU system copies GPU shading results back to CPU-based memory after each call. This copy is fairly expensive, and it is unnecessary if the results will be used immediately in a subsequent GPU shader call. As such, we allow users to allocate GPU-based varying variables in their RSL shader. These variables can be written-to and read-from by GPU shaders without paying the cost of transferring data.

The GPU memory allocation system defines the five following RSL functions:

    float  gpuAllocFloat()
    vector gpuAllocVector()
    normal gpuAllocNormal()
    point  gpuAllocPoint()
    T      gpuRead( T output )    [T = float, color, vector, normal, point]
    void   gpuFree( T gpuMemPtr ) [T = float, color, vector, normal, point]
These functions are defined in a shader plugin (gpuStdLib.so) that must be declared by any shader that uses them (see below).

The gpuAlloc calls return a pointer to GPU memory. This pointer can then be passed to any GPU call in place of a varying input or output parameter. A GPU-based variable can be copied to a CPU-based RSL variable by calling gpuRead. Users must free the memory allocated to that pointer by passing it to gpuFree before the end of the RSL shader. The GPU pointer value only has meaning when passed to GPU shaders or memory management calls and should not be used in other RSL expressions.

We do support input arrays of GPU-allocated temporaries, but it is forbidden to mix GPU-based temporaries with CPU-based temporaries in the same array. The results of doing so are undefined.

GPU Memory Allocation Example
The following is a brief example of allocating, using, and freeing GPU varying variables. The code is valid RSL shader syntax.

    // Import GPU allocation plugin.
    plugin "gpuStdLib";
  
    // Allocate varying variables in GPU memory.
    color gpuTmp1 = gpuAllocColor();
    color gpuTmp2 = gpuAllocColor();
  
    // Compute intermediate results in GPU memory.
    myFirstGpuCall(Cs, gpuTmp1);
  
    // Second pass reads and writes GPU memory 
    mySecondGpuCall(gpuTmp1, gpuTmp2);
  
    // Copy GPU memory to CPU memory.
    // (Could have passed Ci as output instead.)
    Ci = gpuRead(gpuTmp2);      
  
    // Free GPU memory
    gpuFree(gpuTmp1);
    gpuFree(gpuTmp2);
When compiling shaders that use the built-in gpuStdLib.so plugin, be sure to specify the built-in shader plugin directory as an include path:
    shader -I$RMANTREE/lib/shaders/plugins myRslShader.sl
Also, make sure that the built-in shader plugin directory is included in the shader search path. This can be accomplished by adding $RMANTREE/lib/shaders/plugins to the /standardshaderpath in the rendermn.ini file (located in $RMANTREE/etc).

Hardware/Software Requirements

6 Gshader Documentation

The gshader compiler converts a Cg GPU shader into a PRMan plugin that can be called by RSL shaders. gshader uses the Cg compiler, C++ compiler, and a C/C++ linker as well as its own algorithms to create the PRMan plugin.

The simplest usage is:

    gshader myShader.cg
If the Cg shader includes the Cg header rmanGpuStdLib.h, specify the following include path:
    gshader -I$RMANTREE/include myShader.cg
To compile with a user-defined initialization function called myInitFunction that has been compiled to myInitFunction.o:
  gshader myCgShader.cg -init myInitFunction -ldArgs "myInitFunction.o"

For more advanced usage, the complete set of command line options for gshader are described below.

-h Print this message
-v Verbose output
-I Include path for Cg and C/C++ compilers
-cc Full bin path for C/C++ compiler (default 'cc')
-ld Full bin path for C/C++ linker (default 'ld')
-cgcArgs "args" Arguments for Cg compiler
-ccArgs "args" Arguments for C/C++ compiler
-ldArgs "args" Arguments for C/C++ linker
-init name Scoped name for user-defined init function
-nodso Do not build DSO. Just run Cg compiler.
-cgw Write wrapped Cg shader to .cgw file
-params Write parameter .gpp file

 

Pixar Animation Studios
(510) 752-3000 (voice)  (510) 752-3151 (fax)
Copyright © 1996- Pixar. All rights reserved.
RenderMan® is a registered trademark of Pixar.