いまさらDirect3D11入門

初めてグラフィックスAPIを触る人に向けて

ストリームアウトプット

今回のパートではストリームアウトプットステージ(英訳:Stream-Output Stage)について見ていきます。 ストリームアウトプットステージはジオメトリシェーダの次に実行されるステージになり、グラフィックスパイプラインで処理したプリミティブをバッファに出力することができます。

ドキュメント:ストリームアウトプットステージ (日本語) (英語)

概要

それではストリームアウトプットステージの使い方、設定について見ていきましょう。 対応するプロジェクトはPart11_StreamOutputになります。

シェーダの生成

ストリームアウトプットを利用するときに必要な設定について見ていきます。

ID3D11DeviceContext::SOSetTargets

// Scene::onRender関数の一部
//三角形を生成する
this->mpImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
this->mpImmediateContext->VSSetShader(this->mpVSStreamOutput.Get(), nullptr, 0);
this->mpImmediateContext->GSSetShader(this->mpGeometryShader.Get(), nullptr, 0);
//ストリームアウトプットの出力先となるバッファを設定する
std::array<ID3D11Buffer*, 1> ppSOBufs = { {
  this->mpStreamOutputBuffer.Get(),
} };
std::array<UINT, 1> soOffsets = { { 0 } };
this->mpImmediateContext->SOSetTargets(static_cast<UINT>(ppSOBufs.size()), ppSOBufs.data(), soOffsets.data());

//プリミティブ生成を実行する
this->mpImmediateContext->Draw(this->M_STREAM_OUTPUT_COUNT, 0);

//生成したプリミティブ情報はストリームアウトプットから設定をはずさないと使用できないので注意
ppSOBufs[0] = nullptr;
this->mpImmediateContext->SOSetTargets(static_cast<UINT>(ppSOBufs.size()), ppSOBufs.data(), soOffsets.data());
ppSOBufs[0] = this->mpStreamOutputBuffer.Get();

ID3D11DeviceContext::SOSetTargetsでストリームアウトプットの出力先バッファを設定します。 出力先となるバッファは複数設定できます。 生成したバッファを別のところで使う際はストリームアウトプットステージから外さないと使えないので注意してください。

ドキュメント:ID3D11DeviceContext::SOSetTargets (日本語) (英語)

出力先のバッファの作成

// Scene::onInit関数の一部
//ストリームアウトプットの出力先となるバッファの作成
//生成できる最大数はバッファ生成時に決める必要があります。
D3D11_BUFFER_DESC desc = {};
desc.ByteWidth = sizeof(Vertex) * M_STREAM_OUTPUT_COUNT;
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_STREAM_OUTPUT;
auto hr = this->mpDevice->CreateBuffer(&desc, nullptr, this->mpStreamOutputBuffer.GetAddressOf());
if (FAILED(hr)) {
  throw std::runtime_error("ストリームアウトプット用のバッファ作成に失敗");
}

ストリームアウトプットの出力先のバッファを作る際はBindFlagsにD3D11_BIND_STREAM_OUTPUTを指定する必要があります。 また、生成するプリミティブの上限もバッファ生成時に決めないといけません。

シェーダの生成

さて、ストリームアウトプットステージは出力先を設定すると自動的に有効となり、ジオメトリシェーダの出力を設定したバッファに書き込んでくれます。 サンプルではピクセルシェーダを使用していませんが、もちろんストリームアウトプットステージを使用しながら画面描画を行うこともできます。

出力にジオメトリシェーダを使用しますのでそれを用意する必要があるのですが、その時ID3D11GeometryShaderを作成するには専用の関数を使用する必要があります。 その関数はID3D11Device::CreateGeometryShaderWithStreamOutput になります。

ドキュメント:ID3D11Device::CreateGeometryShaderWithStreamOutput (日本語) (英語)

