Interfacce e classi

Il collegamento a shader dinamico usa interfacce e classi di linguaggio di shader di alto livello (HLSL) che sono sintatticamente simili alle loro controparti C++. Ciò consente agli shader di fare riferimento a istanze di interfaccia astratte in fase di compilazione e lasciare la risoluzione di tali istanze a classi concrete per l'applicazione in fase di esecuzione.

Le sezioni seguenti illustrano in dettaglio come configurare uno shader per usare interfacce e classi e come inizializzare le istanze dell'interfaccia nel codice dell'applicazione.

Dichiarazione di interfacce

Funzioni di interfaccia in modo analogo a una classe base astratta in C++. Un'interfaccia viene dichiarata in uno shader usando la parola chiave dell'interfaccia e contiene solo dichiarazioni di metodo. I metodi dichiarati in un'interfaccia saranno tutti metodi virtuali in qualsiasi classe derivata dall'interfaccia. Le classi derivate devono implementare tutti i metodi dichiarati in un'interfaccia. Si noti che le interfacce sono l'unico modo per dichiarare metodi virtuali, non esiste alcuna parola chiave virtuale come in C++e le classi non dichiarano metodi virtuali.

Il codice shader di esempio seguente dichiara due interfacce.

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();

};
      

Dichiarazione di classi

Una classe si comporta in modo simile alle classi in C++. Una classe viene dichiarata con la parola chiave della classe e può contenere variabili e metodi membro. Una classe può ereditare da zero o da una classe e zero o più interfacce. Le classi devono implementare o ereditare implementazioni per tutte le interfacce nella catena di ereditarietà o non è possibile creare un'istanza della classe.

Il codice shader di esempio seguente illustra la derivazione di una classe da un'interfaccia e da un'altra classe.

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);
};        
      

Dichiarazioni di istanza dell'interfaccia in uno shader

Un'istanza dell'interfaccia funge da segnaposto per le istanze di classe che forniscono un'implementazione dei metodi dell'interfaccia. L'uso di un'istanza di un'interfaccia consente al codice shader di chiamare un metodo senza sapere quale implementazione di tale metodo verrà richiamata. Il codice shader dichiara una o più istanze per ogni interfaccia che definisce. Queste istanze vengono usate nel codice shader in modo analogo ai puntatori alla classe di base C++.

Il codice shader di esempio seguente illustra la dichiarazione di diverse istanze dell'interfaccia e l'uso di tali istanze nel codice shader.

// 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); 
}

Dichiarazioni di istanza di classe in uno shader

Ogni classe che verrà usata al posto di un'istanza dell'interfaccia deve essere dichiarata come variabile in un buffer costante o creata dall'applicazione in fase di esecuzione usando il metodo ID3D11ClassLinkage::CreateClassInstance . Le istanze dell'interfaccia verranno puntate alle istanze della classe nel codice dell'applicazione. È possibile fare riferimento alle istanze di classe nel codice shader come qualsiasi altra variabile, ma una classe derivata da un'interfaccia verrà in genere usata solo con un'istanza dell'interfaccia e non verrà fatto riferimento direttamente dal codice shader.

Il codice shader di esempio seguente illustra la dichiarazione di diverse istanze di classe.

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

Inizializzazione delle istanze dell'interfaccia in un'applicazione

Le istanze dell'interfaccia vengono inizializzate nel codice dell'applicazione passando una matrice di collegamento dinamica contenente assegnazioni di interfaccia a uno dei metodi ID3D11DeviceContext SetShader.

Per creare una matrice di collegamento dinamica, seguire questa procedura

  1. Creare un oggetto di collegamento di classe usando CreateClassLinkage.

    ID3D11ClassLinkage* g_pPSClassLinkage = NULL;            
    pd3dDevice->CreateClassLinkage( &g_pPSClassLinkage );
    
    
  2. Creare lo shader che usa il collegamento di classi dinamiche, passando l'oggetto di collegamento della classe come parametro alla funzione di creazione dello shader.

    pd3dDevice->CreatePixelShader( pPixelShaderBuffer->GetBufferPointer(),
        pPixelShaderBuffer->GetBufferSize(), g_pPSClassLinkage, &g_pPixelShader ) );            
    
    
  3. Creare un oggetto ID3D11ShaderReflection usando la funzione D3DReflect .

    ID3D11ShaderReflection* pReflector = NULL; 
    D3DReflect( pPixelShaderBuffer->GetBufferPointer(),                  
        pPixelShaderBuffer->GetBufferSize(), 
        IID_ID3D11ShaderReflection, (void**) &pReflector) );            
    
    
  4. Usare l'oggetto reflection shader per ottenere il numero di istanze dell'interfaccia nello shader usando il metodo ID3D11ShaderReflection::GetNumInterfaceSlots .

    g_iNumPSInterfaces = pReflector->GetNumInterfaceSlots();             
    
    
  5. Creare una matrice abbastanza grande per contenere il numero di istanze dell'interfaccia nello shader.

    ID3D11ClassInstance** g_dynamicLinkageArray = NULL;            
    g_dynamicLinkageArray = 
        (ID3D11ClassInstance**) malloc( sizeof(ID3D11ClassInstance*) * g_iNumPSInterfaces );            
    
    
  6. Determinare l'indice nella matrice che corrisponde a ogni istanza dell'interfaccia usando ID3D11ShaderReflection::GetVariableByName e ID3D11ShaderReflectionVariable::GetInterfaceSlot.

    ID3D11ShaderReflectionVariable* pAmbientLightingVar = 
        pReflector->GetVariableByName("g_abstractAmbientLighting");
        g_iAmbientLightingOffset = pAmbientLightingVar->GetInterfaceSlot(0);            
    
    
  7. Ottenere un'istanza di classe per ogni oggetto di classe derivata da un'interfaccia nello shader usando ID3D11ClassLinkage::GetClassInstance.

    g_pPSClassLinkage->GetClassInstance( "g_hemiAmbientLight", 0, 
        &g_pHemiAmbientLightClass );            
    
    
  8. Impostare istanze dell'interfaccia su istanze di classe impostando la voce corrispondente nella matrice di collegamento dinamico.

    g_dynamicLinkageArray[g_iAmbientLightingOffset] = g_pHemiAmbientLightClass;            
    
    
  9. Passare la matrice di collegamento dinamica come parametro a una chiamata SetShader.

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

Collegamento dinamico