流输出阶段
我们通过渲染管线的流输出阶段让GPU将几何着色器输出的顶点集合写入到指定的顶点缓冲区,如下图所示。
1.用来接收流输出的顶点集合的顶点缓冲区描述符需要指定D3D11_BIND_STREAM_OUTPUT绑定标记。比如
// 设置顶点缓冲区描述
D3D11_BUFFER_DESC vbd;
ZeroMemory(&vbd, sizeof(vbd));
vbd.Usage = D3D11_USAGE_DEFAULT; // 这里需要允许流输出阶段通过GPU写入
vbd.ByteWidth = sizeof vertices;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_STREAM_OUTPUT; // 需要额外添加流输出标签
vbd.CPUAccessFlags = 0;
// 新建顶点缓冲区
D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory(&InitData, sizeof(InitData));
InitData.pSysMem = vertices;
HR(m_pd3dDevice->CreateBuffer(&vbd, &InitData, m_pVertexBuffers[0].ReleaseAndGetAddressOf()));
2.绑定时需要注意,同一个顶点缓冲区不能同时绑定的输入装配阶段和流输出阶段。所以先恢复默认的流输出阶段和输入装配阶段,然后指定流输出的指定缓冲区。
// 先恢复流输出默认设置,防止顶点缓冲区同时绑定在输入和输出阶段
UINT stride = sizeof(VertexPosColor);
UINT offset = 0;
ID3D11Buffer * nullBuffer = nullptr;
m_pd3dImmediateContext->SOSetTargets(1, &nullBuffer, &offset);
// ...
m_pd3dImmediateContext->IASetInputLayout(mVertexPosColorLayout.Get());
// ...
m_pd3dImmediateContext->SOSetTargets(1, vertexBufferOut.GetAddressOf(), &offset);
3.创建几何着色器时需要填充结构体D3D11_SO_DECLARATION_ENTRY,指定哪些数据流向哪个输出槽。
typedef struct D3D11_SO_DECLARATION_ENTRY {
UINT Stream; // 输出流索引,从0开始
LPCSTR SemanticName; // 语义名
UINT SemanticIndex; // 语义索引
BYTE StartComponent; // 从第几个分量(xyzw)开始,只能取0-3
BYTE ComponentCount; // 分量的输出数目,只能取1-4
BYTE OutputSlot; // 输出槽索引,只能取0-3
};
这里对应的是索引为0的流输出对象,输出给绑定在索引为0的输出槽的顶点缓冲区,先输出语义为POSITION的向量中的xyz分量,然后输出COLOR整个向量。这样一个输出的顶点就和原来的顶点一致了。
const D3D11_SO_DECLARATION_ENTRY posColorLayout[2] = {
{ 0, "POSITION", 0, 0, 3, 0 },
{ 0, "COLOR", 0, 0, 4, 0 }
};
- 创建几何着色器有所不同。
//原接口
HRESULT ID3D11Device::CreateGeometryShaderWithStreamOutput(
const void *pShaderBytecode, // [In]编译好的着色器字节码
SIZE_T BytecodeLength, // [In]字节码长度
const D3D11_SO_DECLARATION_ENTRY *pSODeclaration, // [In]D3D11_SO_DECLARATION_ENTRY的数组
UINT NumEntries, // [In]入口总数
const UINT *pBufferStrides, // [In]一个数组包含了每个绑定到流输出的缓冲区中顶点字节大小
UINT NumStrides, // [In]上面数组的元素数目
UINT RasterizedStream, // [In]按索引指定哪个流输出对象用于传递到光栅化阶段
ID3D11ClassLinkage *pClassLinkage, // [In]忽略
ID3D11GeometryShader **ppGeometryShader // [Out]创建好的几何着色器
);
//调用示例
const D3D11_SO_DECLARATION_ENTRY posColorLayout[2] = {
{ 0, "POSITION", 0, 0, 3, 0 },
{ 0, "COLOR", 0, 0, 4, 0 }
};
HR(device->CreateGeometryShaderWithStreamOutput(blob->GetBufferPointer(), blob->GetBufferSize(), posColorLayout, ARRAYSIZE(posColorLayout),
&stridePosColor, 1, D3D11_SO_NO_RASTERIZED_STREAM, nullptr, m_pTriangleSOGS.GetAddressOf()));
5.这样绘制的时候就可以将几何着色器的顶点集合输出到指定的顶点缓冲区中,然后再次作为输入的顶点缓冲区重新绘制。示例如下:
// 初始化所有顶点缓冲区
for (int i = 1; i < 7; ++i)
{
vbd.ByteWidth *= 3;
HR(m_pd3dDevice->CreateBuffer(&vbd, nullptr, m_pVertexBuffers[i].ReleaseAndGetAddressOf()));
m_BasicEffect.SetStreamOutputSplitedTriangle(m_pd3dImmediateContext.Get(), m_pVertexBuffers[i - 1].Get(), m_pVertexBuffers[i].Get());
// 第一次绘制需要调用一般绘制指令,之后就可以使用DrawAuto了
if (i == 1)
{
m_pd3dImmediateContext->Draw(m_InitVertexCounts, 0);
}
else
{
m_pd3dImmediateContext->DrawAuto();
}
}
void BasicEffect::SetStreamOutputSplitedTriangle(ID3D11DeviceContext * deviceContext, ID3D11Buffer * vertexBufferIn, ID3D11Buffer * vertexBufferOut)
{
// 先恢复流输出默认设置,防止顶点缓冲区同时绑定在输入和输出阶段
UINT stride = sizeof(VertexPosColor);
UINT offset = 0;
ID3D11Buffer * nullBuffer = nullptr;
deviceContext->SOSetTargets(1, &nullBuffer, &offset);
deviceContext->IASetInputLayout(nullptr);
deviceContext->SOSetTargets(0, nullptr, &offset);
deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
deviceContext->IASetInputLayout(pImpl->m_pVertexPosColorLayout.Get());
deviceContext->IASetVertexBuffers(0, 1, &vertexBufferIn, &stride, &offset);
deviceContext->VSSetShader(pImpl->m_pTriangleSOVS.Get(), nullptr, 0);
deviceContext->GSSetShader(pImpl->m_pTriangleSOGS.Get(), nullptr, 0);
deviceContext->SOSetTargets(1, &vertexBufferOut, &offset);
deviceContext->RSSetState(nullptr);
deviceContext->PSSetShader(nullptr, nullptr, 0);
}
drawAtuo 的过程如下图所示