Schnittstellen und Klassen

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

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

Deklarieren von Schnittstellen

Eine Schnittstelle funktioniert ähnlich wie eine abstrakte Basisklasse in C++. Eine Schnittstelle wird in einem Shader mithilfe des Interface-Schlüsselworts deklariert und enthält nur Methodendeklarationen. Die in einer Schnittstelle deklarierten Methoden sind alle virtuelle Methoden in allen Klassen, die von der -Schnittstelle abgeleitet sind. Abgeleitete Klassen müssen alle Methoden implementieren, die in einer Schnittstelle deklariert sind. Beachten Sie, dass Schnittstellen die einzige Möglichkeit sind, virtuelle Methoden zu deklarieren. Es gibt kein virtuelles Schlüsselwort wie in C++, und Klassen deklarieren keine virtuellen Methoden.

Der folgende Shader-Beispielcode deklariert zwei Schnittstellen.

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 dem Class-Schlüsselwort 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, da die Klasse nicht instanziiert werden kann.

Der folgende Shader-Beispielcode veranschaulicht das Ableiten einer Klasse von einer Schnittstelle und einer anderen Klasse.

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 Schnittstelleninstanz fungiert als Platzhalter für Klasseninstanzen, die eine Implementierung der Methoden der Schnittstelle bereitstellen. Durch die Verwendung einer Instanz 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 schnittstelle, die er definiert. Diese Instanzen werden in Shadercode auf ähnliche Weise wie C++-Basisklassenzeige verwendet.

Der folgende Shader-Beispielcode 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 statt einer Schnittstelleninstanz verwendet wird, muss entweder als Variable in einem konstanten Puffer deklariert oder von der Anwendung zur Laufzeit mithilfe der ID3D11ClassLinkage::CreateClassInstance-Methode erstellt werden. Schnittstelleninstanzen werden auf Klasseninstanzen im Anwendungscode verwiesen. Auf Klasseninstanzen kann in Shadercode wie jede andere Variable verwiesen werden, aber eine Klasse, die von einer Schnittstelle abgeleitet ist, wird in der Regel nur mit einer Schnittstelleninstanz verwendet und nicht direkt von Shadercode referenziert.

Der folgende Shader-Beispielcode 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 ID3D11DeviceContext SetShader-Methoden übergeben wird.

So erstellen Sie ein dynamisches Verknüpfungsarray

  1. Erstellen Sie mit createClassLinkage ein Klassenverknüpfungsobjekt.

    ID3D11ClassLinkage* g_pPSClassLinkage = NULL;            
    pd3dDevice->CreateClassLinkage( &g_pPSClassLinkage );
    
    
  2. Erstellen Sie den Shader, der dynamische Klassenverknüpfungen 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 zu erhalten.

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

    ID3D11ClassInstance** g_dynamicLinkageArray = NULL;            
    g_dynamicLinkageArray = 
        (ID3D11ClassInstance**) malloc( sizeof(ID3D11ClassInstance*) * g_iNumPSInterfaces );            
    
    
  6. Bestimmen Sie den Index im Array, der den einzelnen Schnittstelleninstanzen entspricht, mit id3D11ShaderReflection::GetVariableByName und ID3D11ShaderReflectionVariable::GetInterfaceSlot.

    ID3D11ShaderReflectionVariable* pAmbientLightingVar = 
        pReflector->GetVariableByName("g_abstractAmbientLighting");
        g_iAmbientLightingOffset = pAmbientLightingVar->GetInterfaceSlot(0);            
    
    
  7. Mit id3D11ClassLinkage::GetClassInstancewird eine Klasseninstanz für jedes Klassenobjekt, das von einer Schnittstelle im Shader abgeleitet wurde, erstellt.

    g_pPSClassLinkage->GetClassInstance( "g_hemiAmbientLight", 0, 
        &g_pHemiAmbientLightClass );            
    
    
  8. Legen Sie Schnittstelleninstanzen auf Klasseninstanzen fest, indem Sie den entsprechenden Eintrag im dynamischen Verknüpfungsarray 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