====== User render configuration ====== The SpeedTree SDK can be configured to have N render passes, each one defined by a name string. In the reference application, we define Forward, Depth Only (shadow casting), and Deferred in ''MyRenderPassNames.h''. These names are passed in an array to each model object in the SDK via a call to ''pModel->SetRenderPasses().'' This is demonstrated in ''CMyPopulate::InitBaseTreeGraphics()'' in ''MyPopulate.cpp''. Right after this code, there is a function call to establish a render config callback for each model (the reference application uses the same callback for all models). The callback is invoked several times for each model, each time with a different configuration to allow the developers to set as many or as few state changes as they need. For example, it’s possible to use only two render configurations for the entire forest: one for 3D tree and grass models, another for the billboard models. Alternatively, it’s possible to establish a different render state/shader for each LOD and each geometry type within each LOD. The former will generate fewer draw calls/state changes and the latter more (but with potentially less general and shorter shaders). Listing 1 below shows our reference application’s render configuration callback, located in ''MyPopulate.cpp''. There is some specialization in this callback, but not a lot. There are separate shaders for grass, trees (each uses a different vertex definition), and billboards. There’s also a distinction for the depth-only pass to allow significantly shorter shaders to be used. The SDK intelligently sorts by these states to minimize state changes in the whole-forest draw loop. **Note:** In our example, the base names of the shaders are derived from the name of the vertex packer selected upon .stsdk export for the Modeler. **Listing 1.** Reference Application Example Callback void MyRenderStateCallback(const SDrawCallDetails* pDrawCallDetails, SClientPipeline* pClientPipeline) { assert(pDrawCallDetails); assert(pClientPipeline); // in our example, the shader filenames are based on the name of the vertex packer selected on // export from the Modeler app // get application pointer assert(g_pThisApp); // doesn't have to non-null in all uses, but it does for our example const SMyCmdLineOptions& sCmdLineOptions = g_pThisApp->GetCmdLineOptions( ); const CMyConfigFile& cConfigFile = g_pThisApp->GetConfig( ); // render pass flags based on the name of the render pass const st_bool c_bForwardLitPass = (strcmp(pDrawCallDetails->m_pPassName, c_pMyRenderPassForwardLit) == 0); const st_bool c_bDeferredPass = (strcmp(pDrawCallDetails->m_pPassName, c_pMyRenderPassDeferred) == 0); const st_bool c_bDepthOnlyPass = (strcmp(pDrawCallDetails->m_pPassName, c_pMyRenderPassDepthOnly) == 0); // shadows const st_bool c_bFogOnMesh = pDrawCallDetails->m_bFogOn3dMesh && c_bForwardLitPass; // render state pClientPipeline->m_sRenderState.m_eFaceCulling = pDrawCallDetails->m_bBillboard ? CULL_FACE_BACK : CULL_FACE_NONE; pClientPipeline->m_sRenderState.m_bAlphaToCoverage = !c_bDepthOnlyPass && (sCmdLineOptions.m_nNumSamples > 1); pClientPipeline->m_sRenderState.m_bMultisampling = !c_bDepthOnlyPass && (sCmdLineOptions.m_nNumSamples > 1); pClientPipeline->m_sRenderState.m_nNumMsaaSamples = c_bDepthOnlyPass ? 1 : sCmdLineOptions.m_nNumSamples; pClientPipeline->m_sRenderState.m_bPolygonOffset = c_bDepthOnlyPass; // figure out the shader path base on the render type const st_char* c_pRenderType = "/forward/"; if (c_bDeferredPass) c_pRenderType = "/deferred/"; else if (c_bDepthOnlyPass) c_pRenderType = "/depth/"; pClientPipeline->m_strShaderPath = cConfigFile.m_sWorld.m_strShaderPath + c_pRenderType + CPipeline::GetCompiledShaderFolder( ); CFixedString& strVertexShader = pClientPipeline->m_strVertexShaderFilename; CFixedString& strPixelShader = pClientPipeline->m_strPixelShaderFilename; // get all lowercase vertex packer names CFixedString strVertexPacker = pDrawCallDetails->m_bBillboard ? pDrawCallDetails->m_pTree->BillboardVertexPackingName( ).Data( ) : pDrawCallDetails->m_pTree->VertexPackingName( ).Data( ); for (size_t i = 0; i < strVertexPacker.size( ); ++i) strVertexPacker[i] = static_cast(tolower(strVertexPacker[i])); // when shader names are assigned, the "_vs" and "_ps" prefixes are left off since they'll be added // by the shader loading system strVertexShader = strPixelShader = CFixedString::Format("%s%s%s", strVertexPacker.c_str( ), c_bDepthOnlyPass ? "_depth" : "", c_bFogOnMesh ? "_fogged" : ""); if (c_bDepthOnlyPass) pClientPipeline->m_sRenderState.m_aeRenderTargetTypes[0] = RENDER_TARGET_TYPE_DEPTH; else { pClientPipeline->m_sRenderState.m_aeRenderTargetTypes[0] = RENDER_TARGET_TYPE_COLOR; if (sCmdLineOptions.m_bDeferred) pClientPipeline->m_sRenderState.m_aeRenderTargetTypes[1] = RENDER_TARGET_TYPE_COLOR; pClientPipeline->m_sRenderState.m_nNumRenderTargets = sCmdLineOptions.m_bDeferred ? 2 : 1; } } The callback is given an ''SDrawCallDetails'' object to base its decisions on, which is in Listing 2. **Listing 2.** SDrawCallDetails Declaration struct SDrawCallDetails { st_int32 m_nPassIndex; const st_char* m_pPassName; const st_char* m_pStsdkFilename; SMaterial m_sMaterial; st_int32 m_nLodIndex; CLodInfo m_cLod; st_bool m_bBillboard; st_bool m_bGrassModel; st_bool m_bFogOn3dMesh; st_int32 m_nDrawCallIndex; SDrawCall m_sDrawCall; const CCore* m_pTree; }; The output of the callback function is an ''SClientPipeline'' object (Listing 3) which simply contains shader filenames, a shader path, and a render state object. **Listing 3.** SClientPipeline Declaration struct SClientPipeline { CFixedString m_strVertexShaderFilename; CFixedString m_strPixelShaderFilename; CFixedString m_strShaderPath; SRenderState m_sRenderState; };