FragmentLinker Sample

This FragmentLinker sample shows how to use the ID3DXFragmentLinker interface. Shader source code can be split into a series of shader fragments, that are compiled separately and linked together to form a complete shader. This linking stage is very efficient, making it suitable for run-time use. In this way a Direct3D application can custom-build an appropriate shader for the current graphics card.

Path

Source: (SDK root)\Samples\C++\Direct3D\FragmentLinker
Executable: (SDK root)\Samples\C++\Direct3D\Bin\x86 or x64\FragmentLinker.exe

How the Sample Works

Large-scale Direct3D applications commonly employ a large set of redundant shaders covering every supported fallback case for a given technique (often generated by uniform parameters) from which the appropriate shader for the current graphics hardware is selected at run time. This approach can result in a large amount of compiled shaders being included with the application, only a small fraction of which are ever used on a given machine. Using shader fragments, the desired shader for the current graphics card can be built at run time.

This sample links together one of two vertex shader fragments that handle projection (vertex animated or static) and one of two vertex shader fragments that handle vertex lighting (diffuse enabled or ambient only). These fragments are compiled once during initialization and are then linked together at run time.

There are only a few steps that need to be followed when using shader fragments in your application:

  1. Create the ID3DXFragmentLinker interface using the D3DXCreateFragmentLinker function.
  2. Load and compile the shader fragments by calling D3DXGatherFragments, D3DXGatherFragmentsFromFile, or D3DXGatherFragmentsFromResource.
  3. Add the compiled fragments to the linker's internal list with a call to ID3DXFragmentLinker::AddFragments. This list defines the complete set of all fragments, from which a subset will be linked to create a shader.
  4. Retrieve handles of the fragments you want linked through calls to ID3DXFragmentLinker::GetFragmentHandleByIndex or ID3DXFragmentLinker::GetFragmentHandleByName.
  5. Link together a subset of the added fragments by passing fragment handles to the ID3DXFragmentLinker::LinkVertexShader, ID3DXFragmentLinker::LinkPixelShader, or ID3DXFragmentLinker::LinkShader method. These methods create an interface to a compiled shader that can be used for rendering.

The sample application performs the above steps upon device creation within the OnCreateDevice function:

// Create the fragment linker interface
V_RETURN( D3DXCreateFragmentLinker( pd3dDevice, 0, &g_pFragmentLinker ) );

// Compile the fragments to a buffer. The fragments must be linked 
// together to form a shader before they can be used for rendering.
V_RETURN( DXUTFindDXSDKMediaFileCch( 
    strPath, MAX_PATH, L"FragmentLinker.fx" ) );
V_RETURN( D3DXGatherFragmentsFromFile( 
    strPath, NULL, NULL, 0, &g_pCompiledFragments, NULL ) );
 
// Build the list of compiled fragments
V_RETURN( g_pFragmentLinker->AddFragments( 
    (DWORD*)g_pCompiledFragments->GetBufferPointer() ) );

// Store the fragment handles
CDXUTComboBox* pComboBox = NULL;
pComboBox = g_SampleUI.GetComboBox( IDC_LIGHTING );
pComboBox->RemoveAllItems();
pComboBox->AddItem( L"Ambient", (void*) g_pFragmentLinker
                 ->GetFragmentHandleByName( "AmbientFragment" ) );
pComboBox->AddItem( L"Ambient & Diffuse", (void*) g_pFragmentLinker
                 ->GetFragmentHandleByName( "AmbientDiffuseFragment" ) );

pComboBox = g_SampleUI.GetComboBox( IDC_ANIMATION );
pComboBox->RemoveAllItems();
pComboBox->AddItem( L"On", (void*) g_pFragmentLinker
                 ->GetFragmentHandleByName( "ProjectionFragment_Animated" ) );
pComboBox->AddItem( L"Off", (void*) g_pFragmentLinker
                 ->GetFragmentHandleByName( "ProjectionFragment_Static" ) );

// Link the desired fragments to create the vertex shader
V_RETURN( LinkVertexShader() );

The final linking step is performed within the LinkVertexShader helper function. In addition to being called upon device creation, this function is also called whenever the user changes a fragment combobox selection. The currently selected fragments are retrieved from the user interface controls, the shader is quickly linked, and the new shader is passed to the effect file:

HRESULT LinkVertexShader()
{
    HRESULT hr;
    ID3DXBuffer *pCode;
    DWORD *pShaderCode;
    IDirect3DDevice9 *pd3dDevice = DXUTGetD3DDevice();
    
    const int NUM_FRAGMENTS = 2;
    D3DXHANDLE aHandles[ NUM_FRAGMENTS ] = {0};
    aHandles[ 0 ] = 
        (D3DXHANDLE) g_SampleUI.GetComboBox(IDC_ANIMATION)->GetSelectedData();
    aHandles[ 1 ] = 
        (D3DXHANDLE) g_SampleUI.GetComboBox(IDC_LIGHTING)->GetSelectedData();

    SAFE_RELEASE( g_pLinkedShader );

    // Link the fragments together to form a shader
    V_RETURN( g_pFragmentLinker->LinkShader( 
	    "vs_1_1", 0, aHandles, NUM_FRAGMENTS, &pCode, NULL ) );
    pShaderCode = (DWORD*) pCode->GetBufferPointer();
    V_RETURN( pd3dDevice->CreateVertexShader( pShaderCode, &g_pLinkedShader));
    V_RETURN( D3DXGetShaderConstantTable( pShaderCode, &g_pConstTable ) );

    SAFE_RELEASE( pCode );

    // Set global variables
    V_RETURN( pd3dDevice->SetVertexShader( g_pLinkedShader ) );

    if (g_pConstTable)
    {
        g_pConstTable->SetVector( 
            pd3dDevice, "g_vMaterialAmbient", &g_vMaterialAmbient );
        g_pConstTable->SetVector( 
            pd3dDevice, "g_vMaterialDiffuse", &g_vMaterialDiffuse );
        g_pConstTable->SetVector( 
            pd3dDevice, "g_vLightColor",      &g_vLightColor );
        g_pConstTable->SetVector( 
            pd3dDevice, "g_vLightPosition",   &g_vLightPosition );
    }

    return S_OK;
}

Resources

The syntax for writing shader fragments is detailed in Writing Shader Fragments and in the Fragment Declaration Syntax.

More in-depth coverage of the process behind compiling and linking shader fragments is found in Building And Rendering Fragments.