minfaox3
記事54
タグ18
カテゴリ0

アクセス数

  • 集計期間:24/03/29~

記事のアーカイブ

std::vectorで頂点データを渡すときのアドレスとサイズ

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;

(同文)

これは果たして動くのかというと動きません。
今回の件に関わってくるのは

  • bd.ByteWidth
    • 頂点データたちのサイズ
  • sd.pSysMem
    • 頂点データたちの先頭アドレス

の指定です。

なぜ動かないのか

なぜかというと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のデバッグビルドだと

1
sizeof(verticesV):    32

さらに32bitのデバッグビルドだと

1
sizeof(verticesV):    16

となります。
デバッグだと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]が同じなのは当たり前ですね。
変数名[添え字]はただ*(変数名+添え字)を簡単に書いただけですから。
さて、&verticesV0x61fd80を返していますが&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は便利ですが使いやすい分こういう所もあるので注意ですね。

投稿日:2024年02月14日
投稿者: minfaox3
本文リンク: https://minfaox3.net/2024/02/14/20240214/

×