Поделиться через


Интерфейсы и классы

Динамическая компоновка шейдеров использует интерфейсы и классы высокоуровневого языка шейдеров (HLSL), которые синтаксически похожи на их аналоги C++. Это позволяет шейдерам ссылаться на абстрактные экземпляры интерфейса во время компиляции и оставлять разрешение этих экземпляров конкретным классам для приложения во время выполнения.

В следующих разделах подробно описано, как настроить шейдер для использования интерфейсов и классов, а также как инициализировать экземпляры интерфейса в коде приложения.

Объявление интерфейсов

Интерфейс работает аналогично абстрактному базовому классу в C++. Интерфейс объявляется в шейдере с помощью интерфейса ключевое слово и содержит только объявления методов. Все методы, объявленные в интерфейсе, будут виртуальными методами в любых классах, производных от интерфейса. Производные классы должны реализовывать все методы, объявленные в интерфейсе . Обратите внимание, что интерфейсы являются единственным способом объявления виртуальных методов. Виртуальные ключевое слово отсутствуют, как в C++, а классы не объявляют виртуальные методы.

В следующем примере кода шейдера объявляется два интерфейса.

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

};
      

Объявление классов

Класс ведет себя аналогично классам в C++. Класс объявляется с классом ключевое слово и может содержать переменные-члены и методы. Класс может наследовать от нуля или одного класса и от нуля или нескольких интерфейсов. Классы должны реализовывать или наследовать реализации для всех интерфейсов в цепочке наследования, иначе экземпляр класса не может быть создан.

В следующем примере кода шейдера показано наследование класса от интерфейса и от другого класса.

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

Объявления экземпляров интерфейса в шейдере

Экземпляр интерфейса выступает в качестве заполнителя для экземпляров класса, которые предоставляют реализацию методов интерфейса. Использование экземпляра интерфейса позволяет коду шейдера вызывать метод, не зная, какая реализация этого метода будет вызвана. Код шейдера объявляет один или несколько экземпляров для каждого определяемого им интерфейса. Эти экземпляры используются в коде шейдера аналогично указателям базового класса C++.

В следующем примере кода шейдера показано объявление нескольких экземпляров интерфейса и их использование в коде шейдера.

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

Объявления экземпляров классов в шейдере

Каждый класс, который будет использоваться вместо экземпляра интерфейса, должен быть объявлен как переменная в буфере констант или создан приложением во время выполнения с помощью метода ID3D11ClassLinkage::CreateClassInstance . Экземпляры интерфейса будут указываться на экземпляры класса в коде приложения. На экземпляры классов можно ссылаться в коде шейдера, как и на любую другую переменную, но класс, производный от интерфейса, обычно используется только с экземпляром интерфейса и не будет ссылаться непосредственно в коде шейдера.

В следующем примере кода шейдера показано объявление нескольких экземпляров класса.

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

Инициализация экземпляров интерфейса в приложении

Экземпляры интерфейса инициализируются в коде приложения путем передачи массива динамической компоновки, содержащего назначения интерфейса, одному из методов SETShader ID3D11DeviceContext .

Чтобы создать массив динамической компоновки, выполните следующие действия.

  1. Создайте объект компоновки класса с помощью createClassLinkage.

    ID3D11ClassLinkage* g_pPSClassLinkage = NULL;            
    pd3dDevice->CreateClassLinkage( &g_pPSClassLinkage );
    
    
  2. Создайте шейдер, который будет использовать динамическое связывание классов, передав объект компоновки класса в качестве параметра в функцию создания шейдера.

    pd3dDevice->CreatePixelShader( pPixelShaderBuffer->GetBufferPointer(),
        pPixelShaderBuffer->GetBufferSize(), g_pPSClassLinkage, &g_pPixelShader ) );            
    
    
  3. Создайте объект ID3D11ShaderReflection с помощью функции D3DReflect .

    ID3D11ShaderReflection* pReflector = NULL; 
    D3DReflect( pPixelShaderBuffer->GetBufferPointer(),                  
        pPixelShaderBuffer->GetBufferSize(), 
        IID_ID3D11ShaderReflection, (void**) &pReflector) );            
    
    
  4. Используйте объект отражения шейдера, чтобы получить количество экземпляров интерфейса в шейдере с помощью метода ID3D11ShaderReflection::GetNumInterfaceSlots .

    g_iNumPSInterfaces = pReflector->GetNumInterfaceSlots();             
    
    
  5. Создайте массив, достаточно большой для хранения количества экземпляров интерфейса в шейдере.

    ID3D11ClassInstance** g_dynamicLinkageArray = NULL;            
    g_dynamicLinkageArray = 
        (ID3D11ClassInstance**) malloc( sizeof(ID3D11ClassInstance*) * g_iNumPSInterfaces );            
    
    
  6. Определите индекс в массиве, соответствующий каждому экземпляру интерфейса, с помощью ID3D11ShaderReflection::GetVariableByName и ID3D11ShaderReflectionVariable::GetInterfaceSlot.

    ID3D11ShaderReflectionVariable* pAmbientLightingVar = 
        pReflector->GetVariableByName("g_abstractAmbientLighting");
        g_iAmbientLightingOffset = pAmbientLightingVar->GetInterfaceSlot(0);            
    
    
  7. Получение экземпляра класса для каждого объекта класса, производного от интерфейса в шейдере, с помощью ID3D11ClassLinkage::GetClassInstance.

    g_pPSClassLinkage->GetClassInstance( "g_hemiAmbientLight", 0, 
        &g_pHemiAmbientLightClass );            
    
    
  8. Задайте экземпляры интерфейса экземплярам класса , задав соответствующую запись в массиве динамической компоновки.

    g_dynamicLinkageArray[g_iAmbientLightingOffset] = g_pHemiAmbientLightClass;            
    
    
  9. Передайте массив динамической компоновки в качестве параметра в вызов SetShader.

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

Динамическое связывание