// Scene::onInit関数の一部
//ストリームアウトプットを使用するジオメトリシェーダの作成
//D3D11_SO_DECLARATION_ENTRYで出力する頂点のレイアウトを指定している
//入力レイアウトに似たもの
std::array<D3D11_SO_DECLARATION_ENTRY, 2> soEntrys = { {
  { 0, "POSITION", 0, 0, 3, 0 },
  { 0, "TEXCOORD", 0, 0, 4, 0 },
} };
std::array<UINT, 1> strides = { { sizeof(float) * 3 + sizeof(float) * 4 } };
//シェーダ作成
auto hr = this->mpDevice->CreateGeometryShaderWithStreamOutput(
  byteCode.data(),
  static_cast<SIZE_T>(byteCode.size()),
  soEntrys.data(),
  static_cast<UINT>(soEntrys.size()),
  strides.data(),
  static_cast<UINT>(strides.size()),
  D3D11_SO_NO_RASTERIZED_STREAM,
  nullptr,
  this->mpGeometryShader.GetAddressOf());
if (FAILED(hr)) {
  throw std::runtime_error("GeometryShaderの作成に失敗");
}

ID3D11Device::CreateGeometryShaderWithStreamOutputの引数一部

  1. RasterizedStream

    画面描画を行う際、出力した頂点の中で使用するものの添字になります。 画面描画を行わない場合はD3D11_SO_NO_RASTERIZED_STREAMを指定してください。

出力する頂点のレイアウトはD3D11_SO_DECLARATION_ENTRYで設定します。 その内容は頂点レイアウトに似たものとなります。

ドキュメント:D3D11_SO_DECLARATION_ENTRY (日本語) (英語)

D3D11_SO_DECLARATION_ENTRY

  1. Stream

    0から始まるストリーム番号になります。

  2. SemanticName

    要素のセマンティクスになります。 指定できるものは"POSITION""NORMAL"、または "TEXCOORD0"になります。 これにnullptrを設定した場合は、この要素はデータが書き込まれていない隙間とみなされComponentCountに5以上の値を設定できるようになります。

  3. SemanticIndex

    セマンティクスの0から始まる番号になります。

  4. StartComponent, ComponentCount

    使用する要素の範囲を指定します。 StartComponentが2でComponentCountが3なら、yzw成分を使用します。

  5. OutputSlot

    出力するストリームアウトプットステージに設定したバッファへの添字になります。

以上でストリームアウトプットステージの設定およびシェーダの作成についての説明を終わります。

ID3D11DeviceContext::DrawAuto

ストリームアウトプットで生成した頂点データは頂点バッファとして描画に使用できます。 が、実際に生成された頂点数はそのままだとわかりません。 頂点数を調べる方法は存在しますが、この場合ID3D11DeviceContext::DrawAutoという特別なドローコールが用意されています。

ドキュメント:ID3D11DeviceContext::DrawAuto (日本語) (英語)

// Scene::onRender関数一部
//生成した三角形を描画する
//入力アセンブラステージ
std::array<UINT, 1> strides = { { sizeof(Vertex) } };
this->mpImmediateContext->IASetVertexBuffers(0, static_cast<UINT>(ppSOBufs.size()), ppSOBufs.data(), strides.data(), soOffsets.data());
this->mpImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
this->mpImmediateContext->IASetInputLayout(this->mpInputLayout.Get());
//頂点シェーダ
this->mpImmediateContext->VSSetShader(this->mpVertexShader.Get(), nullptr, 0);
//ジオメトリシェーダ
this->mpImmediateContext->GSSetShader(nullptr, nullptr, 0);
//ピクセルシェーダ
this->mpImmediateContext->PSSetShader(this->mpPixelShader.Get(), nullptr, 0);
//サイズ指定を省いて描画している
this->mpImmediateContext->DrawAuto();

使い方については特に説明はありません。 ストリームアウトプットによって生成された頂点データを入力アセンブラステージに設定し、ID3D11DeviceContext::DrawAutoを呼び出すだけで描画ができます。

まとめ

以上でストリームアウトプットの説明は終わります。 このステージを利用することでGPUの力を借りたプリミティブ生成が可能となりますので、活用できるときは活用しましょう。

<前 トップ 次>