D3DBook:The Effect Framework

From GDWiki
Jump to: navigation, search

Contents

Effect framework

Starting with Direct3D 10 the effect framework is not longer part of the Direct3D extensions. Like the HLSL compiler it is now part of the core API itself.

FX Files

The effect framework use human readable text files to define an effect. These FX files are an extended version of HLSL shader files. Like these they contain a variable and a function block followed by an additional block that described all the techniques.

GeometryShader gsStreamOut = ConstructGSWithSO( CompileShader( gs_4_0, GSMain() ), "POSITION.xyz; NORMAL.xyz;" );
 
technique10 StreamOut
{
    pass p0
    {
        SetVertexShader( CompileShader( vs_4_0, VSMain1() ) );
        SetGeometryShader( gsStreamOut );
        SetPixelShader( NULL );
 
        SetDepthStencilState( DisableDepth, 0 );
    } 
}
 
 
technique10 UseStream
{
    pass p0
    {
        SetVertexShader( CompileShader( vs_4_0, VSMain2() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PSMain1() ) );
 
        SetRasterizerState( EnableCulling );
        SetDepthStencilState( EnableDepthTestWrite, 0 );
    } 
 
    pass p1
    {
        SetVertexShader( CompileShader( vs_4_0, VSMain2() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PSMain2() ) );
 
        SetRasterizerState( EnableCulling );
        SetDepthStencilState( EnableDepthTestWrite, 0 );
        SetBlendState( AddBlending, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
    } 
}

As you can see every technique can contain one or multiple passes. The passes itself then define which one of the shader functions and state objects should be used. You can assign an optional name to every techniques and passes. Beside of this you can add as much annotations as you want. Direct3D will parse this additional information’s and give you access to them but it will not interpret them. They are served the function of an additional information channel between content creation tools and a Direct3D application.

Even if the Direct3D 10 FX files look similar to their Direct3D 9 counterparts there is no direct support for using a single FX file for both APIs. The only way to get this working is by using the preprocessor to hide all the Direct3D 10 parts from the Direct3D 9 compiler.

Compile Effects

Like with shaders it is possible to compile the human readable content of a FX file to a binary representation. To do this the D3D10CompileEffectFromMemory function need to be called. If the effect is error free the function generates a blob with the binary data for future use. In the case that the effect parser found any errors or warnings it generates a second blob with a human readable list of these problems.

As shaders could be disassembled this is possible with effects to. A call to D3D10DisassembleEffect will give you the assembler code for every shader that is used in the effect. But you would not be able to see the used state object there.

Create Effects

Before an effect could be used it needs to be created with D3D10CreateEffectFromMemory from its ASCII or binary form. If successful an ID3D10Effect interface is returned. Be aware that this main entry point is a full COM interface with reference counting but its sub objects are simple custom interfaces without. Because of this you have to make sure by your own code that none of this sub objects interfaces are used after the effect object is released. As long as the effect object is alive it will always return valid pointer to sub objects even if you request an object that doesn’t exist. In such cases the object will be not valid. To check for this every Interface in the effect framework supports an IsValid method.

A second source of confusion is the memory optimization. To allow a rich reflection model every effect object contains a huge bunch of additional data. To save some memory it could be removed with a call to the Optimize method. But a bunch of reflection methods will not work anymore after this call. Interfaces that are requested before will be still valid until the effect is released. Therefore you should first do all your reflection work after you have created the effect and before you call Optimize.

Techniques

Each technique sub object represents a technique section from the FX file. To get access to such a sub object you have to call the GetTechniqueByIndex or GetTechniqueByName method from the effect object. Each technique is primary a container for the passes that are defined in the FX file. As it is optional to give an technique a name GetTechniqueByName may not be able to return all techniques that are part of an effect.

Passes

A pass object could be queried from the technique with the GetPassByIndex or GetPassByName method. Every pass knows from the FX file which shader, state objects and resources are needed to execute. To make sure that they are transferred to the connected Direct3D device you need to call Apply before making any draw calls. In the case the FX file doesn’t assign a shader or state object to a pipeline stage the effect framework will not change it. It’s up to you to make sure that it is in the right state. The same is true for the input assembler stage that will never touched by the effect framework. This made it necessary to create an input layout that is compatible with the vertex shader that is used by a pass. To do this you will need the input signature of the shader which is contained in the D3D10_PASS_DESC structure. As alternative you can use GetVertexShaderDesc to get access to the full vertex shader data instead of only the input signature.

pPass->GetDesc (&PassDesc);
pd3dDevice->CreateInputLayout
    (InputElements,
    sizeof(InputElements)/sizeof(D3D10_INPUT_ELEMENT_DESC),
    PassDesc.pIAInputSignature, PassDesc.IAInputSignatureSize,
    &pInputLayout);

Variables

Beside the techniques collection an effect contains the set of variables that are defined in the FX file. To get an interface to one of them the effect interface supports the two methods GetVariableByName and GetVariableByIndex. Both will return a pointer to a ID3D10EffectVariable interface. This base interface does not support any data type specific access methods but a number of methods that return an interface to a typed form.


Data type As method Interface
Blend state AsBlend ID3D10EffectBlendVariable
Constant/Texture buffer AsConstantBuffer ID3D10EffectConstantBuffer
Depth stencil state AsDepthStencil ID3D10EffectDepthStencilVariable
Matrix AsMatrix ID3D10EffectMatrixVariable
Rasterizer state AsRasterizer ID3D10EffectRasterizerVariable
Sampler state AsSampler ID3D10EffectSamplerVariable
Scalar AsScalar ID3D10EffectScalarVariable
Shader AsShader ID3D10EffectShaderVariable
Shader resource AsShaderResource ID3D10EffectShaderResourceVariable
String AsString ID3D10EffectStringVariable
Vector AsVector ID3D10EffectVectorVariable

Table: specific variable interfaces

This is easy if you know the data type of the variable. In the case you write an application that need to work with custom FX files Direct3D 10 provides some help. The GetType method returns a pointer to an ID3D10EffectType interface. With this interface you can get all information that is necessary to use the FX file parameter in a dynamic way.

Constant and Texture Buffers

Based on the FX file the Direct3D 10 effect framework generates one or more constant or texture buffers to transfer the variable values to the GPU. To get access to these buffers you can use the GetConstantBufferByName or GetConstantBufferByValue. Another way is to use the GetParentConstantBuffer method from the variable interface. This will get you the buffer that is used from this variable. The BufferOffset member of the variable description structure will you tell you the position inside this buffer were the variable is stored. With this information you will be able to update the buffer direct without using the effect framework variables. But you need to keep in mind that the effect framework still own the constant and texture buffers and can update its content every time. Therefore you should use this possibility only very careful.

Annotation

To store additional information each effect, technique, pass and variable can have attached annotation. As already noticed the number is stored as part of the description. To get the stored values each of these object interfaces contains the GetAnnotationsByName and GetAnnotationByIndex methods. The result is a pointer to a ID3D10EffectVariable interface. Like with the other variable it is possible to convert this interface to a typed variant with one of the AsXXX methods.

D3D10_PASS_DESC PassDesc;
 
pPass->GetDesc (&PassDesc);
LPCSTR pAnnotationString;
 
for<nowiki> (UINT i = 0 ; i < PassDesc.Annotations ; ++i)</nowiki>
{
    ID3D10EffectVariable* pAnnotation = pPass->GetAnnotationByIndex (i);
 
    ID3D10EffectStringVariable* pStringAnnotation = pAnnotation->AsString ();
 
    if (pStringAnnotation->IsValid())
    {
        pStringAnnotation->GetString (&pAnnotationString);
        // use the annotation content
    }
}

State blocks

Every time you call Apply on an effect pass interface the framework will modify the current state of the Direct3D 10 device. To save and restore the state before such a change Direct3D provides state block objects. These objects can be configured as part of their creation with a fine granularity. To do this you need define every element of the pipeline that should be stored in a state block mask. You can manipulate this mask with a set of functions or let the effect framework calculate the right one for you.


Function Purpose
D3D10StateBlockMaskDifference Gets the difference of two masks.
D3D10StateBlockMaskDisableAll Clear the mask.
D3D10StateBlockMaskDisableCapture Disable selected parts of the mask
D3D10StateBlockMaskEnableAll Enable all parts of the mask
D3D10StateBlockMaskEnableCapture Enable selected parts of the mask
D3D10StateBlockMaskGetSetting Get the state of a single element in the mask
D3D10StateBlockMaskIntersect Combine two masks to one that only contains elements that were set in both
D3D10StateBlockMaskUnion Combine two masks to one that contains any element that was set in one of them

Table state block mask functions.

The effect framework can gives you a mask for every pass or a mask for a whole technique. The second one is a combined mask for all passes.

The global function D3D10CreateStateBlock will take a device and such a mask to create a state block object and gives you an ID3D10StateBlock interface to use it. Beside of getting the device that it is attached to you can call Capture to store the current states and Apply to push them back to the device.

// Create a state block as part of the setuppPass->ComputeStateBlockMask (&mask);
 
D3D10CreateStateBlock (pDevice, &Mask, &pStateBlock);
 
// During the render loop use the state block
// update the effect variables
 
pStateBlock->Capture ();
 
pPass->Apply ();
 
// render something
 
pStateBlock->Apply ();
Personal tools