Schnittstellen und Klassen

Die dynamische Shaderverknüpfung verwendet HLSL-Schnittstellen (High-Level Shader Language) und -Klassen, die ihren C++-Entsprechungen syntaktisch ähneln. Dadurch können Shader zur Kompilierzeit auf abstrakte Schnittstelleninstanzen verweisen und die Auflösung dieser Instanzen konkreten Klassen für die Anwendung zur Laufzeit überlassen.

In den folgenden Abschnitten wird beschrieben, wie Sie einen Shader für die Verwendung von Schnittstellen und Klassen einrichten und wie Schnittstelleninstanzen im Anwendungscode initialisiert werden.

Deklarieren von Schnittstellen

Eine Schnittstelle funktioniert ähnlich wie eine abstrakte Basisklasse in C++. Eine Schnittstelle wird in einem Shader mit der Schnittstelle Schlüsselwort (keyword) deklariert und enthält nur Methodendeklarationen. Die in einer Schnittstelle deklarierten Methoden sind alle virtuellen Methoden in allen von der Schnittstelle abgeleiteten Klassen. Abgeleitete Klassen müssen alle In einer Schnittstelle deklarierten Methoden implementieren. Beachten Sie, dass Schnittstellen die einzige Möglichkeit zum Deklarieren virtueller Methoden sind, es keine virtuelle Schlüsselwort (keyword) wie in C++ gibt und dass Klassen virtuelle Methoden nicht deklarieren.

Im folgenden Beispiel-Shadercode werden zwei Schnittstellen deklariert.

interface iBaseLight
{
   float3 IlluminateAmbient(float3 vNormal);
   float3 IlluminateDiffuse(float3 vNormal);
   float3 IlluminateSpecular(float3 vNormal, int specularPower );
};       

interface iBaseMaterial
{
   float3 GetAmbientColor(float2 vTexcoord);
   
   float3 GetDiffuseColor(float2 vTexcoord);

   int GetSpecularPower();

};
      

Deklarieren von Klassen

Eine Klasse verhält sich ähnlich wie Klassen in C++. Eine Klasse wird mit der Klasse Schlüsselwort (keyword) deklariert und kann Membervariablen und -methoden enthalten. Eine Klasse kann von null oder einer Klasse und null oder mehr Schnittstellen erben. Klassen müssen Implementierungen für alle Schnittstellen in ihrer Vererbungskette implementieren oder erben, andernfalls kann die Klasse nicht instanziiert werden.

Im folgenden Beispiel-Shadercode wird veranschaulicht, wie eine Klasse von einer Schnittstelle und einer anderen Klasse abgeleitet wird.

class cAmbientLight : iBaseLight
{
   float3            m_vLightColor;     
   bool     m_bEnable;
   float3 IlluminateAmbient(float3 vNormal);
   float3 IlluminateDiffuse(float3 vNormal);
   float3 IlluminateSpecular(float3 vNormal, int specularPower );
};

class cHemiAmbientLight : cAmbientLight
{
   float4   m_vGroundColor;
   float4   m_vDirUp;
   float3 IlluminateAmbient(float3 vNormal);
};        
      

Schnittstelleninstanzdeklarationen in einem Shader

Eine Schnittstelle instance als Platzhalter für Klasseninstanzen fungiert, die eine Implementierung der Methoden der Schnittstelle bereitstellen. Mithilfe eines instance einer Schnittstelle kann Shadercode eine Methode aufrufen, ohne zu wissen, welche Implementierung dieser Methode aufgerufen wird. Shadercode deklariert eine oder mehrere Instanzen für jede von ihr definierte Schnittstelle. Diese Instanzen werden im Shadercode ähnlich wie C++-Basisklassenzeiger verwendet.

Der folgende Beispiel-Shadercode veranschaulicht das Deklarieren mehrerer Schnittstelleninstanzen und deren Verwendung im Shadercode.

// Declare interface instances
iBaseLight     g_abstractAmbientLighting;
iBaseLight     g_abstractDirectLighting;
iBaseMaterial  g_abstractMaterial;

struct PS_INPUT
{
    float4 vPosition : SV_POSITION;
    float3 vNormal   : NORMAL;
    float2 vTexcoord : TEXCOORD0;
};

float4 PSMain( PS_INPUT Input ) : SV_TARGET
{ 
    float3 Ambient = (float3)0.0f;       
    Ambient = g_abstractMaterial.GetAmbientColor( Input.vTexcoord ) *         
        g_abstractAmbientLighting.IlluminateAmbient( Input.vNormal );

    float3 Diffuse = (float3)0.0f;  
    Diffuse += g_abstractMaterial.GetDiffuseColor( Input.vTexcoord ) * 
        g_abstractDirectLighting.IlluminateDiffuse( Input.vNormal );

    float3 Specular = (float3)0.0f;   
    Specular += g_abstractDirectLighting.IlluminateSpecular( Input.vNormal, 
        g_abstractMaterial.GetSpecularPower() );
     
    float3 Lighting = saturate( Ambient + Diffuse + Specular );
     
    return float4(Lighting,1.0f); 
}

