And*_*son 5 graphics performance direct3d direct3d11
我正在尝试编写一个OpenGL包装器,它允许我使用我现有的所有图形代码(为OpenGL编写),并将OpenGL调用路由到Direct3D等价物.到目前为止,这种方法效果令人惊讶,但性能变得非常严重.
现在,我承认我最有可能以一种从未设计过的方式使用D3D.我每个渲染循环更新一个顶点缓冲区数千次.每当我绘制一个"精灵"时,我会用纹理坐标等向GPU发送4个顶点,当屏幕上的"精灵"数量达到1k到1.5k左右时,我的应用程序的FPS就会下降到低于10fps.
使用VS2012性能分析(这很棒,顺便说一下),我可以看到ID3D11DeviceContext-> Draw方法占用了大部分时间: 这里有截图
在设置顶点缓冲区时,还是在绘制方法期间,是否存在一些我没有正确使用的设置?对我的所有精灵使用相同的顶点缓冲区真的非常非常糟糕吗?如果是这样,我还有哪些其他选项不会彻底改变我现有图形代码库的架构(它们是围绕OpenGL范例构建的......每一帧都将所有内容发送到GPU!)
我游戏中最大的FPS杀手就是我在屏幕上显示大量文字.每个字符都是一个纹理四边形,每个字符都要求对顶点缓冲区进行单独更新,并单独调用Draw.如果D3D或硬件不喜欢很多Draw的调用,那么你怎么能一次在屏幕上绘制大量文本呢?
如果您希望看到更多代码以帮助我诊断此问题,请告诉我们.
谢谢!
这是我正在运行的硬件:
这是我正在运行的软件:
这是绘制方法:
void OpenGL::Draw(const std::vector<OpenGLVertex>& vertices)
{
auto matrix = *_matrices.top();
_constantBufferData.view = DirectX::XMMatrixTranspose(matrix);
_context->UpdateSubresource(_constantBuffer, 0, NULL, &_constantBufferData, 0, 0);
_context->IASetInputLayout(_inputLayout);
_context->VSSetShader(_vertexShader, nullptr, 0);
_context->VSSetConstantBuffers(0, 1, &_constantBuffer);
D3D11_PRIMITIVE_TOPOLOGY topology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
ID3D11ShaderResourceView* texture = _textures[_currentTextureId];
// Set shader texture resource in the pixel shader.
_context->PSSetShader(_pixelShaderTexture, nullptr, 0);
_context->PSSetShaderResources(0, 1, &texture);
D3D11_MAPPED_SUBRESOURCE mappedResource;
D3D11_MAP mapType = D3D11_MAP::D3D11_MAP_WRITE_DISCARD;
auto hr = _context->Map(_vertexBuffer, 0, mapType, 0, &mappedResource);
if (SUCCEEDED(hr))
{
OpenGLVertex *pData = reinterpret_cast<OpenGLVertex *>(mappedResource.pData);
memcpy(&(pData[_currentVertex]), &vertices[0], sizeof(OpenGLVertex) * vertices.size());
_context->Unmap(_vertexBuffer, 0);
}
UINT stride = sizeof(OpenGLVertex);
UINT offset = 0;
_context->IASetVertexBuffers(0, 1, &_vertexBuffer, &stride, &offset);
_context->IASetPrimitiveTopology(topology);
_context->Draw(vertices.size(), _currentVertex);
_currentVertex += (int)vertices.size();
}
Run Code Online (Sandbox Code Playgroud)
这是创建顶点缓冲区的方法:
void OpenGL::CreateVertexBuffer()
{
D3D11_BUFFER_DESC bd;
ZeroMemory(&bd, sizeof(bd));
bd.Usage = D3D11_USAGE_DYNAMIC;
bd.ByteWidth = _maxVertices * sizeof(OpenGLVertex);
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = D3D11_CPU_ACCESS_FLAG::D3D11_CPU_ACCESS_WRITE;
bd.MiscFlags = 0;
bd.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA initData;
ZeroMemory(&initData, sizeof(initData));
_device->CreateBuffer(&bd, NULL, &_vertexBuffer);
}
Run Code Online (Sandbox Code Playgroud)
这是我的顶点着色器代码:
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
matrix model;
matrix view;
matrix projection;
};
struct VertexShaderInput
{
float3 pos : POSITION;
float4 color : COLOR0;
float2 tex : TEXCOORD0;
};
struct VertexShaderOutput
{
float4 pos : SV_POSITION;
float4 color : COLOR0;
float2 tex : TEXCOORD0;
};
VertexShaderOutput main(VertexShaderInput input)
{
VertexShaderOutput output;
float4 pos = float4(input.pos, 1.0f);
// Transform the vertex position into projected space.
pos = mul(pos, model);
pos = mul(pos, view);
pos = mul(pos, projection);
output.pos = pos;
// Pass through the color without modification.
output.color = input.color;
output.tex = input.tex;
return output;
}
Run Code Online (Sandbox Code Playgroud)
您需要做的是尽可能积极地批量顶点,然后绘制大块.我已经非常幸运地将其改造成旧的即时模式OpenGL游戏.不幸的是,这样做很痛苦.
最简单的概念解决方案是使用某种设备状态(您可能已经跟踪它)来为特定的顶点集创建唯一的标记.混合模式和绑定纹理之类的东西是一个很好的集合.如果您可以找到快速哈希算法以在结构中运行,则可以非常有效地存储它.
接下来,您需要执行顶点缓存.有两种方法可以处理,两者都有优势.最具侵略性,最复杂的,并且在具有相似属性的多组顶点的情况下,最有效的是制作设备状态的结构,分配大(例如4KB)缓冲区,并继续存储具有匹配状态的顶点.阵列.然后,您可以将整个数组转储到帧末尾的顶点缓冲区中,并绘制缓冲区块(以重新创建原始顺序).然而,跟踪所有缓冲区以及状态和顺序是困难的.
更简单的方法,可以在良好的环境下提供良好的缓存,是将顶点缓存在大缓冲区中,直到设备状态发生变化.此时,在实际更改状态之前,将数组转储到顶点缓冲区并绘制.然后重置数组索引,提交状态更改,然后再次运行.
如果您的应用程序具有大量类似的顶点,这很可能使用精灵(纹理坐标和颜色可能会改变,但好的精灵将使用单个纹理图集和少量混合模式),即使第二种方法也可以提高性能.
这里的技巧是在系统内存中建立一个缓存,最好是一大块预先分配的内存,然后在绘图之前将其转储到视频内存中.这使您可以对视频内存和绘制调用执行少得多的写入,这往往很昂贵(特别是在一起).正如您所看到的,您所进行的呼叫数量变慢,并且批处理很有可能帮助实现这一目标.诀窍是如果你可以帮助它,不分配每帧的内存,批量足够大的块是值得的,并保持正确的设备状态和每次抽奖的顺序.