The CPU Programming Pipeline

The architecture of a CPU material exactly matches the architecture of GPU materials: CPU shaders are added to rendering passes exactly as GPU shaders are. In fact, there’s no difference between CPU and GPU shaders: both are based on the RED::RenderShader and RED::StateShader classes. The only differences are in the definition of the shader target (software shader are using RED::HW_SOFT_TRACER) and of programs, that are made of C++ compiled code.

RED::RenderShader in software follows the same binding mechanics as GPU shaders for the RED::RenderCode part, to bridge geometrical data directly to the shader. However, unlike GPU shaders, a CPU shader has only one program, that is declared as both the vertex and pixel shader from the HOOPS Luminate RED::RenderShader API.

The Software Program Callback

The software shader program is only defined by a single callback following the RED::SOFT_SHADER_CALLBACK prototype:

#include "REDSoftShaderCallback.h"

RED_RC SoftwareShader( RED::SoftFrameBufferSample&       fbsample,
                      const RED::ISoftRayContext&       rayctx,
                      const RED::ISoftShaderContext&    shaderctx,
                      const RED::ISoftRenderingContext& renderctx,
                      const RED::Version&               version )
{
  fbsample._out_color[0] = 1.0;
  fbsample._out_color[1] = 1.0;
  fbsample._out_color[2] = 1.0;
  fbsample._out_color[3] = 1.0;

  return RED_OK;
}

The method must return ‘fbsample._out_color’. The RED::SoftFrameBufferSample is the class that describes all the properties of the frame buffer for the considered pixel sample at the time of the shading call.

Several specific API objects are in charge of providing all the necessary informations to the software shader:

  • RED::ISoftRayContext: this interface gives an access to all current geometrical informations of the shaded point in the ray-tree: the hit primitive, the current depth in the ray-tree, our thread number, etc. It also provides texture sampling helpers.

  • RED::ISoftShaderContext: this is the shader parameters. You’ll find the RED::RenderCode and all local parameters that were declared during the shader setup.

  • RED::ISoftRenderingContext: this interface returns all rendering options for the current frame.

The RED::Version used in the method prototype is here to help versioning shaders whenever shader inputs evolve over releases.

From the RED::ISoftRayContext, all informations relative to our position in the ray-tree are stored and can be leveraged to figure out what to do. Most common uses for the current ray context are:

  • Access the current position in space: RED::ISoftRayContext::GetWCSHit.

  • Access the interpolated normal at the hit point: RED::ISoftRayContext::GetWCSNormal.

  • Sample a texture: RED::ISoftRayContext::SampleTexture.

Note

The software shading method matches the hardware shading methods: we still have input geometry channels that are connected to the shading using a RED::RenderCode instance. The main difference between hardware and software shading is about the fact that we have one single shader for the vertex shader and pixel shader stages in the hardware.

By default, the engine launches a number of rendering threads equal to RED::OPTIONS_RAY_MAX_THREADS. All software shading methods are called in this multi-threaded context.

Defining Software Shader DLL Entry Points

A software shader method prototype must be exported by a DLL to be visible by HOOPS Luminate, and it must be indicated as so in the shader program itself:

RED::RenderShader shader;
RED::ShaderProgramID sshid;
RED::ShaderString ssh;
RED::Version version;

// Define the software shader callback DLL entry point.
RC_TEST( ssh.SoftShaderStart( "MyLibrary.dll", "SoftwareShader", version ) );
ssh.ShaderEnd();

// Load the software shader.
RC_TEST( iresmgr->LoadShaderFromString( sshid, ssh ) );

// Create the render code to bind vertex position data.
RED::RenderCode rcode;
rcode.BindChannel( RED_VSH_VERTEX, RED::MCL_VERTEX );

// Setup the shader.
RC_TEST( shader.SetRenderCode( rcode, RED_L0, RED::HW_SOFT_TRACER ) );
RC_TEST( shader.SetVertexProgramId( sshid, RED_L0, resmgr, RED::HW_SOFT_TRACER ) );
RC_TEST( shader.SetPixelProgramId( sshid, RED_L0, resmgr, RED::HW_SOFT_TRACER ) );

Then, the ‘SoftwareShader’ method pointed to by the shader program must be a valid DLL entry point in ‘MyLibrary.dll’.

Please note that the name of the library must be libMyLibrary.so on Linux and libMyLibrary.dylib on MacOS, and it must still be declared as being “MyLibrary.dll” in the RED::ShaderString::SoftShaderStart method.

