DirectX/DXGIのDXGI_MODE_DESC1をドキュメントの順番どおりに波括弧で初期化したのに、なぜかコンパイルエラー——。原因は「順番」ではなく、途中にあるDXGI_RATIONALなど“構造体メンバーの型”です。混同しやすいDXGI_MODE_DESCとの違いも含め、確実に通る初期化パターンを整理します。
DXGI_MODE_DESC1とは:まず「定義(メンバーの型)」を確認する
DXGI_MODE_DESC1は、表示モード(解像度・リフレッシュレート・ピクセルフォーマット等)を表すDXGIの構造体です。C++で問題が起きやすいのは、メンバーの一部が「数値」ではなく「別の構造体」や「列挙型」になっている点です。
代表的な定義は次のとおりです(Windows SDKのヘッダーに定義されています)。
typedef struct DXGI_MODE_DESC1
{
UINT Width;
UINT Height;
DXGI_RATIONAL RefreshRate;
DXGI_FORMAT Format;
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;
DXGI_MODE_SCALING Scaling;
BOOL Stereo;
} DXGI_MODE_DESC1;
そして、問題の中心になるのがRefreshRateの型であるDXGI_RATIONALです。これは「分子/分母」でレートを表す、もう一つの構造体です。
typedef struct DXGI_RATIONAL
{
UINT Numerator; // 分子
UINT Denominator; // 分母
} DXGI_RATIONAL;
| メンバー | 型 | 意味 | 初期化で注意する点 |
|---|---|---|---|
| Width | UINT | 横解像度 | 単なる数値でOK |
| Height | UINT | 縦解像度 | 単なる数値でOK |
| RefreshRate | DXGI_RATIONAL | リフレッシュレート(Hz相当) | 構造体なので{分子, 分母}が必要 |
| Format | DXGI_FORMAT | ピクセルフォーマット | 列挙値を指定 |
| ScanlineOrdering | DXGI_MODE_SCANLINE_ORDER | 走査線順 | 通常はUNSPECIFIEDで十分な場面が多い |
| Scaling | DXGI_MODE_SCALING | スケーリング方式 | 通常はUNSPECIFIEDで十分な場面が多い |
| Stereo | BOOL | ステレオ3Dの有無 | FALSE/TRUE(またはfalse/true)を指定 |
「ドキュメント順に書いたのに」コンパイルエラーになる理由
結論から言うと、エラーの原因は順番ではなく型です。DXGI_MODE_DESC1の3番目のメンバーはDXGI_RATIONALなので、そこに60のようなUINTを1つだけ渡すと型が一致しません。
ありがちなNG例:RefreshRateに60だけ渡してしまう
DXGI_MODE_DESC1 desc = {
1920, 1080,
60, // ← ここがDXGI_RATIONALではないので不一致
DXGI_FORMAT_R8G8B8A8_UNORM,
DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED,
DXGI_MODE_SCALING_UNSPECIFIED,
FALSE
};
このコードは「3番目にDXGI_RATIONALが必要なのにUINTしか渡していない」ため、多くの環境でコンパイルエラーになります。エラーメッセージはコンパイラや設定で変わりますが、概ね次のようなニュアンスです。
- 初期化リストから
DXGI_RATIONALへ変換できない DXGI_RATIONALの初期化が不正- 初期化子の型が一致しない
OK例:RefreshRateは{分子,分母}で渡す
60Hzを指定したいなら、60/1として表現します。
DXGI_MODE_DESC1 desc = {
1920, // Width
1080, // Height
{ 60, 1 }, // RefreshRate (DXGI_RATIONAL)
DXGI_FORMAT_R8G8B8A8_UNORM, // Format
DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED, // ScanlineOrdering
DXGI_MODE_SCALING_UNSPECIFIED, // Scaling
FALSE // Stereo
};
ポイントは3番目だけ二重の波括弧になることです。C++のリスト初期化は型チェックが厳密なので、「それっぽい数値」を並べても通りません。
波括弧でまとめて初期化する場合:メンバー順は固定で動かせない
DXGI_MODE_DESC1は(少なくとも一般的なWindows SDKでは)コンストラクタを持たない単純な構造体です。そのため、
DXGI_MODE_DESC1 desc = { ... };DXGI_MODE_DESC1 desc{ ... };
のような書き方は集成(aggregate)初期化になり、値は構造体で宣言された順番に割り当てられます。
つまり、波括弧で書く限り「ドキュメントの並び」ではなくヘッダーにある実際のメンバー順がルールです。今回提示されている定義では、Width→Height→RefreshRate→…の順で固定になります。
順番を崩すとどうなる?
順番を入れ替えると、型が違えばコンパイルエラーで気づけます。しかし、型が同じ(または暗黙変換できる)フィールド同士を入れ替えると「コンパイルは通るのに意味が壊れる」という厄介な事故が起きます。
| 入れ替え例 | コンパイル | 何が困るか | 対策 |
|---|---|---|---|
| Format と ScanlineOrdering をうっかり入れ替え | 多くの場合はエラー | 列挙型同士でも型が違うため、気づけることが多い | 順番を固定で守る/代入式にする |
| ScanlineOrdering と Scaling を入れ替え | 多くの場合はエラー | こちらも別の列挙型なのでエラーになりやすい | 代入式にする |
| Width と Height を入れ替え | 通る | 解像度が縦横逆になり、実行時に画面サイズが想定とズレる | 代入式にして意図を明確化 |
「順番は関係ないのでは?」という疑問はもっともですが、波括弧で一括初期化する限り順番は関係します。逆に言えば、順番を気にしたくないなら、次のセクションの方法が実務では安全です。
実務でおすすめ:ゼロ初期化してからメンバー代入する
DirectX/DXGIの構造体はメンバーが増えたり、似た構造体が複数存在したりします。波括弧で一発初期化は短く書ける反面、1つでも型や順番を間違えるとビルドが止まる/あるいは気づきにくいバグになることがあります。
そこでおすすめなのが「まずゼロで初期化 → 必要なメンバーだけ代入」です。
DXGI_MODE_DESC1 desc = {}; // 全メンバーを0で初期化(Width/Heightも0になる)
desc.Width = 1920;
desc.Height = 1080;
desc.RefreshRate = { 60, 1 }; // 60Hz
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
desc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
desc.Stereo = FALSE;
この書き方のメリットは、
- 順番を意識しなくていい(代入なのでどこから書いても同じ)
- メンバー名が見えるので、レビュー時にミスを見つけやすい
- 途中に構造体(
DXGI_RATIONAL)があっても迷いにくい
「= {}」と「未初期化」の違いはかなり大きい
DXGIの構造体は、未初期化のままAPIに渡すと予測不能な動作になりやすい領域です。特にBOOL Stereoのようなフラグ系は、偶然1になってしまうと意図しない分岐を引き起こします。
| 書き方 | 初期状態 | おすすめ度 | コメント |
|---|---|---|---|
DXGI_MODE_DESC1 desc = {}; | 全メンバーが0 | 高 | 簡潔で安全。まずこれでOK |
DXGI_MODE_DESC1 desc{}; | 全メンバーが0 | 高 | C++らしい書き方。意味はほぼ同じ |
DXGI_MODE_DESC1 desc; | 未定義(ゴミ値) | 低 | 代入し忘れがあると危険 |
ZeroMemory(&desc, sizeof(desc)); | 全メンバーが0 | 中 | 古典的。C++では{}の方が読みやすいことが多い |
C++20以降なら指定初期化も選べる(ただし制約あり)
C++20ではC言語のような指定初期化(designated initializers)が使えます。DXGIの構造体はまさにこの恩恵を受けやすい代表例です。
DXGI_MODE_DESC1 desc{
.Width = 1920,
.Height = 1080,
.RefreshRate = { 60, 1 },
.Format = DXGI_FORMAT_R8G8B8A8_UNORM,
.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED,
.Scaling = DXGI_MODE_SCALING_UNSPECIFIED,
.Stereo = FALSE
};
ただしC++の指定初期化は、C言語ほど自由ではありません。一般に次の点を覚えておくと安全です。
- メンバーの宣言順を飛び越えて並べ替えることはできない(宣言順に沿って書く必要がある)
- 指定初期化と非指定初期化を混在させない方が無難
- プロジェクトの言語規格設定(/std:c++20 など)が必要
「順番を完全に無視したい」という用途には向きませんが、メンバー名を明示しながら一括で初期化できるので、可読性は高いです。
DXGI_MODE_DESC と DXGI_MODE_DESC1 を混同しない
質問の文脈で非常に多いのが、DXGI_MODE_DESC1を触っているつもりで、実際には似た名前のDXGI_MODE_DESCを初期化しているケースです。両者は「ほぼ同じ」に見えますが、別の型です。
| 型 | 主なメンバー | 特徴 | よくある事故 |
|---|---|---|---|
DXGI_MODE_DESC | Width, Height, RefreshRate, Format, ScanlineOrdering, Scaling | DXGIの基本モード記述 | 最後にStereoを書いてしまい「初期化子が多すぎる」 |
DXGI_MODE_DESC1 | 上記に加えて Stereo | DXGI 1.2以降で拡張(Stereo追加) | DESCのつもりで6要素だけ書くとStereoは0になる(意図せず固定されることがある) |
混同すると起きる代表的なコンパイルエラー
例えば、DXGI_MODE_DESCを初期化しているのに7要素渡すと、典型的には次のような状況になります。
DXGI_MODE_DESC desc = {
1920, 1080,
{ 60, 1 },
DXGI_FORMAT_R8G8B8A8_UNORM,
DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED,
DXGI_MODE_SCALING_UNSPECIFIED,
FALSE // ← DXGI_MODE_DESCにはStereoがない
};
この場合は「初期化子が多すぎる」「余計な要素がある」といったエラーになりやすく、原因は「順番」ではなくそもそも型が違うことです。
IDEの「定義へ移動(F12)」で型を開き、実際に使っている構造体がどちらなのかをまず確認すると、解決までが早くなります。
RefreshRate(DXGI_RATIONAL)の実用的な指定例
DXGI_RATIONALは「分子/分母」で値を表します。実際のHz相当の値はNumerator / Denominatorです。浮動小数点を直接渡すのではなく、よく使う値を決め打ちで用意しておくと読みやすくなります。
| 一般的な表記 | DXGI_RATIONAL(Numerator/Denominator) | メモ |
|---|---|---|
| 60Hz | { 60, 1 } | 最も一般的 |
| 120Hz | { 120, 1 } | ハイリフレッシュでよく使う |
| 144Hz | { 144, 1 } | ゲーミングモニターの定番 |
| 59.94Hz | { 60000, 1001 } | 映像/放送系で頻出(約59.94) |
| 23.976Hz | { 24000, 1001 } | 約23.976。映画系コンテンツで見かける |
このあたりは「よくある値」を覚えるより、列挙した表示モードからそのままコピーするのが堅実です。たとえばIDXGIOutput/IDXGIOutput1で取得したモード一覧(DXGI_MODE_DESCやDXGI_MODE_DESC1の配列)には、実機がサポートする正確なDXGI_RATIONALが入っています。
ドキュメントは古い?間違っている?判断のコツ
WindowsのAPIドキュメントは基本的に信頼できますが、実務では「ドキュメントの表示」と「手元のWindows SDKヘッダー」の間にズレが生じることもあります。ズレを疑うときは、次の優先順位で確認すると迷いません。
- 最優先:手元のWindows SDKのヘッダー定義(コンパイルが参照しているものが正)
- 次点:IDEでの型定義(F12で開く、IntelliSenseの表示を確認)
- 参考:Web上のドキュメント(更新タイミングの差があり得る)
今回のように「ドキュメント順で初期化したらエラー」になるケースは、実際には型の渡し方が間違っている、または似た型を取り違えていることがほとんどです。提示の定義どおり、RefreshRateがDXGI_RATIONALである限り、60単体で初期化できないのは自然な挙動です。
トラブルシュート手順:原因を最短で切り分ける
DXGIの構造体初期化で詰まったときは、次の順で確認すると無駄がありません。
- 変数の型を確認:本当に
DXGI_MODE_DESC1か?DXGI_MODE_DESCになっていないか? - 3番目の要素を確認:
RefreshRateに{ Numerator, Denominator }を渡しているか? - 一括初期化をやめて代入に切り替える:順番・省略・型の問題を一気に排除できる
- ヘッダー定義を開く:インストールされているWindows SDKでのメンバー順・型を確認する
- 列挙結果を利用する:表示モードを自分で組み立てず、取得した
DXGI_MODE_DESC1を基にする
すぐ使える「安全な初期化」テンプレート
最後に、ミスが起きにくいテンプレートを載せます。まずはこれをベースにして、必要に応じて値だけ差し替えるのが確実です。
// 例:1920x1080 / 60Hz / RGBA8 / Stereoなし
DXGI_MODE_DESC1 MakeModeDesc1_1080p60()
{
DXGI_MODE_DESC1 d = {};
d.Width = 1920;
d.Height = 1080;
d.RefreshRate = DXGI_RATIONAL{ 60, 1 };
d.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
d.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
d.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
d.Stereo = FALSE;
return d;
}
「一括初期化の短さ」を優先したい場合でも、RefreshRateだけは必ず構造体として渡す、という点は変わりません。どちらのスタイルを選ぶかはチームの方針次第ですが、レビューや保守の観点ではゼロ初期化+代入が強い場面が多いです。
まとめ:順番は固定、エラーの本質は「型」と「取り違え」
- 波括弧で初期化する場合、値の並びは構造体のメンバー順で固定
RefreshRateはDXGI_RATIONALなので、{ 60, 1 }のように2要素で渡すDXGI_MODE_DESCとDXGI_MODE_DESC1を取り違えると、初期化子の数や型が合わずに詰まりやすい- 迷ったらゼロ初期化+メンバー代入に切り替えると、順番問題を根こそぎ回避できる
- ドキュメントの表示に不安があるときは、最終的に手元のWindows SDKヘッダーを正とする

コメント