Shaders were first added to Microsoft DirectX in DirectX 8.0. At that time, several virtual shader machines were defined, each roughly corresponding to a particular graphics processor produced by the top 3D graphics vendors. For each of these virtual shader machines, an assembly language was designed. Programs written to the shader models (names vs_1_1 and ps_1_1 - ps_1_4) were relatively short and were generally written by developers directly in the appropriate assembly language. The application would pass this human-readable assembly language code to the D3DX library via D3DXAssembleShader and get back a binary representation of the shader which would in turn get passed via IDirect3DDevice9::CreateVertexShader or IDirect3DDevice9::CreatePixelShader. For more detail, see the software development kit (SDK).
The situation in Direct3D 9 is similar. An application passes an high-level shader language (HLSL) shader to D3DX via D3DXCompileShader and gets back a binary representation of the compiled shader which in turn is passed to Microsoft Direct3D via IDirect3DDevice9::CreatePixelShader or IDirect3DDevice9::CreateVertexShader. The runtime does not know anything about HLSL, only the binary assembly shader models. This is nice because it means that the HLSL compiler can be updated independent of the Direct3D runtime.
In addition to the development of the HLSL compiler, Direct3D 9 also introduced the assembly-level shader models to expose the functionality of the latest generation of graphics hardware. Application developers can work in assembly for these new models (vs_2_0, vs_3_0, ps_2_0, ps_3_0) but we expect most developers to move to HLSL for shader development.
Of course, the ability to write an HLSL program to express a particular shading algorithm does not automatically enable it to run on any given hardware. An application calls D3DX to compile a shader into binary assembly code with D3DXCompileShader. One of the limitations with this entry point is a parameter that defines which of the assembly-level models (or compilation targets) the HLSL compiler should use to express the final shader code. If an application is doing HLSL shader compilation at run time (as opposed to compile time or offline), the application could examine the capabilities of the Direct3D device and select the compilation target to match. If the algorithm expressed in the HLSL shader is too complex to execute on the selected compilation target, compilation will fail. This means that while HLSL is a huge benefit to shader development, it does not free developers from the realities of shipping games to a target audience with graphics devices of varying capabilities. As a game developer, you still have to manage a tiered approach to your visuals; this means writing better shaders for more capable graphics cards and writing more basic versions for older cards. With well written HLSL, however, this burden can be eased significantly.
Rather than compile HLSL shaders using D3DX on the customer's machine at application load time or on first use, many developers choose to compile their shader from HLSL to binary assembly code before they even ship. This keeps their HLSL source code away from prying eyes and also ensures that all the shaders their application will ever run have gone through their internal quality assurance process. A convenient utility that allows developers to compile shaders offline is the Shader Compilers. This utility has a number of options that you can use to compile code for the specified compile target. Studying the disassembled output can be very educational during development if you want to optimize your shaders or just generally get to know the virtual shader machine's capabilities at a more detailed level. These options are summarized below:
Shader constants are contained in the constant table. This can be accessed with the ID3DXConstantTable interface. Global shader variables can be initialized in shader code. These are initialized at run time by calling ID3DXConstantTable::SetDefaults.
The compiler will automatically assign registers to global variables. The compiler would assign Environment to sampler register s0, SparkleNoise to sampler register s1, and k_s to constant register c0 (assuming no other sampler or constant registers were already assigned) for the following three global variables:
sampler Environment; sampler SparkleNoise; float4 k_s;
It is also possible to bind variables to a specific register. To force the compiler to assign to a particular register, use the following syntax:
register(RegisterName)
where RegisterName is the name of the specific register. The following examples demonstrate the specific register assignment syntax, where the sampler Environment will be bound to sampler register s1, SparkleNoise will be bound to sampler register s0, and k_s will be bound to constant register c12:
sampler Environment : register(s1); sampler SparkleNoise : register(s0); float4 k_s : register(c12);
A shader is rendered by setting the current shader in the device, initializing the shader constants, telling the device where the varying input data is coming from, and finally rendering the primitives. Each of these can be accomplished by calling the following methods respectively:
What is it, how is it created, how is it compiled and rendered. D3DXFillVolumeTextureTX. HLSL workshop, Goal 4 uses a procedural texture.
Do you want to debug your vertex and pixel shader code by stepping through the per-vertex code or the per-pixel code and watching instructions as they are executed, looking at the values in shader constants and variables, and setting breakpoints in trouble spots? The DirectX extension for Microsoft Visual Studio .NET is fully integrated into the Visual Studio .NET Integrated Development Environment (IDE).
In order to prepare for shader debugging, you must install the right tools on your machine. These are listed below. If you already have the right tools installed, you can also try the Goal 5 - Shader Debugging Tutorial.
Goal 5 - Shader Debugging Tutorial
Take this from the Optimization topics in Jason and Craig's paper.
Fragments can be compiled, linked, and rendered with or without effects. Since it is easier to take advantage of an effect, this case will be shown first.
Given an effect that contains an HLSL or an assembly fragment, (see Writing Shader Fragments), compile the fragment by calling D3DXGatherFragmentsFromFile as shown here:
DWORD g_dwShaderFlags; // Shader compilate and link flags LPD3DXBUFFER g_pCompiledFragments = NULL; D3DXGatherFragmentsFromFile( L"FragmentLinker.fx", NULL, NULL, g_dwShaderFlags, &g_pCompiledFragments, NULL );
D3DXGatherFragmentsFromFile requires the .fx file, pointers to the #define and #include handlers (both set to NULL in this example), and the shader compile flags. The method returns a buffer which contains the compiled shader fragment. The method can return a second buffer with compile errors, which is set to NULL in this example because it is not used. D3DXGatherFragments is overloaded to handle loading fragments from a string, a file, or a resource.
Set your debugger to break on this method to look for compile errors in the debugger. The compiler can catch errors in syntax, but it cannot check for registers that are shared incorrecly due to the fact that it has no way to predict which parameters a user may want to share between fragments.
You need a fragment linker to manage the compiled fragments. Create the fragment linker by calling D3DXCreateFragmentLinker:
ID3DXFragmentLinker* g_pFragmentLinker = NULL; // Fragment linker interface IDirect3DDevice9* pd3dDevice = NULL; // Initialize the device before using it ... // Create the fragment linker interface D3DXCreateFragmentLinker( pd3dDevice, 0, &g_pFragmentLinker );
Then simply add the compiled fragments to the fragment linker using ID3DXFragmentLinker::AddFragments.
// Add the compiled fragments to a list g_pFragmentLinker->AddFragments( (DWORD*)g_pCompiledFragments->GetBufferPointer() );
ID3DXFragmentLinker::AddFragments requires a pointer to the DWORD stream that contains the compiled shader.
After compiling fragments and creating a fragment linker, there are several ways to link fragments. One way to link a vertex shader fragment is to call ID3DXFragmentLinker::LinkVertexShader. Here is an example that links two vertex shader fragments:
// Get a handle to each fragment D3DXHANDLE fragmentHandle[2]; fragmentHandle[0] = (D3DXHANDLE)g_pFragmentLinker->GetFragmentHandleByName("Ambient"); fragmentHandle[1] = (D3DXHANDLE)g_pFragmentLinker->GetFragmentHandleByName("AmbientDiffuseFragment"); // Link the fragments together to form a vertex shader IDirect3DVertexShader9* pVertexShader = NULL; g_pFragmentLinker->LinkVertexShader( "vs_1_1", g_dwShaderFlags, fragmentHandle, 2, &pVertexShader, NULL );
This requires a shader compile target, the shader compile and link flags, and a handle to each of the fragments to link. If the fragments are successfully linked, ID3DXFragmentLinker::LinkVertexShader returns a vertex shader (IDirect3DVertexShader9). The vertex shader needs to be set in the effect before rendering. But before this, here's how the shader is declared in the effect:
VertexShader MyVertexShader; // Vertex shader set by the application
The effect technique contains all the state set for a pass. This pass specifies the vertex shader like this:
technique RenderScene { pass P0 { VertexShader = <MyVertexShader>; PixelShader = compile ps_1_1 ModulateTexture(); } }
With the effect's vertex shader created and initialized, the render code also sets the uniform constants and calls the render loop. Set the uniform constants similar to this:
// Update the uniform shader constants. g_pEffect->SetMatrix( "g_mWorldViewProjection", &mWorldViewProjection ); g_pEffect->SetMatrix( "g_mWorld", &mWorld ); g_pEffect->SetFloat( "g_fTime", (float)fTime );
Then render the effect by setting the current technique and pass:
// Render the scene if( SUCCEEDED( pd3dDevice->BeginScene() ) ) { // Apply the technique contained in the effect UINT cPasses, iPass; g_pEffect->Begin(&cPasses, 0); for (iPass = 0; iPass < cPasses; iPass++) { g_pEffect->BeginPass(iPass); // Render the mesh with the applied technique g_pMesh->DrawSubset(0); g_pEffect->EndPass(); } g_pEffect->End(); pd3dDevice->EndScene(); }
When setting uniform shader constants, it is more efficient to cache a handle to the parameter by calling ID3DXBaseEffect::GetParameterByName. This avoids the string lookup that is necessary when calling effect methods like ID3DXBaseEffect::SetMatrix.
// Instead of setting a uniform constant like this in the render loop g_pEffect->SetMatrix( "g_mWorldViewProjection", &mWorldViewProjection ); // Get a handle to a uniform constant outside of the render loop D3DXHANDLE hParameter; GetParameterByName( hParameter,"g_mWorldViewProjection"); ... // Use the handle to set the uniform constant in the render loop g_pEffect->SetMatrix(hParameter);
Building a fragment that does not live in an effect is very similar to building a fragment that is in an effect. You still use D3DXGatherFragments to compile one or more fragments. If the fragments are HLSL, they will be compiled. If the fragments are assembly fragments, they will be assembled. You still create a fragment linker(created by D3DXCreateFragmentLinker), add all the fragments to the linker (with ID3DXFragmentLinker::AddFragments), and link the fragments together to create a vertex shader with ID3DXFragmentLinker::LinkVertexShader.
Rendering a fragment without an effect is slightly different, because you cannot use the effect to set the uniform shader constants. Because every vertex or pixel shader contains a constant table (ID3DXConstantTable), you will need to get the constant table and use it to initialize the uniform shader constants.
LPD3DXCONSTANTTABLE pConstantTable; LPD3DXBUFFER pShaderBuf; IDirect3DVertexShader9* pVertexShader = NULL; // Compile the fragments to a buffer. D3DXGatherFragmentsFromFile( L"FragmentLinker.fx", NULL, NULL, g_dwShaderFlags, &g_pCompiledFragments, NULL ); g_pFragmentLinker->AddFragments((DWORD*)g_pCompiledFragments->GetBufferPointer()); g_pFragmentLinker->LinkShader( "vs_1_1", g_dwShaderFlags, aHandles, NUM_FRAGMENTS, &pShaderBuf, NULL); D3DXGetShaderConstantTable( (DWORD*)pShaderBuf->GetBufferPointer(), &pConstantTable ); pDevice->CreateVertexShader( (DWORD*)pShaderBuf->GetBufferPointer(), &pVertexShader); RELEASE(pShaderBuf);
The fragment linking operation efficiently links together HLSL fragments and assembly fragments, and generates a shader object from one or more fragments. The constant table generated from a fragment is identical to the constant table generated from a shader. This is because each binary shader contains the constant table and the shader input declarations.