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


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

Динамическая компоновка шейдеров использует интерфейсы и классы языка шейдеров высокого уровня (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 SetShader.

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

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

динамической компоновки