As an alternative, software shading methods can be defined in the executable program itself:

Defining Software Shader Local Entry Points

To inform HOOPS Luminate that a custom software shading callback is located in the program executable file itself, just do:

RED::RenderShader shader;
RED::ShaderProgramID sshid;
RED::ShaderString ssh;
RED::Version version;

// Define the software shader callback entry point as local.
RC_TEST( ssh.SoftShaderStart( "", "SoftwareShader", version ) );
ssh.ShaderEnd();

// Load the software shader.
RC_TEST( iresmgr->LoadShaderFromString( sshid, ssh ) );

// Create the render code to bind vertex position data.
RED::RenderCode rcode;
rcode.BindChannel( RED_VSH_VERTEX, RED::MCL_VERTEX );

// Setup the shader.
RC_TEST( shader.SetRenderCode( rcode, RED_L0, RED::HW_SOFT_TRACER ) );
RC_TEST( shader.SetVertexProgramId( sshid, RED_L0, resmgr, RED::HW_SOFT_TRACER ) );
RC_TEST( shader.SetPixelProgramId( sshid, RED_L0, resmgr, RED::HW_SOFT_TRACER ) );

// Register the shading callback to the resource manager.
RC_TEST( iresmgr->RegisterShadingCallback( "SoftwareShader", SoftwareShader, NULL ) );

The ‘SoftwareShader’ method must be defined elsewhere in the same executable file otherwise the engine will return with an error code during the rendering of the shader.

Note

You can also go the way described in Defining Software Shader DLL Entry Points and set the shader file to be the program executable itself instead of a DLL.

The Software Shading Loop

As explained in the ‘The Shading Pipeline’ page, in GPU mode the shader programs contained in the RED::MTL_LIT pass are executed one time for each light in the scene. The pass is cumulative. Hence, GPU shaders must be written as one step of the loop.

Unlike the GPU mode, the software shader callbacks defined in the RED::MTL_LIT pass are called only once. The pass is additive. The cumulative loop through the lights must be handled by the user in the shader callback.

The lights can be accessed via the RED::ISoftRenderingContext::GetLightsCount and RED::ISoftRenderingContext::GetLight methods. A call to RED::ISoftRayContext::PrepareLights is mandatory before accessing the lights in order to setup them before the shading.

It is up to the writer to choose it’s light sampling method. To facilitate the sampling, HOOPS Luminate provides useful built-in functions to do it: RED::ISoftLight::NeedSampling, RED::ISoftLight::StartSampling and RED::ISoftLight::GetNextWCSSample. The methods are already taking shadows and light source attenuation into consideration.

Light source culling must be performed manually: the engine does not make any assumption. The RED::ISoftLight::GetAttenuation method allows to do it.

Iterating Over Lights in a Software Shader

Contrary to the GPU version, a software shader contained in the RED::MTL_LIT pass have to loop through the lights in order to cumulate their effect.

Here is a simple code to iterate over the lights and perform sampling when needed:

// let:
// - rayctx be the RED::ISoftRayContext.
// - shaderctx be the RED::ISoftShaderContext.
// - renderctx be the RED::ISoftRenderingContext.
double hit[4], normal[4];
double latt[4];
double ldir[4], lcolor[4], lshadows[4];
bool moresamples;

// Get the ray hit and normal.
rayctx.GetWCSHit( hit );
rayctx.GetWCSNormal( normal, shaderctx.GetRenderCode(), RED_VSH_NORMAL );

// Prepare lights before shading.
// This call is mandatory before accessing the lights.
RC_TEST( rayctx.PrepareLights( hit, normal, false, renderctx ) );

// Iterate over the scene lights.
for( unsigned int i = 0; i < renderctx.GetLightsCount(); ++i )
{
   const RED::ISoftLight& light = renderctx.GetLight( i );

    // Perform culling.
    if( !light.GetAttenuation( latt, rayctx.GetThreadID() ) )
        continue;

    // Sampled light.
    if( light.NeedSampling() )
    {
        moresamples = true;
        while( true )
        {
            RC_TEST( light.GetNextWCSSample( moresamples,
                                             ldir, lcolor, lshadows,
                                             hit, normal,
                                             rayctx, renderctx ) );
            if( !moresamples )
              break;

            // Do something with the light sample...
        }
    }
    // Not sampled light.
    else
    {
        // Do something with the light...
    }
}