Interfacce e classi

Il collegamento allo shader dinamico usa interfacce e classi HLSL (High-Level Shader Language) sintatticamente simili alle controparti C++. In questo modo gli shader possono fare riferimento a istanze di interfaccia astratta 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 come configurare uno shader per usare interfacce e classi e come inizializzare le istanze di interfaccia nel codice dell'applicazione.

Dichiarazione di interfacce

Funzioni di interfaccia in modo simile a una classe base astratta in C++. Un'interfaccia viene dichiarata in uno shader usando la parola chiave interface 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++, mentre 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 class e può contenere variabili e metodi membro. Una classe può ereditare da zero o una classe e zero o più interfacce. Le classi devono implementare o ereditare implementazioni per tutte le interfacce nella relativa catena di ereditarietà o la classe non può essere creata un'istanza.

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 di interfaccia in uno shader

Un'istanza di 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 definita. 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 di 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 di 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 di interfaccia verranno puntate alle istanze di 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 di interfaccia e non verrà fatto riferimento direttamente dal codice dello 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 di istanze di interfaccia in un'applicazione

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

Per creare una matrice di collegamento dinamico, 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 di 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 di interfaccia nello shader usando il metodo ID3D11ShaderReflection::GetNumInterfaceSlots .

    g_iNumPSInterfaces = pReflector->GetNumInterfaceSlots();             
    
    
  5. Creare una matrice sufficientemente grande da contenere il numero di istanze di 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 classe derivato da un'interfaccia nello shader usando ID3D11ClassLinkage::GetClassInstance.

    g_pPSClassLinkage->GetClassInstance( "g_hemiAmbientLight", 0, 
        &g_pHemiAmbientLightClass );            
    
    
  8. Impostare istanze di 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 dinamico come parametro a una chiamata SetShader.

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

Collegamento dinamico