std::vectorで頂点データを渡すときのアドレスとサイズ
はじめに
最近DirectXを試したときのプログラムが出てきて頂点データ等を扱うときにstd::vectorを使ってミスしたことを思い出したのでそのことを今日は書いておきます。
配列を渡すとき
ポリゴンを描画するときに頂点データを構造体の配列などで渡すことがあります。
まず構造体は仮に
1 2 3 4 5
| struct Vertex{ struct { float x, y, z; } pos; };
|
だとしたとき
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| Vertex vertices[] = { {0.0f, 0.0f, 0.0f}, (省略) };
Microsoft::WRL::ComPtr<ID3D11Buffer> pVertexBuffer; D3D11_BUFFER_DESC bd = {}; bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; bd.Usage = D3D11_USAGE_DEFAULT; bd.CPUAccessFlags = 0; bd.MiscFlags = 0; bd.ByteWidth = sizeof(vertices); bd.StructureByteStride = sizeof(Vertex);
D3D11_SUBRESOURCE_DATA sd = {}; sd.pSysMem = vertices;
_d3dDevice->CreateBuffer(&bd, &sd, &pVertexBuffer);
const UINT stride = sizeof(Vertex); const UINT offset = 0;
_d3dContext->IASetVertexBuffers(0, 1, pVertexBuffer.GetAddressOf(), &stride, &offset);
|
みたいな感じで配列を渡せます。
std::vectorにそのまま変えるとどうなるか
先ほどのコードの配列の部分をそのままstd::vector<Vertex>
に変えてみます。
1 2 3 4 5 6 7 8 9 10 11
| std::vector<Vertex> vertices ={ {0.0f, 0.0f, 0.0f}, (省略) };
(同文)
sd.pSysMem = &vertices;
(同文)
|
これは果たして動くのかというと動きません。
今回の件に関わってくるのは
の指定です。
なぜ動かないのか
なぜかというとstd::vector
のメモリの事情が配列とは異なるからです。
具体的にどう違うのかは実際に値を見ればあきらかです。
今回の検証で使用するコードは以下の物です。
Vertex
構造体をもつ配列とstd::Vector
を同じ内容で用意しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include <iostream> #include <vector>
struct Vertex { struct { float x, y, z; } pos; };
int main() { Vertex verticesA[] = { {0.0f,1.0f,2.0f}, {0.0f,1.0f,2.0f}, {0.0f,1.0f,2.0f}, {0.0f,1.0f,2.0f} }; std::vector<Vertex> verticesV = { {0.0f,1.0f,2.0f}, {0.0f,1.0f,2.0f}, {0.0f,1.0f,2.0f}, {0.0f,1.0f,2.0f} };
std::cout << "sizeof(float): " << sizeof(float) << '\n'; std::cout << "sizeof(Vertex): " << sizeof(Vertex) << '\n'; std::cout << "sizeof(verticesA): " << sizeof(verticesA) << '\n'; std::cout << "sizeof(verticesV): " << sizeof(verticesV) << '\n'; std::cout << "verticesV.size(): " << verticesV.size() << '\n'; std::cout << "std::size(verticesV): " << std::size(verticesV) << '\n';
std::cout << "\n ================= \n";
std::cout << "verticesA: " << verticesA << '\n'; std::cout << "&verticesA: " << &verticesA << '\n'; std::cout << "&verticesA[0]: " << &verticesA[0] << '\n'; std::cout << "&verticesV: " << &verticesV << '\n'; std::cout << "&verticesV[0]: " << &verticesV[0] << '\n'; std::cout << "verticesV.data(): " << verticesV.data() << '\n'; }
|
データサイズについて
データサイズについて検証コードの出力結果は以下の通りです。
※64bitビルド
1 2 3 4 5 6
| sizeof(float): 4 sizeof(Vertex): 12 sizeof(verticesA): 48 sizeof(verticesV): 24 verticesV.size(): 4 std::size(verticesV): 4
|
float
が4バイトでVertex
の中にはfloatが3つなのでsizeof(Vertex)
は12で正しいですね。
そしてverticesA
には4つのVertex
があるのでsizeof(verticesA)
は48で期待通りです。
さてsizeof(verticesV)
は48ではなく24ですね。
これはstd::vector
を見れば
1 2 3
| pointer _Myfirst; // pointer to beginning of array pointer _Mylast; // pointer to current end of sequence pointer _Myend; // pointer to end of array
|
とありますね。8 × 3 = 24
ということです。
verticesV.size()
とstd::size(verticesV)
は今回配列内容のVertex
の4個ですね。
つまりVertex
のサイズ×verticesV
の個数で欲しい48が得られます。
ちなみに32bitでビルドすると
1 2 3 4 5 6
| sizeof(float): 4 sizeof(Vertex): 12 sizeof(verticesA): 48 sizeof(verticesV): 12 verticesV.size: 4 std::size(verticesV): 4
|
と出てきます。4 × 3 = 12
ということです。
Vertex
のサイズ×verticesV
の個数をすれば良いのは変わりません。
余談ですが
64bitのデバッグビルドだと
さらに32bitのデバッグビルドだと
となります。
デバッグだとstd::vector
の中身が1つ増えてるみたいです。
アドレスについて
アドレスについて検証コードの出力結果は以下の通りです。
1 2 3 4 5 6
| verticesA: 0x61fda0 &verticesA: 0x61fda0 &verticesA[0]: 0x61fda0 &verticesV: 0x61fd80 &verticesV[0]: 0xf57f80 verticesV.data(): 0xf57f80
|
verticesA
、&verticesA
、&verticesA[0]
が同じなのは当たり前ですね。
変数名[添え字]
はただ*(変数名+添え字)
を簡単に書いただけですから。
さて、&verticesV
は0x61fd80
を返していますが&verticesV[0]
とverticesV.data()
は違う値を返していますね。
std::vector
はただの配列ではなくて他のデータも保持しているので配列に当たる部分が始まるのは&verticesV[0]
からです。
&verticesV[0]
とverticesV.data()
は同じ内容です。
解決策
以上の結果を踏まえて
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| std::vector<Vertex> vertices ={ {0.0f, 0.0f, 0.0f}, (省略) };
(同文)
bd.ByteWidth = sizeof(Vertex)*std::size(vertices); //←実データのバイトサイズ
(同文)
sd.pSysMem = vertices.data(); //←実データのアドレス
(同文)
|
とすることで正常にstd::vector
の頂点データを渡せます。
おわりに
std::vector
は便利ですが使いやすい分こういう所もあるので注意ですね。