Klasseninstanzdeklarationen in einem Shader

Jede Klasse, die anstelle einer Schnittstelle instance verwendet wird, muss entweder als Variable in einem Konstantenpuffer deklariert oder von der Anwendung zur Laufzeit mit der ID3D11ClassLinkage::CreateClassInstance-Methode erstellt werden. Schnittstelleninstanzen werden im Anwendungscode auf Klasseninstanzen verwiesen. Klasseninstanzen können im Shadercode wie jede andere Variable referenziert werden, aber eine Klasse, die von einer Schnittstelle abgeleitet ist, wird in der Regel nur mit einer Schnittstelle instance verwendet und nicht direkt durch Shadercode referenziert.

Der folgende Beispiel-Shadercode veranschaulicht das Deklarieren mehrerer Klasseninstanzen.

cbuffer cbPerFrame : register( b0 )
{
   cAmbientLight     g_ambientLight;
   cHemiAmbientLight g_hemiAmbientLight;
   cDirectionalLight g_directionalLight;
   cEnvironmentLight g_environmentLight;
   float4            g_vEyeDir;   
};        
      

Initialisieren von Schnittstelleninstanzen in einer Anwendung

Schnittstelleninstanzen werden im Anwendungscode initialisiert, indem ein dynamisches Verknüpfungsarray mit Schnittstellenzuweisungen an eine der SetShader-Methoden ID3D11DeviceContext übergeben wird.

Führen Sie die folgenden Schritte aus, um ein dynamisches Verknüpfungsarray zu erstellen

  1. Erstellen Sie mithilfe von CreateClassLinkage ein Klassenverknüpfungsobjekt.

    ID3D11ClassLinkage* g_pPSClassLinkage = NULL;            
    pd3dDevice->CreateClassLinkage( &g_pPSClassLinkage );
    
    
  2. Erstellen Sie den Shader, der die dynamische Klassenverknüpfung verwendet, und übergeben Sie das Klassenverknüpfungsobjekt als Parameter an die Create-Funktion des Shaders.

    pd3dDevice->CreatePixelShader( pPixelShaderBuffer->GetBufferPointer(),
        pPixelShaderBuffer->GetBufferSize(), g_pPSClassLinkage, &g_pPixelShader ) );            
    
    
  3. Erstellen Sie mithilfe der D3DReflect-Funktion ein ID3D11ShaderReflection-Objekt.

    ID3D11ShaderReflection* pReflector = NULL; 
    D3DReflect( pPixelShaderBuffer->GetBufferPointer(),                  
        pPixelShaderBuffer->GetBufferSize(), 
        IID_ID3D11ShaderReflection, (void**) &pReflector) );            
    
    
  4. Verwenden Sie das Shaderreflektionsobjekt, um die Anzahl der Schnittstelleninstanzen im Shader mithilfe der ID3D11ShaderReflection::GetNumInterfaceSlots-Methode abzurufen.

    g_iNumPSInterfaces = pReflector->GetNumInterfaceSlots();             
    
    
  5. Erstellen Sie ein Array, das groß genug ist, um die Anzahl der Schnittstelleninstanzen im Shader aufzunehmen.

    ID3D11ClassInstance** g_dynamicLinkageArray = NULL;            
    g_dynamicLinkageArray = 
        (ID3D11ClassInstance**) malloc( sizeof(ID3D11ClassInstance*) * g_iNumPSInterfaces );            
    
    
  6. Bestimmen Sie den Index im Array, der den einzelnen Schnittstellen instance entspricht, mithilfe von ID3D11ShaderReflection::GetVariableByName und ID3D11ShaderReflectionVariable::GetInterfaceSlot.

    ID3D11ShaderReflectionVariable* pAmbientLightingVar = 
        pReflector->GetVariableByName("g_abstractAmbientLighting");
        g_iAmbientLightingOffset = pAmbientLightingVar->GetInterfaceSlot(0);            
    
    
  7. Rufen Sie mithilfe von ID3D11ClassLinkage::GetClassInstance eine Klasse instance für jedes Klassenobjekt ab, das von einer Schnittstelle im Shader abgeleitet ist.

    g_pPSClassLinkage->GetClassInstance( "g_hemiAmbientLight", 0, 
        &g_pHemiAmbientLightClass );            
    
    
  8. Legen Sie Schnittstelleninstanzen auf Klasseninstanzen fest, indem Sie den entsprechenden Eintrag im Array für dynamische Verknüpfung festlegen.

    g_dynamicLinkageArray[g_iAmbientLightingOffset] = g_pHemiAmbientLightClass;            
    
    
  9. Übergeben Sie das dynamische Verknüpfungsarray als Parameter an einen SetShader-Aufruf.

    pd3dImmediateContext->PSSetShader( g_pPixelShader, g_dynamicLinkageArray, g_iNumPSInterfaces );            
    
    

Dynamische Verknüpfung