Adreno GPU上从 OpenGL ES 迁移到 Direct3D11.1介绍 (3)

  • 6.2.3 渲染状态
  • 6.2.4 Uniforms/Constants


6.2.3 渲染状态

在 OpenGL ES 中,大多数渲染状态使用 glEnable()、glBlendFunc()、glDepthFunc() 等例程一次指定一个函数调用。渲染状态到单个函数的映射提供了显着的易用性 对开发人员的优势:渲染状态可以指定为已知和需要。 但是,OpenGL ES 应用程序中的一个常见性能问题是冗余状态设置。 此外,存在与为每个单独的渲染状态调用例程相关的函数开销。

Direct3D11 的设计者决定使 API 对性能更友好,以减少状态更改的开销和频率。 在 Direct3D11 中,所有渲染状态都是通过渲染状态块设置的。 渲染状态被组合成通常为特定流水线阶段设置在一起的状态块。 虽然这种状态模型具有性能优势,但对于习惯使用 OpenGL 状态模型的开发人员来说也不太方便。

例如,在 OpenGL ES 中,可以通过以下简单的函数调用来设置深度测试、背面剔除和混合:

glEnable( GL_CULL_FACE );
glCullFace( GL_BACK );
glEnable( GL_DEPTH_TEST );
glDepthFunc( GL_LEQUAL );
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

在 Direct3D11 中设置相同的渲染状态集需要创建多个渲染状态对象并将它们设置为当前,如下所示。

ComPtr<ID3D11RasterizerState> rasterizerState;
D3D11_RASTERIZER_DESC rdesc = CD3D11_RASTERIZER_DESC(D3D11_DEFAULT);
rdesc.CullMode = D3D11_CULL_BACK;
rdesc.FrontCounterClockwise = TRUE;
pD3DDevice->CreateRasterizerState(&rdesc, &rasterizerState);
ComPtr<ID3D11DepthStencilState> depthStencilState;
D3D11_DEPTH_STENCIL_DESC dsdesc = CD3D11_DEPTH_STENCIL_DESC(D3D11_DEFAULT);
dsdesc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL;
dsdesc.DepthEnable = TRUE;
pD3DDevice->CreateDepthStencilState(&dsdesc, &depthStencilState);
ComPtr<ID3D11BlendState> blendState;
D3D11_BLEND_DESC bdesc = CD3D11_BLEND_DESC(D3D11_DEFAULT);
bdesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
bdesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
bdesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
bdesc.RenderTarget[0].BlendEnable = TRUE;
pD3DDevice->CreateBlendState(&bdesc, &blendState);
// Set the render states
pD3DDeviceContext->RSSetState( rasterizerState.Get() );
pD3DDeviceContext-> OMSetDepthStencilState ( depthStencilState.Get() );
float blendFactor[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
UINT32 sampleMask = 0xffffffff;
pD3DDeviceContext->OMSetBlendState(blendState.Get(), blendFactor, sampleMask);

重要的是要了解,为了在 Direct3D11 中最有效地使用渲染状态对象,应用程序应尽量减少设置对象的次数。 此外,理想情况下,渲染状态对象应该在加载/启动时创建,而不是动态创建。 在运行时,应用程序应该只将渲染状态对象设置为当前而不是创建新对象。 这并不总是可行的,但理想情况下应该采用这种方法。

6.2.4 Uniforms/Constants

在 OpenGL ES 2.0 中,所有着色器常量都在 GLSL 中声明为统一变量,并使用 glUniform*() API 设置值。 加载统一数据是 OpenGL ES 2.0 应用程序中常见的瓶颈,尤其是在处理大量数据(例如矩阵调色板蒙皮)时。 与渲染状态 API 非常相似,Direct3D11 设计人员采用了一种旨在提高效率的方法。 在 Direct3D11 中加载到着色器的所有常量数据都是通过常量缓冲区的用户完成的。 常量缓冲区基本上是一组将以相似频率更新的常量。 例如,以下显示了 HLSL 中的常量缓冲区声明:

cbuffer BumpedReflectionConstantBuffer : register(c0)
{
float4x4 MatModelViewProj;
float4x4 MatModelView;
float4 LightPos;
float4 EyePos;
float FresnelPower;
float SpecularPower;
};

常量缓冲区使用 ID3D11Buffer 加载数据。 通常,应用程序将声明一个 C/C++ 结构,表示存储在着色器常量缓冲区中的相同数据。 例如,下面的 C 结构表示上面的常量缓冲区数据。

struct BumpedReflectionConstantBuffer
{
FRMMATRIX4X4 MatModelViewProj;
FRMMATRIX4X4 MatModelView;
FRMVECTOR4 LightPos;
FRMVECTOR4 EyePos;
FLOAT32 FresnelPower;
FLOAT32 SpecularPower;
FRMVECTOR2 Padding; // Pad to multiple of 16-bytes
};

常量缓冲区是使用以下块代码创建的:

D3D11_SUBRESOURCE_DATA constantBufferData;
constantBufferData.pSysMem = pSrcConstants;
constantBufferData.SysMemPitch = 0;
constantBufferData.SysMemSlicePitch = 0;
ComPtr<ID3D11Buffer> buffer;
if ( FAILED(
pD3DDevice->CreateBuffer(
&CD3D11_BUFFER_DESC(nBufferSize, D3D11_BIND_CONSTANT_BUFFER),
&constantBufferData,
&buffer) ) )
{
return FALSE;
}

当常量数据更改时,可以使用 ID3D11DeviceContext::UpdateSubresource() 使用数据更新常量缓冲区。 最后,常量缓冲区可以绑定到顶点(和/或像素着色器),如下所示:

pD3DDeviceContext->VSSetConstantBuffers( bufferIndex,1,
m_pBuffer.GetAddressOf());
pD3DDeviceContext->PSSetConstantBuffers( bufferIndex, 1,
m_pBuffer.GetAddressOf());