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.
Source: | (SDK root)\Samples\C++\Direct3D\FragmentLinker |
Executable: | (SDK root)\Samples\C++\Direct3D\Bin\x86 or x64\FragmentLinker.exe |
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:
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; }
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.