5

既存のすべてのグラフィックコード(OpenGL用に作成)を使用できるようにし、OpenGL呼び出しを同等のDirect3DにルーティングするOpenGLラッパーを作成しようとしています。これは、パフォーマンスがかなりの問題であることが判明していることを除いて、これまでのところ驚くほどうまく機能しています。

今、私はD3Dを設計されていない方法で使用している可能性が高いことを認めます。レンダリングループごとに1つの頂点バッファーを数千回更新しています。「スプライト」を描画するたびに、テクスチャ座標などを使用して4つの頂点をGPUに送信し、画面上の「スプライト」の数が一度に約1k〜1.5kに達すると、アプリのFPSは次のように低下​​します。 10fps未満。

VS2012パフォーマンス分析(これは素晴らしいですが)を使用すると、ID3D11DeviceContext->Drawメソッドが大部分の時間を占めていることがわかります。 スクリーンショットはこちら

頂点バッファの設定中、または描画メソッド中に正しく使用していない設定はありますか?すべてのスプライトに同じ頂点バッファーを使用するのは本当に本当に悪いことですか?もしそうなら、既存のグラフィックコードベースのアーキテクチャを大幅に変更しない他のオプションはありますか(OpenGLパラダイムを中心に構築されています...すべてをフレームごとにGPUに送信してください!)

私のゲームで最大のFPSキラーは、画面に大量のテキストを表示しているときです。各文字はテクスチャクワッドであり、各文字には頂点バッファへの個別の更新とDrawへの個別の呼び出しが必要です。D3DまたはハードウェアがDrawの多くの呼び出しを好まない場合、他にどのようにして一度に多くのテキストを画面に描画できますか?

この問題の診断に役立つコードが他にあるかどうかをお知らせください。

ありがとう!

これが私が実行しているハードウェアです:

  • Core i7 @ 3.5GHz
  • 16ギガのRAM
  • GeForce GTX 560 Ti

そして、これが私が実行しているソフトウェアです:

  • Windows8リリースプレビュー
  • VS 2012
  • DirectX 11

描画方法は次のとおりです。

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

そして、頂点バッファを作成するメソッドは次のとおりです。

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

これが私の頂点シェーダーコードです:

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

1 に答える 1

6

必要なのは、頂点をできるだけ積極的にバッチ処理してから、大きなチャンクで描画することです。私はこれを古い即時モードのOpenGLゲームに後付けすることができてとても幸運でした。残念ながら、それは一種の苦痛です。

最も単純な概念的な解決策は、ある種のデバイス状態(おそらくすでに追跡している)を使用して、特定の頂点のセットに固有のスタンプを作成することです。ブレンドモードやバウンドテクスチャのようなものが良いセットです。にある構造体で実行する高速ハッシュアルゴリズムを見つけることができれば、それをかなり効率的に保存できます。

次に、頂点キャッシングを実行する必要があります。これを処理する方法は2つあり、どちらにも利点があります。最も積極的で、最も複雑で、同様のプロパティを持つ頂点のセットが多数ある場合、最も効率的なのは、デバイス状態の構造体を作成し、大きな(たとえば、4KB)バッファーを割り当て、それに一致する状態の頂点を格納することです。配列。次に、フレームの最後にある頂点バッファーに配列全体をダンプし、バッファーのチャンクを描画します(元の順序を再作成するため)。ただし、すべてのバッファと状態および順序を追跡することは困難です。

より簡単な方法は、適切な状況下で適切なキャッシュを提供できるため、デバイスの状態が変化するまで頂点を大きなバッファーにキャッシュすることです。その時点で、実際に状態を変更する前に、配列を頂点バッファーにダンプして描画します。次に、アレイインデックスをリセットし、状態の変更をコミットして、もう一度実行します。

アプリケーションに類似した頂点が多数ある場合(スプライトでの作業は非常に可能です(テクスチャの座標と色は変わる可能性がありますが、優れたスプライトは単一のテクスチャアトラスといくつかのブレンドモードを使用します)、2番目の方法でもパフォーマンスが向上します。

ここでの秘訣は、システムメモリ、できれば事前に割り当てられたメモリの大きなチャンクにキャッシュを構築し、描画する直前にそれをビデオメモリにダンプすることです。これにより、ビデオメモリへの書き込みと描画呼び出しをはるかに少なく実行できます。これは(特に一緒に)コストがかかる傾向があります。これまで見てきたように、呼び出す呼び出しの数は遅くなり、バッチ処理はそれを支援する良いチャンスです。秘訣は、フレームごとにメモリを割り当てないことです。それを支援し、価値のある十分な大きさのチャンクをバッチ処理し、各描画の正しいデバイス状態と順序を維持します。

于 2012-08-27T14:58:35.957 に答える