Engine Troublehttps://enginetrouble.net/2017-01-18T09:13:00+09:00UTF-32/UCS-4 に対応した islower/tolower 関数を作ってみよう2017-01-18T09:13:00+09:00mogemimitag:enginetrouble.net,2017-01-18:2017/01/making-islower-and-tolower-functions-for-utf32-ucs4.html<p>今回は、ロケールや処理系、標準ライブラリの実装に依存しない UTF-32/UCS-4 向けの <code>islower</code>/<code>isupper</code>/<code>tolower</code>/<code>toupper</code> 関数を作ります。
C++11 から UTF-32/UCS-4 の文字を格納できる型 <code>char32_t</code> が追加されたので、文字の型は <code>char32_t</code> で指定できるようにします。
また Unicode の文字セットをすべてカバーできるように、 Unicode Character Database を利用してみます。</p>
<h2>レターケース (Letter case)</h2>
<p>今回は Unicode の文字セットと UTF-32/UCS-4 エンコーディングされた文字を扱います。
アルファベットの中には <strong>大文字 (Uppercase)</strong>, <strong>小文字 (Lowercase)</strong> などの区別ができるものがあります。
例えば <code>A</code> (U+0041) は大文字で、 <code>a</code> (U+0061) は小文字です。また、<code>A</code> の小文字は <code>a</code> になり、 <code>a</code> の大文字は <code>A</code> になります。
こういった区別を <strong>レターケース (letter case)</strong> といいます。 Unicode では、大文字・小文字の他に次のレターケースが定義されています:</p>
<ul>
<li>(1) Uppercase</li>
<li>(2) Lowercase</li>
<li>(3) Titlecase</li>
<li>(4) Modifier letter</li>
<li>(5) Other letter</li>
</ul>
<p>それぞれについて説明すると:</p>
<ul>
<li>(1) 大文字のことです。 Capital letter とも言います。例えば <code>A</code> (U+0041) や <code>Σ</code> (U+03A3) が Uppercase です。文字列を Uppercase で示すと <code>UPPERCASE</code> となります。</li>
<li>(2) 小文字のことです。 Small letter とも言います。例えば <code>a</code> (U+0061) や <code>σ</code> (U+03C3) が Lowercase です。文字列を Lowercase で示すと <code>lowercase</code> となります。</li>
<li>(3) 文字列の先頭が大文字に、先頭以外では小文字になるレターケースです。文字列を Titlecase で示すと <code>Titlecase</code> となります。例えば <code>Dz</code> (U+01F2) が Titlecase です。<code>Dz</code> (U+01F2) の Uppercase は <code>DZ</code> (U+01F1) で、 Lowercase は <code>dz</code> (U+01F2) です。</li>
<li>(4) Unicode の中では、隣の文字を修飾する文字や記号を示します。例えば <code>々</code> (U+3005) は Modifier letter です。</li>
<li>(5) (1) から (4) 以外のその他のレターケースです。日本語のひらがなとカタカナがこれに当たります。例えば <code>あ</code> (U+3041) <sup id="fnref:hiragana-lowercase-mapping"><a class="footnote-ref" href="#fn:hiragana-lowercase-mapping" rel="footnote">1</a></sup> や <code>ア</code> (U+30A2) が (5) に含まれます。</li>
</ul>
<h2>Unicode Data File</h2>
<p>Unicode の文字セットは <strong>Unicode Character Database (UCD)</strong> として公開されています。
今回は UCD の中に含まれている <em>UnicodeData.txt</em> ファイル <sup id="fnref:unicodedata-txt"><a class="footnote-ref" href="#fn:unicodedata-txt" rel="footnote">2</a></sup> を使って文字のレターケースを列挙してみます。
<em>UnicodeData.txt</em> ファイルのダウンロードとそのファイルフォーマットについて、詳しくは次のページを参照してください。</p>
<ul>
<li><a href="http://www.unicode.org/ucd/">Unicode Character Database</a></li>
<li><a href="http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt">http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt</a></li>
<li><a href="http://www.unicode.org/reports/tr44/#UnicodeData.txt">http://www.unicode.org/reports/tr44/#UnicodeData.txt</a> - UnicodeData.txt ファイルの形式</li>
</ul>
<p>次のテキストは <em>UnicodeData.txt</em> の一部を引用したものです。</p>
<div class="highlight"><pre><span></span>005F;LOW LINE;Pc;0;ON;;;;;N;SPACING UNDERSCORE;;;;
0060;GRAVE ACCENT;Sk;0;ON;;;;;N;SPACING GRAVE;;;;
0061;LATIN SMALL LETTER A;Ll;0;L;;;;;N;;;0041;;0041
0062;LATIN SMALL LETTER B;Ll;0;L;;;;;N;;;0042;;0042
</pre></div>
<p><em>UnicodeData.txt</em> は、 ASCII コードのみで書かれたテキストファイルになっていて、一行ごとに 1 文字の定義がされています。
各フィールドはセミコロンで区切られているので、取り扱いも難しくありません。
例えば、次の行は <code>a</code> (U+0061) の文字とその定義です。</p>
<div class="highlight"><pre><span></span>0061;LATIN SMALL LETTER A;Ll;0;L;;;;;N;;;0041;;0041
</pre></div>
<p>フィールドは 15 項目に分かれており、最初のフィールドは code point を表しています。例では <code>0061</code> が code point です。
3 番目のフィールド <sup id="fnref:unicode-category"><a class="footnote-ref" href="#fn:unicode-category" rel="footnote">3</a></sup> の <code>Ll</code> は "Letter, Lowercase" のことで、レターケースを示しています。</p>
<table>
<thead>
<tr>
<th align="left">略語</th>
<th align="left">レターケース</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>Lu</code></td>
<td align="left">Letter, Uppercase</td>
</tr>
<tr>
<td align="left"><code>Ll</code></td>
<td align="left">Letter, Lowercase</td>
</tr>
<tr>
<td align="left"><code>Lt</code></td>
<td align="left">Letter, Titlecase</td>
</tr>
<tr>
<td align="left"><code>Lm</code></td>
<td align="left">Letter, Modifier letter</td>
</tr>
<tr>
<td align="left"><code>Lo</code></td>
<td align="left">Letter, Other</td>
</tr>
</tbody>
</table>
<p>また最後の 3 つのフィールド <code>0041;;0041</code> は Uppercase/Lowercase/Titlecase へのマッピングを示しています。
例えば、<code>a</code> (U+0061) の文字の Uppercase または Titlecase が <code>A</code> (U+0041) になります。</p>
<h2>UnicodeData.txt を読み込む</h2>
<p>さっそく読み込んでみます。文字データは次の構造体に格納することにしました。</p>
<div class="highlight"><pre><span></span><span class="k">struct</span> <span class="n">UnicodeCharacterData</span> <span class="p">{</span>
<span class="kt">char32_t</span> <span class="n">codePoint</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">characterName</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">generalCategory</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">canonicalCombiningClasses</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">bidirectionalCategory</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">characterDecompositionMapping</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">decimalDigitValue</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">digitValue</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">numericValue</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">mirrored</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">unicode_1_0_name</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">commentField10646</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">optional</span><span class="o"><</span><span class="kt">char32_t</span><span class="o">></span> <span class="n">uppercaseMapping</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">optional</span><span class="o"><</span><span class="kt">char32_t</span><span class="o">></span> <span class="n">lowercaseMapping</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">optional</span><span class="o"><</span><span class="kt">char32_t</span><span class="o">></span> <span class="n">titlecaseMapping</span><span class="p">;</span>
<span class="p">};</span>
</pre></div>
<p>任意の区切り文字で文字列を分割するために <code>Split</code> 関数を定義します:</p>
<div class="highlight"><pre><span></span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">></span> <span class="n">Split</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&</span> <span class="n">source</span><span class="p">,</span> <span class="kt">char</span> <span class="n">separator</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">></span> <span class="n">tokens</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">size_type</span> <span class="n">start</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">size_type</span> <span class="n">end</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">while</span> <span class="p">((</span><span class="n">end</span> <span class="o">=</span> <span class="n">source</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">separator</span><span class="p">,</span> <span class="n">start</span><span class="p">))</span> <span class="o">!=</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">::</span><span class="n">npos</span><span class="p">)</span> <span class="p">{</span>
<span class="n">tokens</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">source</span><span class="p">.</span><span class="n">substr</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">end</span> <span class="o">-</span> <span class="n">start</span><span class="p">));</span>
<span class="n">start</span> <span class="o">=</span> <span class="n">end</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">tokens</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">source</span><span class="p">.</span><span class="n">substr</span><span class="p">(</span><span class="n">start</span><span class="p">));</span>
<span class="k">return</span> <span class="n">tokens</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>文字列で表現された code point を <code>char32_t</code> に変換する <code>ToCodePointFromHexString</code> 関数を定義します:</p>
<div class="highlight"><pre><span></span><span class="n">std</span><span class="o">::</span><span class="n">optional</span><span class="o"><</span><span class="kt">char32_t</span><span class="o">></span> <span class="n">ToCodePointFromHexString</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&</span> <span class="n">s</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">std</span><span class="o">::</span><span class="n">nullopt</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">static_cast</span><span class="o"><</span><span class="kt">char32_t</span><span class="o">></span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">stoi</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="k">nullptr</span><span class="p">,</span> <span class="mi">16</span><span class="p">));</span>
<span class="p">}</span>
</pre></div>
<p><em>UnicodeData.txt</em> ファイルを読み込む <code>ReadUnicodeDataFile</code> 関数を定義します。最低限のエラーハンドリングしか書いていないので注意してください。</p>
<div class="highlight"><pre><span></span><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">UnicodeCharacterData</span><span class="o">></span> <span class="n">ReadUnicodeDataFile</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&</span> <span class="n">path</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">ifstream</span> <span class="n">ifs</span><span class="p">(</span><span class="n">path</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ifs</span><span class="p">)</span> <span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">cerr</span> <span class="o"><<</span> <span class="s">"Cannot open the file: '"</span> <span class="o"><<</span> <span class="n">path</span> <span class="o"><<</span> <span class="s">"'"</span><span class="p">;</span>
<span class="k">return</span> <span class="p">{};</span>
<span class="p">}</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">UnicodeCharacterData</span><span class="o">></span> <span class="n">datas</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">line</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">getline</span><span class="p">(</span><span class="n">ifs</span><span class="p">,</span> <span class="n">line</span><span class="p">))</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">words</span> <span class="o">=</span> <span class="n">Split</span><span class="p">(</span><span class="n">line</span><span class="p">,</span> <span class="sc">';'</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">words</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">!=</span> <span class="mi">15</span><span class="p">)</span> <span class="p">{</span>
<span class="k">continue</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">UnicodeCharacterData</span> <span class="n">data</span><span class="p">;</span>
<span class="n">data</span><span class="p">.</span><span class="n">codePoint</span> <span class="o">=</span> <span class="o">*</span><span class="n">ToCodePointFromHexString</span><span class="p">(</span><span class="n">words</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
<span class="n">data</span><span class="p">.</span><span class="n">characterName</span> <span class="o">=</span> <span class="n">words</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="n">data</span><span class="p">.</span><span class="n">generalCategory</span> <span class="o">=</span> <span class="n">words</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>
<span class="n">data</span><span class="p">.</span><span class="n">canonicalCombiningClasses</span> <span class="o">=</span> <span class="n">words</span><span class="p">[</span><span class="mi">3</span><span class="p">];</span>
<span class="n">data</span><span class="p">.</span><span class="n">bidirectionalCategory</span> <span class="o">=</span> <span class="n">words</span><span class="p">[</span><span class="mi">4</span><span class="p">];</span>
<span class="n">data</span><span class="p">.</span><span class="n">characterDecompositionMapping</span> <span class="o">=</span> <span class="n">words</span><span class="p">[</span><span class="mi">5</span><span class="p">];</span>
<span class="n">data</span><span class="p">.</span><span class="n">decimalDigitValue</span> <span class="o">=</span> <span class="n">words</span><span class="p">[</span><span class="mi">6</span><span class="p">];</span>
<span class="n">data</span><span class="p">.</span><span class="n">digitValue</span> <span class="o">=</span> <span class="n">words</span><span class="p">[</span><span class="mi">7</span><span class="p">];</span>
<span class="n">data</span><span class="p">.</span><span class="n">numericValue</span> <span class="o">=</span> <span class="n">words</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>
<span class="n">data</span><span class="p">.</span><span class="n">mirrored</span> <span class="o">=</span> <span class="n">words</span><span class="p">[</span><span class="mi">9</span><span class="p">];</span>
<span class="n">data</span><span class="p">.</span><span class="n">unicode_1_0_name</span> <span class="o">=</span> <span class="n">words</span><span class="p">[</span><span class="mi">10</span><span class="p">];</span>
<span class="n">data</span><span class="p">.</span><span class="n">commentField10646</span> <span class="o">=</span> <span class="n">words</span><span class="p">[</span><span class="mi">11</span><span class="p">];</span>
<span class="n">data</span><span class="p">.</span><span class="n">uppercaseMapping</span> <span class="o">=</span> <span class="n">ToCodePointFromHexString</span><span class="p">(</span><span class="n">words</span><span class="p">[</span><span class="mi">12</span><span class="p">]);</span>
<span class="n">data</span><span class="p">.</span><span class="n">lowercaseMapping</span> <span class="o">=</span> <span class="n">ToCodePointFromHexString</span><span class="p">(</span><span class="n">words</span><span class="p">[</span><span class="mi">13</span><span class="p">]);</span>
<span class="n">data</span><span class="p">.</span><span class="n">titlecaseMapping</span> <span class="o">=</span> <span class="n">ToCodePointFromHexString</span><span class="p">(</span><span class="n">words</span><span class="p">[</span><span class="mi">14</span><span class="p">]);</span>
<span class="n">datas</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">data</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">datas</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>実際に <em>UnicodeData.txt</em> ファイルを読み込んで、 code point のリストを表示してみます:</p>
<div class="highlight"><pre><span></span><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
<span class="c1">// NOTE: http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt</span>
<span class="k">auto</span> <span class="n">unicodeDatas</span> <span class="o">=</span> <span class="n">ReadUnicodeDataFile</span><span class="p">(</span><span class="s">"UnicodeData.txt"</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="o">&</span> <span class="nl">data</span> <span class="p">:</span> <span class="n">unicodeDatas</span><span class="p">)</span> <span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">cout</span>
<span class="o"><<</span> <span class="s">"U+"</span> <span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">setfill</span><span class="p">(</span><span class="sc">'0'</span><span class="p">)</span> <span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">setw</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>
<span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">uppercase</span> <span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">hex</span> <span class="o"><<</span> <span class="n">data</span><span class="p">.</span><span class="n">codePoint</span> <span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<h2>IsLowercase の実装</h2>
<p>読み込んだ Unicode Data を使って、 <code>IsLowercase</code> 関数を実装してみます。
レターケースのマッピングを次のような構造体で定義します。</p>
<div class="highlight"><pre><span></span><span class="k">struct</span> <span class="n">LetterCaseMapping</span> <span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">optional</span><span class="o"><</span><span class="kt">char32_t</span><span class="o">></span> <span class="n">uppercase</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">optional</span><span class="o"><</span><span class="kt">char32_t</span><span class="o">></span> <span class="n">lowercase</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">optional</span><span class="o"><</span><span class="kt">char32_t</span><span class="o">></span> <span class="n">titlecase</span><span class="p">;</span>
<span class="p">};</span>
</pre></div>
<p><code>IsLowercase</code> 関数を実装するために、文字セットの中から任意の文字を検索する必要があります。
今回は、ハッシュで検索できる <code>std::unordered_map</code> を使うことにしました。
次の関数は Unicode Data のリストからハッシュマップを作ります。</p>
<div class="highlight"><pre><span></span><span class="n">std</span><span class="o">::</span><span class="n">unordered_map</span><span class="o"><</span><span class="kt">char32_t</span><span class="p">,</span> <span class="n">LetterCaseMapping</span><span class="o">></span>
<span class="n">CreateLetterCaseMap</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">UnicodeCharacterData</span><span class="o">>&</span> <span class="n">unicodeDatas</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">unordered_map</span><span class="o"><</span><span class="kt">char32_t</span><span class="p">,</span> <span class="n">LetterCaseMapping</span><span class="o">></span> <span class="n">letterCaseMap</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="o">&</span> <span class="nl">data</span> <span class="p">:</span> <span class="n">unicodeDatas</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">generalCategory</span> <span class="o">==</span> <span class="s">"Lu"</span><span class="p">)</span> <span class="p">{</span>
<span class="n">LetterCaseMapping</span> <span class="n">mapping</span><span class="p">;</span>
<span class="n">mapping</span><span class="p">.</span><span class="n">uppercase</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">codePoint</span><span class="p">;</span>
<span class="n">mapping</span><span class="p">.</span><span class="n">lowercase</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">lowercaseMapping</span><span class="p">;</span>
<span class="n">mapping</span><span class="p">.</span><span class="n">titlecase</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">titlecaseMapping</span><span class="p">;</span>
<span class="n">letterCaseMap</span><span class="p">.</span><span class="n">emplace</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">codePoint</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">mapping</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">generalCategory</span> <span class="o">==</span> <span class="s">"Ll"</span><span class="p">)</span> <span class="p">{</span>
<span class="n">LetterCaseMapping</span> <span class="n">mapping</span><span class="p">;</span>
<span class="n">mapping</span><span class="p">.</span><span class="n">uppercase</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">uppercaseMapping</span><span class="p">;</span>
<span class="n">mapping</span><span class="p">.</span><span class="n">lowercase</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">codePoint</span><span class="p">;</span>
<span class="n">mapping</span><span class="p">.</span><span class="n">titlecase</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">titlecaseMapping</span><span class="p">;</span>
<span class="n">letterCaseMap</span><span class="p">.</span><span class="n">emplace</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">codePoint</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">mapping</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">generalCategory</span> <span class="o">==</span> <span class="s">"Lt"</span><span class="p">)</span> <span class="p">{</span>
<span class="n">LetterCaseMapping</span> <span class="n">mapping</span><span class="p">;</span>
<span class="n">mapping</span><span class="p">.</span><span class="n">uppercase</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">uppercaseMapping</span><span class="p">;</span>
<span class="n">mapping</span><span class="p">.</span><span class="n">lowercase</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">lowercaseMapping</span><span class="p">;</span>
<span class="n">mapping</span><span class="p">.</span><span class="n">titlecase</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">codePoint</span><span class="p">;</span>
<span class="n">letterCaseMap</span><span class="p">.</span><span class="n">emplace</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">codePoint</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">mapping</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">letterCaseMap</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>このハッシュマップを使って <code>IsLowercase</code> 関数を次のように実装しました。</p>
<div class="highlight"><pre><span></span><span class="kt">bool</span> <span class="nf">IsLowercase</span><span class="p">(</span><span class="kt">char32_t</span> <span class="n">c</span><span class="p">,</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">unordered_map</span><span class="o"><</span><span class="kt">char32_t</span><span class="p">,</span> <span class="n">LetterCaseMapping</span><span class="o">>&</span> <span class="n">map</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">auto</span> <span class="n">iter</span> <span class="o">=</span> <span class="n">map</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">c</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">iter</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">end</span><span class="p">(</span><span class="n">map</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">iter</span><span class="o">-></span><span class="n">second</span><span class="p">.</span><span class="n">lowercase</span> <span class="o">&&</span> <span class="p">(</span><span class="o">*</span><span class="n">iter</span><span class="o">-></span><span class="n">second</span><span class="p">.</span><span class="n">lowercase</span> <span class="o">==</span> <span class="n">c</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p><code>IsLowercase</code> 関数の使い方は次の通りです:</p>
<div class="highlight"><pre><span></span><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
<span class="c1">// NOTE: http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt</span>
<span class="k">auto</span> <span class="n">unicodeDatas</span> <span class="o">=</span> <span class="n">ReadUnicodeDataFile</span><span class="p">(</span><span class="s">"UnicodeData.txt"</span><span class="p">);</span>
<span class="k">auto</span> <span class="n">letterCaseMap</span> <span class="o">=</span> <span class="n">CreateLetterCaseMap</span><span class="p">(</span><span class="n">unicodeDatas</span><span class="p">);</span>
<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">boolalpha</span> <span class="o"><<</span> <span class="n">IsLowercase</span><span class="p">(</span><span class="n">U</span><span class="err">'\</span><span class="n">U00000041</span><span class="err">'</span><span class="p">,</span> <span class="n">letterCaseMap</span><span class="p">)</span> <span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">boolalpha</span> <span class="o"><<</span> <span class="n">IsLowercase</span><span class="p">(</span><span class="n">U</span><span class="err">'\</span><span class="n">U00000061</span><span class="err">'</span><span class="p">,</span> <span class="n">letterCaseMap</span><span class="p">)</span> <span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<h2>ToLowercase 関数の実装</h2>
<p><code>IsLowercase</code> と同様に、 <code>ToLowercase</code> 関数を実装してみます。</p>
<div class="highlight"><pre><span></span><span class="kt">char32_t</span> <span class="nf">ToLowercase</span><span class="p">(</span><span class="kt">char32_t</span> <span class="n">c</span><span class="p">,</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">unordered_map</span><span class="o"><</span><span class="kt">char32_t</span><span class="p">,</span> <span class="n">LetterCaseMapping</span><span class="o">>&</span> <span class="n">map</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">auto</span> <span class="n">iter</span> <span class="o">=</span> <span class="n">map</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">c</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">iter</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">end</span><span class="p">(</span><span class="n">map</span><span class="p">)</span> <span class="o">||</span> <span class="o">!</span><span class="n">iter</span><span class="o">-></span><span class="n">second</span><span class="p">.</span><span class="n">lowercase</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">c</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="o">*</span><span class="n">iter</span><span class="o">-></span><span class="n">second</span><span class="p">.</span><span class="n">lowercase</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p><code>ToLowercase</code> 関数の使い方は次の通りです:</p>
<div class="highlight"><pre><span></span><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
<span class="c1">// NOTE: http://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt</span>
<span class="k">auto</span> <span class="n">unicodeDatas</span> <span class="o">=</span> <span class="n">ReadUnicodeDataFile</span><span class="p">(</span><span class="s">"UnicodeData.txt"</span><span class="p">);</span>
<span class="k">auto</span> <span class="n">letterCaseMap</span> <span class="o">=</span> <span class="n">CreateLetterCaseMap</span><span class="p">(</span><span class="n">unicodeDatas</span><span class="p">);</span>
<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o"><<</span> <span class="n">ToLowercase</span><span class="p">(</span><span class="n">U</span><span class="err">'\</span><span class="n">U00000041</span><span class="err">'</span><span class="p">,</span> <span class="n">letterCaseMap</span><span class="p">)</span> <span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o"><<</span> <span class="n">ToLowercase</span><span class="p">(</span><span class="n">U</span><span class="err">'\</span><span class="n">U00000061</span><span class="err">'</span><span class="p">,</span> <span class="n">letterCaseMap</span><span class="p">)</span> <span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<h2>その他の関数の実装</h2>
<p>その他の <code>IsUppercase</code>, <code>ToUppercase</code>, <code>IsTitlecase</code>, <code>ToTitlecase</code> の実装も載せておきます。
<code>IsLowercase</code> 関数や <code>ToLowercase</code> 関数と同じようにハッシュマップを使っています。</p>
<div class="highlight"><pre><span></span><span class="kt">bool</span> <span class="nf">IsUppercase</span><span class="p">(</span><span class="kt">char32_t</span> <span class="n">c</span><span class="p">,</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">unordered_map</span><span class="o"><</span><span class="kt">char32_t</span><span class="p">,</span> <span class="n">LetterCaseMapping</span><span class="o">>&</span> <span class="n">map</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">auto</span> <span class="n">iter</span> <span class="o">=</span> <span class="n">map</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">c</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">iter</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">end</span><span class="p">(</span><span class="n">map</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">iter</span><span class="o">-></span><span class="n">second</span><span class="p">.</span><span class="n">uppercase</span> <span class="o">&&</span> <span class="p">(</span><span class="o">*</span><span class="n">iter</span><span class="o">-></span><span class="n">second</span><span class="p">.</span><span class="n">uppercase</span> <span class="o">==</span> <span class="n">c</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="kt">char32_t</span> <span class="nf">ToUppercase</span><span class="p">(</span><span class="kt">char32_t</span> <span class="n">c</span><span class="p">,</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">unordered_map</span><span class="o"><</span><span class="kt">char32_t</span><span class="p">,</span> <span class="n">LetterCaseMapping</span><span class="o">>&</span> <span class="n">map</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">auto</span> <span class="n">iter</span> <span class="o">=</span> <span class="n">map</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">c</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">iter</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">end</span><span class="p">(</span><span class="n">map</span><span class="p">)</span> <span class="o">||</span> <span class="o">!</span><span class="n">iter</span><span class="o">-></span><span class="n">second</span><span class="p">.</span><span class="n">uppercase</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">c</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="o">*</span><span class="n">iter</span><span class="o">-></span><span class="n">second</span><span class="p">.</span><span class="n">uppercase</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="kt">bool</span> <span class="nf">IsTitlecase</span><span class="p">(</span><span class="kt">char32_t</span> <span class="n">c</span><span class="p">,</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">unordered_map</span><span class="o"><</span><span class="kt">char32_t</span><span class="p">,</span> <span class="n">LetterCaseMapping</span><span class="o">>&</span> <span class="n">map</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">auto</span> <span class="n">iter</span> <span class="o">=</span> <span class="n">map</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">c</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">iter</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">end</span><span class="p">(</span><span class="n">map</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">iter</span><span class="o">-></span><span class="n">second</span><span class="p">.</span><span class="n">titlecase</span> <span class="o">&&</span> <span class="p">(</span><span class="o">*</span><span class="n">iter</span><span class="o">-></span><span class="n">second</span><span class="p">.</span><span class="n">titlecase</span> <span class="o">==</span> <span class="n">c</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="kt">char32_t</span> <span class="nf">ToTitlecase</span><span class="p">(</span><span class="kt">char32_t</span> <span class="n">c</span><span class="p">,</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">unordered_map</span><span class="o"><</span><span class="kt">char32_t</span><span class="p">,</span> <span class="n">LetterCaseMapping</span><span class="o">>&</span> <span class="n">map</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">auto</span> <span class="n">iter</span> <span class="o">=</span> <span class="n">map</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">c</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">iter</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">end</span><span class="p">(</span><span class="n">map</span><span class="p">)</span> <span class="o">||</span> <span class="o">!</span><span class="n">iter</span><span class="o">-></span><span class="n">second</span><span class="p">.</span><span class="n">titlecase</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">c</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="o">*</span><span class="n">iter</span><span class="o">-></span><span class="n">second</span><span class="p">.</span><span class="n">titlecase</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<h2>最適化</h2>
<p>これらの関数をどんなアプリケーションに使用するのかにもよりますが、 ASCII コードの範囲内 (<code>U+0000</code> から <code>U+007F</code>) の文字が頻繁に入力として与えられることが考えられます。
ASCII コードの文字が入力されたときに、 <code>std::unordered_map</code> よりも早く検索できるようにしてみます。
次のようなテーブルを用意します。</p>
<div class="highlight"><pre><span></span><span class="k">constexpr</span> <span class="kt">char32_t</span> <span class="n">toLowerCharacters</span><span class="p">[</span><span class="mi">128</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
<span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>
<span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>
<span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>
<span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>
<span class="mi">0</span><span class="p">,</span> <span class="mi">97</span><span class="p">,</span> <span class="mi">98</span><span class="p">,</span> <span class="mi">99</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">101</span><span class="p">,</span> <span class="mi">102</span><span class="p">,</span> <span class="mi">103</span><span class="p">,</span> <span class="mi">104</span><span class="p">,</span> <span class="mi">105</span><span class="p">,</span> <span class="mi">106</span><span class="p">,</span> <span class="mi">107</span><span class="p">,</span> <span class="mi">108</span><span class="p">,</span> <span class="mi">109</span><span class="p">,</span> <span class="mi">110</span><span class="p">,</span> <span class="mi">111</span><span class="p">,</span>
<span class="mi">112</span><span class="p">,</span> <span class="mi">113</span><span class="p">,</span> <span class="mi">114</span><span class="p">,</span> <span class="mi">115</span><span class="p">,</span> <span class="mi">116</span><span class="p">,</span> <span class="mi">117</span><span class="p">,</span> <span class="mi">118</span><span class="p">,</span> <span class="mi">119</span><span class="p">,</span> <span class="mi">120</span><span class="p">,</span> <span class="mi">121</span><span class="p">,</span> <span class="mi">122</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>
<span class="mi">0</span><span class="p">,</span> <span class="mi">97</span><span class="p">,</span> <span class="mi">98</span><span class="p">,</span> <span class="mi">99</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">101</span><span class="p">,</span> <span class="mi">102</span><span class="p">,</span> <span class="mi">103</span><span class="p">,</span> <span class="mi">104</span><span class="p">,</span> <span class="mi">105</span><span class="p">,</span> <span class="mi">106</span><span class="p">,</span> <span class="mi">107</span><span class="p">,</span> <span class="mi">108</span><span class="p">,</span> <span class="mi">109</span><span class="p">,</span> <span class="mi">110</span><span class="p">,</span> <span class="mi">111</span><span class="p">,</span>
<span class="mi">112</span><span class="p">,</span> <span class="mi">113</span><span class="p">,</span> <span class="mi">114</span><span class="p">,</span> <span class="mi">115</span><span class="p">,</span> <span class="mi">116</span><span class="p">,</span> <span class="mi">117</span><span class="p">,</span> <span class="mi">118</span><span class="p">,</span> <span class="mi">119</span><span class="p">,</span> <span class="mi">120</span><span class="p">,</span> <span class="mi">121</span><span class="p">,</span> <span class="mi">122</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>
<span class="p">};</span>
</pre></div>
<p>その文字が Lowercase ならその文字の code point を格納します。
また Lowercase でない文字で Lowercase mapping が定義されているなら、Lowercase mapping の code point を格納し、それ以外の文字は 0 を指定します。</p>
<p>このテーブルを使って <code>IsLowercase</code> 関数を実装したのが次のコードです:</p>
<div class="highlight"><pre><span></span><span class="kt">bool</span> <span class="nf">IsLowercase</span><span class="p">(</span><span class="kt">char32_t</span> <span class="n">c</span><span class="p">,</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">unordered_map</span><span class="o"><</span><span class="kt">char32_t</span><span class="p">,</span> <span class="n">LetterCaseMapping</span><span class="o">>&</span> <span class="n">map</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">c</span> <span class="o"><</span> <span class="n">SizeOfArray</span><span class="p">(</span><span class="n">toLowerCharacters</span><span class="p">))</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">t</span> <span class="o">=</span> <span class="n">toLowerCharacters</span><span class="p">[</span><span class="n">c</span><span class="p">];</span>
<span class="k">return</span> <span class="p">(</span><span class="n">t</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&&</span> <span class="p">(</span><span class="n">t</span> <span class="o">==</span> <span class="n">c</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">auto</span> <span class="n">iter</span> <span class="o">=</span> <span class="n">map</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">c</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">iter</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">end</span><span class="p">(</span><span class="n">map</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">iter</span><span class="o">-></span><span class="n">second</span><span class="p">.</span><span class="n">lowercase</span> <span class="o">&&</span> <span class="p">(</span><span class="o">*</span><span class="n">iter</span><span class="o">-></span><span class="n">second</span><span class="p">.</span><span class="n">lowercase</span> <span class="o">==</span> <span class="n">c</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>同様に <code>ToLowercase</code> 関数も実装してみました:</p>
<div class="highlight"><pre><span></span><span class="kt">char32_t</span> <span class="nf">ToLowercase</span><span class="p">(</span><span class="kt">char32_t</span> <span class="n">c</span><span class="p">,</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">unordered_map</span><span class="o"><</span><span class="kt">char32_t</span><span class="p">,</span> <span class="n">LetterCaseMapping</span><span class="o">>&</span> <span class="n">map</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">c</span> <span class="o"><</span> <span class="n">SizeOfArray</span><span class="p">(</span><span class="n">toLowerCharacters</span><span class="p">))</span> <span class="p">{</span>
<span class="k">auto</span> <span class="n">t</span> <span class="o">=</span> <span class="n">toLowerCharacters</span><span class="p">[</span><span class="n">c</span><span class="p">];</span>
<span class="k">return</span> <span class="p">(</span><span class="n">t</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="o">?</span> <span class="nl">c</span> <span class="p">:</span> <span class="n">t</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">auto</span> <span class="n">iter</span> <span class="o">=</span> <span class="n">map</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">c</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">iter</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">end</span><span class="p">(</span><span class="n">map</span><span class="p">)</span> <span class="o">||</span> <span class="o">!</span><span class="n">iter</span><span class="o">-></span><span class="n">second</span><span class="p">.</span><span class="n">lowercase</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">c</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="o">*</span><span class="n">iter</span><span class="o">-></span><span class="n">second</span><span class="p">.</span><span class="n">lowercase</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>今回は <code>constexpr</code> の配列サイズを取るために次のような関数を用意しました。</p>
<div class="highlight"><pre><span></span><span class="k">template</span> <span class="o"><</span><span class="k">typename</span> <span class="n">T</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">N</span><span class="o">></span>
<span class="k">constexpr</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">SizeOfArray</span><span class="p">(</span><span class="n">T</span> <span class="p">(</span><span class="o">&</span><span class="p">)[</span><span class="n">N</span><span class="p">])</span> <span class="p">{</span> <span class="k">return</span> <span class="n">N</span><span class="p">;</span> <span class="p">}</span>
</pre></div>
<h2>参考文献</h2>
<ul>
<li><a href="http://www.unicode.org/ucd/">Unicode Character Database</a></li>
<li><a href="http://www.unicode.org/reports/tr44/#UnicodeData.txt">http://www.unicode.org/reports/tr44/#UnicodeData.txt</a> - UnicodeData.txt について</li>
<li><a href="http://www.unicode.org/reports/tr44/#GC_Values_Table">http://www.unicode.org/reports/tr44/#GC_Values_Table</a> - Letter case などのカテゴリについて</li>
</ul>
<div class="footnote">
<hr />
<ol>
<li id="fn:hiragana-lowercase-mapping">
<p>余談ですが、ひらがなの <code>あ</code> (U+3042) は <code>ぁ</code> (U+3041) へ Lowercase マッピングされているのか気になって確認したところ、 <em>UnicodeData.txt</em> ではそういったマッピングは定義されていませんでした。Unicode の世界ではラテン文字でいうところの "小文字" と、ひらがなでいうところの "小文字" は区別されているようです。 <a class="footnote-backref" href="#fnref:hiragana-lowercase-mapping" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:unicodedata-txt">
<p>UCD の中でも <em>UnicodeData.txt</em> は比較的に古い形式で書かれていて、後方互換性のために残されています。今回はプレーンな C++ でも取り扱いが簡単なテキストファイルということで <em>UnicodeData.txt</em> を使用しました。 <a class="footnote-backref" href="#fnref:unicodedata-txt" rev="footnote" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:unicode-category">
<p>Letter case や文字のカテゴリについてはこちらを参照ください: <a href="http://www.unicode.org/reports/tr44/#GC_Values_Table">http://www.unicode.org/reports/tr44/#GC_Values_Table</a> <a class="footnote-backref" href="#fnref:unicode-category" rev="footnote" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
</ol>
</div>DNA 塩基配列を文字列で表現する2016-12-28T00:00:00+09:00mogemimitag:enginetrouble.net,2016-12-14:2016/12/how-does-string-represent-dna-sequence.html<p><img alt="" src="https://enginetrouble.net/images/2016/12/manga_oct14.png" /><br />
<em>(図1) この日記で書きたかったことを漫画にしました。</em></p>
<h2>TL;DR</h2>
<ul>
<li>DNA の塩基配列は ATGC の 4 文字からなる文字列で表現できます。</li>
<li>二本鎖 DNA や二本鎖 RNA を 1 次元配列で表現できるのは、塩基対によって相補鎖を求められるため。</li>
<li>DNA や RNA について調べるときに文字列の問題として計算機上で扱えば、文字列のアルゴリズムを適用できます。</li>
</ul>
<blockquote>
<p><strong>NOTE: どうして専門分野外の DNA の話を?</strong><br />
文字列アルゴリズムに関する論文やテキストを読むと、かならずと言っていいほど「DNA は ATGC からなる文字列で表せる」と書いています。
特に文字列の編集距離の話でよく登場します。
例えば、 <em>Text Algorithms [MR]</em> や <em>Algorithms on Strings [MHL]</em> といった書籍にも出てきますし、 <em>O(ND) Difference Algorithm [Myers]</em> のイントロダクションでも DNA の話は出てきます。 DNA だけでなく RNA や タンパク質のアミノ酸配列の話も出てきます。
ところで、 DNA の二重らせんモデルの絵を思い浮かべてみると「どうして二重らせん構造なのに一次元配列 1本 で表せるのか」という疑問が湧き上がりました。
そこで、必要最小限の内容にしぼって「どうして DNA は文字列で表せるのか?」という話を日記に書きます。</p>
</blockquote>
<h2>計算機で DNA 塩基配列を扱おう</h2>
<p><strong>DNA</strong> や <strong>RNA</strong>, <strong>タンパク質</strong> という言葉を聞くと生物学や化学の世界を最初に思い浮かべますが、計算機の上でそれらを表現できるのであれば計算機科学の世界の話として扱えます。
実は、計算機科学ではこれらを文字列として扱うことができます。
ただの文字列であれば、それがウイルスの進化系統樹の作成であろうと、もっと一般的な文字列の問題として扱うことができ、文字列のアルゴリズムを適用できます。
それでは何をどうやって文字列として扱っているのでしょうか?</p>
<h2>DNA の塩基配列を文字列で表す</h2>
<p><strong>DNA (deoxyribonucleic acid, デオキシリボ核酸)</strong> の塩基配列は、ATGC の 4 文字からなる文字列 (または 1 次元配列) で表現できます。
例えば、実際の Variola virus (天然痘ウイルス) の DNA 塩基配列は次のように表現できます<sup id="fnref:NCBI"><a class="footnote-ref" href="#fn:NCBI" rel="footnote">1</a></sup>。</p>
<div class="highlight"><pre><span></span>CTCGAGAGTATATGTTGTTGAACGTTATTGTTTGAGAAATAGTTGATGCATCAGAATGGTTTGCATTTAT
</pre></div>
<p>DNA は <strong>ヌクレオチド</strong> が鎖状に繋がってできています。
ヌクレオチドは <strong>リン酸</strong> と <strong>糖</strong> <sup id="fnref:sugar"><a class="footnote-ref" href="#fn:sugar" rel="footnote">2</a></sup>、そして 4 種類の <strong>塩基</strong> のうちの 1 個から構成される物質です。(図2, 図3参照)
4 種類の塩基は次の通りです。</p>
<ul>
<li><strong>Adenine (アデニン)</strong></li>
<li><strong>Thymine (チミン)</strong></li>
<li><strong>Guanine (グアニン)</strong></li>
<li><strong>Cytosine (シトシン)</strong></li>
</ul>
<p><img alt="" src="https://enginetrouble.net/images/2016/12/base-ph-sugar.png" /><br />
<em>(図2) リン酸 (phosphate) と糖 (sugar) そして 4 種類の塩基 (Base) Adenine, Thymine, Guanine, Cytosine のモデル図</em></p>
<p><img alt="" src="https://enginetrouble.net/images/2016/12/nucleotide.png" /><br />
<em>(図3) ヌクレオチド (Nucleotide) のモデル図</em></p>
<p>ATGC の文字集合は、この 4 種類の塩基 <strong>A</strong>denine, <strong>T</strong>hymine, <strong>G</strong>uanine そして <strong>C</strong>ytosine の頭文字に由来します。
また、ヌクレオチドが鎖のように繋がったものを <strong>塩基配列 (sequence)</strong> といいます。
DNA は 2 本のヌクレオチドの鎖が <strong>二重らせん構造</strong> になるように、ヌクレオチドの塩基と塩基が組み合わさってできています。(図4参照)</p>
<p><img alt="" src="https://enginetrouble.net/images/2016/12/dna-nucleotide-double-strandded.png" /><br />
<em>(図4) ヌクレオチドの鎖が 2 本組み合わさってできた DNA のモデル図</em></p>
<p>ここで気になるのが、どうして二重らせん構造なのに DNA の塩基配列を 1 次元配列(または文字列)で表現できるのかということです。例えば 図4 では <code>ACTG</code> と <code>TGAC</code> の 2 つの 1 次元配列を用意する必要があるのではないかと感じます。</p>
<p>この疑問に答えるのが <strong>塩基対 (base pair, bp)</strong> です。A は T と、 G は C と必ず対になります。(図5参照) この組み合わせを塩基対といいます。</p>
<p><img alt="" src="https://enginetrouble.net/images/2016/12/base-pair.png" /><br />
<em>(図5) Adenine は Thymine と、 Guanine は Cytosine と塩基対を作る</em></p>
<p>DNA は二本の鎖が塩基対により <strong>相補的 (complementary)</strong> に組み合わさってできています <sup id="fnref:double-stranded"><a class="footnote-ref" href="#fn:double-stranded" rel="footnote">3</a></sup>。
塩基対の関係を使えば、片方の塩基配列から対になるもう片方の塩基配列(これを <strong>相補鎖</strong> といいます)がわかります。例えば <code>ACTG</code> なら塩基対の関係から、もう片方の塩基配列は <code>TGAC</code> とわかります。(図6参照)</p>
<p><img alt="" src="https://enginetrouble.net/images/2016/12/bp-actg.png" /><br />
<em>(図6) 塩基対の関係からもう片方の塩基配列を求められる</em></p>
<p>ここから DNA の塩基配列は ATGC からなる文字列(または 1 次元配列)で表現できることがわかります。</p>
<h2>RNA</h2>
<p><strong>RNA (ribonucleic acid, リボ核酸)</strong> も DNA と同様に文字列で表現できます。 DNA と異なるところは、構成する塩基です <sup id="fnref:rna-sugar"><a class="footnote-ref" href="#fn:rna-sugar" rel="footnote">4</a></sup>。 RNA は次の 4 種類の塩基から構成されています。</p>
<ul>
<li><strong>A</strong>denine (アデニン)</li>
<li><strong>U</strong>racil (ウラシル)</li>
<li><strong>G</strong>uanine (グアニン)</li>
<li><strong>C</strong>ytosine (シトシン)</li>
</ul>
<p>また、RNA には二重らせん構造の RNA と、一本の鎖から成り立つ RNA の二種類があります<sup id="fnref:RNA-double-strandded"><a class="footnote-ref" href="#fn:RNA-double-strandded" rel="footnote">5</a></sup>。
RNA の場合も DNA と同じように塩基対の関係があり、 A は U と、 G は C と結合します。
なので、一本鎖 RNA も二本鎖 RNA も AUGC の 4 種類の文字からなる文字列で表現できます。</p>
<p>実際の RNA ウイルスを文字列で表現した例を示します。
2016 年にノースカロライナで見つかった豚インフルエンザウイルスの RNA の一部です<sup id="fnref:NCBI2"><a class="footnote-ref" href="#fn:NCBI2" rel="footnote">6</a></sup>。</p>
<div class="highlight"><pre><span></span>ATGAAGGCAGCACTAGCAGTCCTGCTATATGCATTTACAACTGCAAATGCCGACACATTATGTATAGGCT
</pre></div>
<p>ここで U (Uracil) ではなく T で表記されていることに違和感を覚えるかもしれません。
RNA なので T ではなく U と表記してよいのですが、ここでは <strong>FASTA</strong> フォーマット<sup id="fnref:fasta-fna"><a class="footnote-ref" href="#fn:fasta-fna" rel="footnote">7</a></sup> の慣例に従い Uracil を T で表記しています。</p>
<h2>タンパク質</h2>
<p>DNA や RNA といった <strong>核酸</strong> と同じように <strong>タンパク質</strong> の <strong>アミノ酸配列</strong> も文字列で表現できます。
今回はアミノ酸配列についての説明を省略しますが、タンパク質は 20 種類のアミノ酸が 1 次元配列でつながったものです。
20種類のタンパク質はそれぞれ ACDEFGHIKLMNPQRSTVWY のアルファベット一文字で表記されます。
ちなみに、ここで使われていないアルファベットは BJOUXZ の 6 文字です。</p>
<h2>文字列として見たときの DNA</h2>
<p>DNA ウイルスだけでなく、地球上のどんな生物の DNA も ATGC からなる文字列として表現できます。
また、進化的に近い種は DNA の塩基配列も似ています。</p>
<p>DNA を文字列と見立てたとき、文字列同士の <strong>類似度のようなもの</strong> を求めれば、進化的に近い種かどうかを調べることができます。
この類似度のようなものを計算機科学の世界では、 文字列の <strong>編集距離 (edit distance)</strong> といいます。
編集距離にはいくつか種類があり、例をあげると <strong>レーベンシュタイン距離 (Levenshtein distance)</strong> や <strong>ハミング距離 (Hamming distance)</strong>, <strong>Jaro-Winkler distance</strong> などがあります。
この中でよく使われるのはレーベンシュタイン距離です。</p>
<p>また多くの生物で共通する文字の並びがわかれば、生命にとって重要な塩基配列やアミノ酸配列がわかります。
この共通する文字の並びを <strong>共通部分列 (common subsequence)</strong> と言います <sup id="fnref:subsequence"><a class="footnote-ref" href="#fn:subsequence" rel="footnote">8</a></sup>。
2 つの文字列間で共通する <strong>部分列 (subsequence)</strong> の中で最長となるものを <strong>最長共通部分列 (longest common subsequence, LCS)</strong> といい、計算機科学の世界では LCS を求める問題を <strong>LCS Problem</strong> といいます<sup id="fnref:LCS"><a class="footnote-ref" href="#fn:LCS" rel="footnote">9</a></sup>。</p>
<h2>アラインメント</h2>
<p>遺伝子工学の世界では、共通部分列を求めることを <strong>アラインメント (alignment)</strong> と言います。
アラインメントは、2 つの文字列を比較して共通する部分を揃えて、2 つの文字列間の共通点を可視化したものです。</p>
<p>例えば、アラインメント前の 2 つの文字列を x, y とします。</p>
<div class="highlight"><pre><span></span><span class="n">x</span> <span class="o">=</span> <span class="s">"AGCTCGAATATGC"</span>
<span class="n">y</span> <span class="o">=</span> <span class="s">"ACTGAAGAGC"</span>
</pre></div>
<p>x, y のアラインメント結果は次のようになります:</p>
<div class="highlight"><pre><span></span><span class="n">AGCTCGAAT</span><span class="o">-</span><span class="n">ATGC</span> <span class="c1">// x</span>
<span class="n">A</span><span class="o">-</span><span class="n">CT</span><span class="o">-</span><span class="n">GAA</span><span class="o">-</span><span class="n">GA</span><span class="o">-</span><span class="n">GC</span> <span class="c1">// y</span>
</pre></div>
<p>ここで揃えるために挿入した <code>-</code> (ハイフン) のことを <strong>ギャップ (gap)</strong> または <strong>ホール (hole)</strong> といいます。</p>
<h2>マルチプルアラインメント</h2>
<p>2 つ以上の文字列の共通点を可視化したものを <strong>マルチプルアライメント (multiple alignment)</strong> (または Multiple Sequence Alignment, MSA) といいます。</p>
<p>マルチプルアラインメントの入力例は次のように、複数の文字列になります。
ここでは文字列 x, y, z, w を入力としてマルチプルアライメントを求めてみます。</p>
<div class="highlight"><pre><span></span><span class="n">x</span> <span class="o">=</span> <span class="s">"AGCTCGAATATGC"</span>
<span class="n">y</span> <span class="o">=</span> <span class="s">"ACTGAAGAGC"</span>
<span class="n">z</span> <span class="o">=</span> <span class="s">"CGAAGAC"</span>
<span class="n">w</span> <span class="o">=</span> <span class="s">"TCAATT"</span>
</pre></div>
<p>これらの文字列のアラインメント結果は次のようになります。</p>
<div class="highlight"><pre><span></span><span class="n">AGCTCGAAT</span><span class="o">-</span><span class="n">ATGC</span> <span class="c1">// x</span>
<span class="n">A</span><span class="o">-</span><span class="n">CT</span><span class="o">-</span><span class="n">GAA</span><span class="o">-</span><span class="n">GA</span><span class="o">-</span><span class="n">GC</span> <span class="c1">// y</span>
<span class="o">--</span><span class="n">C</span><span class="o">--</span><span class="n">GAA</span><span class="o">-</span><span class="n">GA</span><span class="o">--</span><span class="n">C</span> <span class="c1">// z</span>
<span class="o">---</span><span class="n">TC</span><span class="o">-</span><span class="n">AAT</span><span class="o">--</span><span class="n">T</span><span class="o">--</span> <span class="c1">// w</span>
</pre></div>
<p>このマルチプルアライメントの結果から何がわかるのでしょうか?
例えば、 x, y, z, w がそれぞれヒト、牛、魚、カブトムシの DNA だとします。
すると次のことが見えてきます。</p>
<ul>
<li>ヒトと牛が(魚やカブトムシに比べて)近い種かも?</li>
<li><code>AA</code> は地球上の生物にとって大事な塩基配列かも?</li>
</ul>
<p>例では 4 つの文字列のみですが、このアラインメントの操作をたくさん集めた塩基配列のサンプルにたいして行って、インフルエンザウイルスの進化系統樹を作成したり、動物にとって重要な塩基配列やアミノ酸配列を見つけることができます。</p>
<h2>文字列のアルゴリズムを使おう</h2>
<p>昔はこれらの塩基配列のアラインメントを、生物学者みずから手作業で求めていたそうです。
インフルエンザウイルスの RNA 塩基配列は短いものでも 800 文字、長いと 2200 文字ほどになります<sup id="fnref:bp"><a class="footnote-ref" href="#fn:bp" rel="footnote">10</a></sup>。
これを経験とセンスだけでアラインメントするのはとても大変です。
そこで DNA は文字列なので、今では計算機上でアラインメントを自動的に求められます。もちろん文字列なので文字列のアルゴリズムが使えます。</p>
<p>アラインメントを「文字列 x を文字列 y に変換する編集操作の手順」と見なせば、アラインメントを求める問題は 「M 個の文字列間の LCS を求める問題」または「<strong>Edit graph (エディットグラフ)</strong> を求める問題」に変換することができます。</p>
<p>ちなみに、以下の問題は Edit graph を求める問題に変換できます。</p>
<ul>
<li>編集距離を求める (Edit distance problem)</li>
<li>最長共通部分列を求める (LCS problem)</li>
<li>文字列の差分 diff を求める (文字列 x を文字列 y にする最小の編集操作の手順)</li>
</ul>
<p>大雑把に言ってしまうと、 Edit graph さえ求めてしまえば、編集距離も LCS も diff もアラインメントも求まります。
これらの文字列アルゴリズムの問題例に DNA の塩基配列が使われるのはそういった背景があります。</p>
<p>機会があれば、次回は計算機科学の世界に戻って LCS の話をします。</p>
<h2>参考文献</h2>
<ul>
<li>[Myers] Myers, E.W. "An O(ND) difference algorithm and its variations." Algorithmica Volume 1, Issue 1-4. (1986) 251-266.</li>
<li>[MR] Crochemore, Maxime and Wojciech Rytter. Text Algorithms. (ISBN-13: 978-0195086096)</li>
<li>[MHL] Crochemore, Maxime, Christophe Hancart and Thierry Lecroq. "Alighnment". Algorithms on Strings. (ISBN-13: 978-1107670990) Chapter 7.</li>
<li>星田昌紀, 遺伝子情報処理への挑戦 - コンピュータとバイオのフュージョン (ISBN-13: 978-4320026933) (1994)</li>
<li>Molecular Biology of the Cell, 5th edition. - MBoC の最新版は第6版です。</li>
<li>https://www.ncbi.nlm.nih.gov/genome - NCBI のデータベース。 DNA と RNA の FASTA 表記の実例として引用しました。</li>
</ul>
<div class="footnote">
<hr />
<ol>
<li id="fn:NCBI">
<p>Variola virus の塩基配列の一部。NCBI Reference Sequence: NC_001611.1 から引用しました (<a href="https://www.ncbi.nlm.nih.gov/nuccore/9627521?report=fasta">https://www.ncbi.nlm.nih.gov/nuccore/9627521?report=fasta</a>) <a class="footnote-backref" href="#fnref:NCBI" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:sugar">
<p>DNA を構成する糖はデオキシリボース (deoxyribose)、RNA を構成する糖はリボース (ribose) です。 <a class="footnote-backref" href="#fnref:sugar" rev="footnote" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:double-stranded">
<p>DNA の二本の鎖にはそれぞれ名前がついていて、発見者名をとって片方を <strong>Watson strand</strong> (ワトソン鎖)、もう片方を <strong>Crick strand</strong> (クリック鎖) と呼びます。 <a class="footnote-backref" href="#fnref:double-stranded" rev="footnote" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
<li id="fn:rna-sugar">
<p>構成している糖も DNA と RNA で異なります。 <a class="footnote-backref" href="#fnref:rna-sugar" rev="footnote" title="Jump back to footnote 4 in the text">↩</a></p>
</li>
<li id="fn:RNA-double-strandded">
<p>少し前まで RNA は一本鎖だと言われていましたが、1998 年に <strong>RNAi</strong> とともに 二本鎖 RNA が発見されました。そのため、それ以前の書籍を見ると RNA に関して「一本鎖」と記述されていることがあります。 <a class="footnote-backref" href="#fnref:RNA-double-strandded" rev="footnote" title="Jump back to footnote 5 in the text">↩</a></p>
</li>
<li id="fn:NCBI2">
<p>Influenza A virus の塩基配列の一部。NCBI GenBank: KU598329.1 から引用しました (<a href="https://www.ncbi.nlm.nih.gov/nuccore/992420281?report=fasta">https://www.ncbi.nlm.nih.gov/nuccore/992420281?report=fasta</a>) <a class="footnote-backref" href="#fnref:NCBI2" rev="footnote" title="Jump back to footnote 6 in the text">↩</a></p>
</li>
<li id="fn:fasta-fna">
<p>FASTA フォーマットは広く使われている塩基配列やアミノ酸配列のデータ表現のひとつです。 FASTA フォーマットについてはこちらを参照ください: <a href="https://www.ncbi.nlm.nih.gov/books/NBK53702/">Formatting your Submission - The GenBank Submissions Handbook</a> <a class="footnote-backref" href="#fnref:fasta-fna" rev="footnote" title="Jump back to footnote 7 in the text">↩</a></p>
</li>
<li id="fn:subsequence">
<p>ここでは、部分列 (subsequence) と部分文字列 (substring) を区別しています。 <a class="footnote-backref" href="#fnref:subsequence" rev="footnote" title="Jump back to footnote 8 in the text">↩</a></p>
</li>
<li id="fn:LCS">
<p>実のところ LCS を求めることとレーベンシュタイン距離を求めることはまったく同じ問題です。 <a class="footnote-backref" href="#fnref:LCS" rev="footnote" title="Jump back to footnote 9 in the text">↩</a></p>
</li>
<li id="fn:bp">
<p>NCBI に登録されている Influenza A virus がそのくらいの長さ。長さの単位は bp (base pair) で、ATGC の 1 文字が 1 bp に相当します。 <a class="footnote-backref" href="#fnref:bp" rev="footnote" title="Jump back to footnote 10 in the text">↩</a></p>
</li>
</ol>
</div>動的な定数バッファーを D3D11_MAP_WRITE_NO_OVERWRITE で Map する2016-11-15T15:00:00+09:00mogemimitag:enginetrouble.net,2016-11-15:2016/11/mapping-constant-buffer-with-d3d11-map-write-no-overwrite.html<h2>TL;DR</h2>
<ul>
<li>DirectX 11 で 動的な定数バッファーを Map して書き込むときは <code>D3D11_MAP_WRITE_DISCARD</code> を使おう。</li>
<li>ただし Direct3D 11.1 ランタイムから、デバイスが対応していれば、動的な定数バッファーでも <code>D3D11_MAP_WRITE_NO_OVERWRITE</code> が使用できる。<ul>
<li>それ以前のランタイムでは、頂点バッファーとインデックスバッファーでのみ <code>D3D11_MAP_WRITE_NO_OVERWRITE</code> を使用できる。</li>
<li>デバイスが対応しているかどうかは <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ff476497.aspx">ID3D11Device::CheckFeatureSupport</a> で調べられる。</li>
</ul>
</li>
<li>また Feature level が 9.1, 9.2, 9.3 のグラフィックスデバイスでは、ドライバー側でエミュレートするのでどの環境でも定数バッファーに <code>D3D11_MAP_WRITE_NO_OVERWRITE</code> が使える。</li>
</ul>
<h5>「動的なバッファへ書き込むときに D3D11_MAP_WRITE_NO_OVERWRITE で Map できる?」早見表</h5>
<table>
<thead>
<tr>
<th align="left"></th>
<th align="left">Vertex Buffer</th>
<th align="left">Index Buffer</th>
<th align="left">Constant Buffer</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">DirectX 11 (Feature level: 9.1, 9.2, 9.3)</td>
<td align="left">○</td>
<td align="left">○</td>
<td align="left">○</td>
</tr>
<tr>
<td align="left">DirectX 11.0 and earlier runtimes</td>
<td align="left">○</td>
<td align="left">○</td>
<td align="left">-</td>
</tr>
<tr>
<td align="left">DirectX 11.1 runtime (Windows 8 and later)</td>
<td align="left">○</td>
<td align="left">○</td>
<td align="left">△ (デバイスによる)</td>
</tr>
</tbody>
</table>
<h2>ことの発端</h2>
<p>古い Windows マシンで<a href="https://github.com/mogemimi/pomdog">作っているゲームエンジン</a>を動かしたところ、Direct3D 11 のエラーが発生しました。
動的な<sup id="fnref:1"><a class="footnote-ref" href="#fn:1" rel="footnote">1</a></sup> 定数バッファー (Constant Buffer) へ書き込もうと Map したときにエラーが必ず起こるようです。
ほかのマシンでは問題なく動作していたのに、どうしてエラーが起きたのでしょうか。</p>
<p>今回、動作確認に使った Windows デスクトップは次の 2 つです。DirectX 診断ツール (dxdiag) の実行結果を載せています。</p>
<h5>比較的に新しい Windows 環境(問題なく動いたマシン)</h5>
<ul>
<li>Operating System: Windows 10 Pro 64-bit</li>
<li>Processor: Intel(R) Core(TM) i7-3720QM CPU @ 2.60GHz (8 CPUs)</li>
<li>Device: Intel(R) HD Graphics 4000</li>
<li>Feature Levels: <code>11_0</code>, <code>10_1</code>, <code>10_0</code>, <code>9_3</code>, <code>9_2</code>, <code>9_1</code></li>
</ul>
<h5>古い Windows 環境(問題のあったマシン)</h5>
<ul>
<li>Operating System: Windows 10 Pro</li>
<li>Processor: Intel(R) Core(TM)2 Duo CPU E7500 @ 2.93GHz (2 CPUs)</li>
<li>Device: Intel(R) G41 Express Chipset</li>
<li>Feature Levels: <code>10_0</code>, <code>9_1</code></li>
</ul>
<blockquote>
<p><strong>NOTE: 母国語もまた苦痛</strong><br />
僕は、 <strong>定数バッファー</strong> よりも <strong>constant buffer</strong> という表記のほうが色々と考えなくて済むので好きなのですが、 MSDN の日本語ドキュメントでは前者が使われているようでしたので今回はそちらに従いました。
似たような話で、 depth stencil buffer を「深度ステンシルバッファー」と書くべきか「デプスステンシルバッファー」と書くべきか(あるいはそのままの英語表記にするか)よく迷います。
これに加えて古い慣例で、最後の長音を伸ばすか伸ばさないかなんて些細な取り決めもありますよね…。
可能なかぎり表記揺れのない文章を心がけたいものですが、英語で読み書きするときとは違うストレスを、こういった技術用語の和訳によって感じます。</p>
</blockquote>
<h2>問題のソースコードとその原因</h2>
<p>次のソースコードに問題がありました:</p>
<div class="highlight"><pre><span></span><span class="k">constexpr</span> <span class="n">D3D11_MAP</span> <span class="n">mapType</span> <span class="o">=</span> <span class="n">D3D11_MAP_WRITE_NO_OVERWRITE</span><span class="p">;</span>
<span class="n">D3D11_MAPPED_SUBRESOURCE</span> <span class="n">mappedResource</span><span class="p">;</span>
<span class="k">auto</span> <span class="n">hr</span> <span class="o">=</span> <span class="n">deviceContext</span><span class="o">-></span><span class="n">Map</span><span class="p">(</span><span class="n">buffer</span><span class="p">.</span><span class="n">Get</span><span class="p">(),</span> <span class="mi">0</span><span class="p">,</span> <span class="n">mapType</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&</span><span class="n">mappedResource</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">FAILED</span><span class="p">(</span><span class="n">hr</span><span class="p">))</span> <span class="p">{</span>
<span class="c1">// FUS RO DAH!</span>
<span class="p">}</span>
</pre></div>
<p>ソースコード中の <code>buffer</code> は <code>D3D11_USAGE_DYNAMIC</code>, <code>D3D11_CPU_ACCESS_WRITE</code> そして <code>D3D11_BIND_CONSTANT_BUFFER</code> で作成した、いわゆる動的な定数バッファーオブジェクトです。
<a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ff476457.aspx">ID3D11DeviceContext::Map</a> を呼び出した時に、エラーが返ってきます。
返ってきたハンドルの値は <code>E_INVALIDARG</code> で、このエラー内容は "One or more arguments are invalid" でした。</p>
<p>このエラーの原因は、定数バッファーを Map するときに <code>D3D11_MAP_WRITE_NO_OVERWRITE</code> を指定していたことでした。
試しに <code>D3D11_MAP_WRITE_NO_OVERWRITE</code> の代わりに <code>D3D11_MAP_WRITE_DISCARD</code> を指定したところ問題なく動きました。</p>
<h2>特定の環境でエラーが出る理由</h2>
<p>では新しいマシンで使えて、古いマシンでは使えなかったのは、どうしてでしょうか?</p>
<p>まず、<code>D3D11_MAP_WRITE_NO_OVERWRITE</code> は頂点バッファーとインデックスバッファーにしか利用できず、動的な定数バッファーを Map するときには使用できません。
ただし Windows 8 以降で利用可能な Direct3D 11.1 ランタイムでは、グラフィックスデバイスによって定数バッファーでも <code>D3D11_MAP_WRITE_NO_OVERWRITE</code> が使えるようになったそうです。</p>
<p>グラフィックスデバイスが動的な定数バッファーの <code>D3D11_MAP_WRITE_NO_OVERWRITE</code> に対応しているかどうかは、 <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ff476497.aspx">ID3D11Device::CheckFeatureSupport</a> で調べられます。ドライバーが対応している場合は <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/hh404457.aspx">D3D11_FEATURE_DATA_D3D11_OPTIONS::MapNoOverwriteOnDynamicConstantBuffer</a> の値が <code>TRUE</code> になります。</p>
<div class="highlight"><pre><span></span><span class="n">D3D11_FEATURE_DATA_D3D11_OPTIONS</span> <span class="n">options</span><span class="p">;</span>
<span class="k">auto</span> <span class="n">hr</span> <span class="o">=</span> <span class="n">device</span><span class="o">-></span><span class="n">CheckFeatureSupport</span><span class="p">(</span>
<span class="n">D3D11_FEATURE_D3D11_OPTIONS</span><span class="p">,</span>
<span class="o">&</span><span class="n">options</span><span class="p">,</span>
<span class="k">sizeof</span><span class="p">(</span><span class="n">D3D11_FEATURE_DATA_D3D11_OPTIONS</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="n">FAILED</span><span class="p">(</span><span class="n">hr</span><span class="p">))</span> <span class="p">{</span>
<span class="c1">// FUS RO DAH!</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">options</span><span class="p">.</span><span class="n">MapNoOverwriteOnDynamicConstantBuffer</span> <span class="o">==</span> <span class="n">TRUE</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// OK</span>
<span class="p">}</span>
</pre></div>
<p>これが <code>FALSE</code> の場合は、ドライバーが対応していないので上述したように Map 時に <code>E_INVALIDARG</code> の値が返ります。</p>
<p>また、 Feature level が 9.1, 9.2, 9.3 のグラフィックスデバイスでは、この <code>MapNoOverwriteOnDynamicConstantBuffer</code> が常に <code>TRUE</code> になります。
これは <code>D3D11_MAP_WRITE_NO_OVERWRITE</code> の動作をランタイム側でエミュレートするからです。</p>
<p>この問題の解決策を 3 つ紹介します<sup id="fnref:2"><a class="footnote-ref" href="#fn:2" rel="footnote">2</a></sup>。</p>
<h2>解決策 1: D3D11_MAP_WRITE_DISCARD を使う</h2>
<p>1 つ目は、動的な定数バッファーを Map するときには <code>D3D11_MAP_WRITE_NO_OVERWRITE</code> の代わりに <code>D3D11_MAP_WRITE_DISCARD</code> を使うことです。</p>
<div class="highlight"><pre><span></span><span class="k">constexpr</span> <span class="n">D3D11_MAP</span> <span class="n">mapType</span> <span class="o">=</span> <span class="n">D3D11_MAP_WRITE_DISCARD</span><span class="p">;</span>
<span class="n">D3D11_MAPPED_SUBRESOURCE</span> <span class="n">mappedResource</span><span class="p">;</span>
<span class="k">auto</span> <span class="n">hr</span> <span class="o">=</span> <span class="n">deviceContext</span><span class="o">-></span><span class="n">Map</span><span class="p">(</span><span class="n">buffer</span><span class="p">.</span><span class="n">Get</span><span class="p">(),</span> <span class="mi">0</span><span class="p">,</span> <span class="n">mapType</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&</span><span class="n">mappedResource</span><span class="p">);</span>
</pre></div>
<p>これはとてもうまく動きます。</p>
<h2>解決策 2: Feature level を下げたグラフィックスデバイスを作成する</h2>
<p>2 つ目は、 Feature level を 9.1, 9.2 または 9.3 に落としてから <code>ID3D11Device</code> オブジェクトを作成する方法です。</p>
<div class="highlight"><pre><span></span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">D3D_FEATURE_LEVEL</span><span class="o">></span> <span class="n">featureLevels</span> <span class="o">=</span> <span class="p">{</span>
<span class="n">D3D_FEATURE_LEVEL_9_3</span><span class="p">,</span>
<span class="n">D3D_FEATURE_LEVEL_9_2</span><span class="p">,</span>
<span class="n">D3D_FEATURE_LEVEL_9_1</span><span class="p">,</span>
<span class="p">};</span>
<span class="k">auto</span> <span class="n">hr</span> <span class="o">=</span> <span class="n">D3D11CreateDevice</span><span class="p">(</span>
<span class="n">adapter</span><span class="p">,</span>
<span class="n">driverType</span><span class="p">,</span>
<span class="k">nullptr</span><span class="p">,</span>
<span class="n">createDeviceFlags</span><span class="p">,</span>
<span class="n">featureLevels</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span>
<span class="k">static_cast</span><span class="o"><</span><span class="n">UINT</span><span class="o">></span><span class="p">(</span><span class="n">featureLevels</span><span class="p">.</span><span class="n">size</span><span class="p">()),</span>
<span class="n">D3D11_SDK_VERSION</span><span class="p">,</span>
<span class="o">&</span><span class="n">device</span><span class="p">,</span>
<span class="o">&</span><span class="n">featureLevel</span><span class="p">,</span>
<span class="o">&</span><span class="n">deviceContext</span><span class="p">);</span>
</pre></div>
<p>ただし、これはあまりオススメできる方法ではありません。
Feature level が低いグラフィックスデバイスでは、利用できる機能が制限され、多くの制約がついてきます。
例えば、頂点シェーダーをコンパイルするときに、コンパイラーターゲットを <code>vs_4_0_level_9_1</code> または <code>vs_4_0_level_9_3</code> とする必要があります <sup id="fnref:3"><a class="footnote-ref" href="#fn:3" rel="footnote">3</a></sup>。</p>
<p>事前にコンパイルしておいたシェーダーバイナリをアプリケーションで利用する場合、Feature level に沿ってシェーダーを複数用意するのは大変そうです。
試しに <code>D3D_FEATURE_LEVEL_9_3</code> のデバイスを作り、 <code>vs_4_0_level_9_1</code> ではなく通常の <code>vs_4_0</code> でコンパイルしたシェーダーバイナリを使って <code>ID3D11Device::CreateVertexShader()</code> を実行すると、エラー <code>E_INVALIDARG</code> ("One or more arguments are invalid.") が返ってきました。</p>
<h2>解決策 3: 動作環境を引き上げる</h2>
<p>3 つ目の解決策は、ゲームの動作環境を新しいハードウェアのみサポートすることです。これはとてもうまくいきます。
例えば、動作環境を「Feature level は 11.0 以降、ドライバーモデルは WDDM 1.1 以降、OS は Windows 10 以降」のように引き上げると、ゲームエンジン側での対応が色々と軽減されます。</p>
<p>Steam が公開しているハードウェアとソフトウェアの調査報告 <a href="http://store.steampowered.com/hwsurvey/videocard/">Steam Hardware & Software Survey</a> を見ると、Steam ユーザーの 70 % が DirectX 12 の Feature level に対応した GPU を使っているそうです (2016 年 11 月現在)。古い環境でもゲームが動作するのは魅力的ですが、もし開発リソースが制限されている場合 30% のゲームプレイヤーをサポートするかどうかは検討したほうがよさそうです。</p>
<blockquote>
<p><strong>NOTE:</strong><br />
Steam Hardware & Software Survey に記載されている "DirectX 12 GPUs" や "DirectX 11 GPUs" は、 Feature level(s) ごとに分けているようです。つまり "DirectX 11 GPUs" に分類された GPU であれば Feature level は 11.0 以上になります。もちろん GPU が対応している Feature level が 11.0 であっても、高レベルなドライバーやソフトウェアのほうで対応していれば DirectX 12 のランタイムは動くので DirectX 12 の API も動きます。ドライバー開発者・ベンダーのみなさまに感謝を。</p>
</blockquote>
<h2>(補足) CheckFeatureSupport の実行結果</h2>
<p>今回は次のようなコードで、 GPU が対応している Feature level のグラフィックスデバイスを作成しました。</p>
<div class="highlight"><pre><span></span><span class="k">constexpr</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">D3D_FEATURE_LEVEL</span><span class="o">></span> <span class="n">featureLevels</span> <span class="o">=</span> <span class="p">{</span>
<span class="n">D3D_FEATURE_LEVEL_11_1</span><span class="p">,</span>
<span class="n">D3D_FEATURE_LEVEL_11_0</span><span class="p">,</span>
<span class="n">D3D_FEATURE_LEVEL_10_1</span><span class="p">,</span>
<span class="n">D3D_FEATURE_LEVEL_10_0</span><span class="p">,</span>
<span class="n">D3D_FEATURE_LEVEL_9_3</span><span class="p">,</span>
<span class="n">D3D_FEATURE_LEVEL_9_2</span><span class="p">,</span>
<span class="n">D3D_FEATURE_LEVEL_9_1</span><span class="p">,</span>
<span class="p">};</span>
<span class="k">auto</span> <span class="n">hr</span> <span class="o">=</span> <span class="n">D3D11CreateDevice</span><span class="p">(</span>
<span class="n">adapter</span><span class="p">,</span>
<span class="n">driverType</span><span class="p">,</span>
<span class="k">nullptr</span><span class="p">,</span>
<span class="n">createDeviceFlags</span><span class="p">,</span>
<span class="n">featureLevels</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span>
<span class="k">static_cast</span><span class="o"><</span><span class="n">UINT</span><span class="o">></span><span class="p">(</span><span class="n">featureLevels</span><span class="p">.</span><span class="n">size</span><span class="p">()),</span>
<span class="n">D3D11_SDK_VERSION</span><span class="p">,</span>
<span class="o">&</span><span class="n">device</span><span class="p">,</span>
<span class="o">&</span><span class="n">featureLevel</span><span class="p">,</span>
<span class="o">&</span><span class="n">deviceContext</span><span class="p">);</span>
</pre></div>
<p>上述した新しいマシンと古いマシンではそれぞれ <code>D3D_FEATURE_LEVEL_11_0</code> と <code>D3D_FEATURE_LEVEL_10_0</code> のデバイスが作成されました。
また試しに、新しいマシンのほうで <code>D3D_FEATURE_LEVEL_10_1</code> を指定してデバイスを作ってみました。
実際にこれらの各デバイスを作成して CheckFeatureSupport した結果を以下に載せます。</p>
<table>
<thead>
<tr>
<th align="left"></th>
<th align="left">新しいマシン (11_0)</th>
<th align="left">新しいマシン (10_1)</th>
<th align="left">古いマシン (10_0)</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">OutputMergerLogicOp</td>
<td align="left">FALSE</td>
<td align="left">FALSE</td>
<td align="left">FALSE</td>
</tr>
<tr>
<td align="left">UAVOnlyRenderingForcedSampleCount</td>
<td align="left">TRUE</td>
<td align="left">FALSE</td>
<td align="left">FALSE</td>
</tr>
<tr>
<td align="left">DiscardAPIsSeenByDriver</td>
<td align="left">TRUE</td>
<td align="left">TRUE</td>
<td align="left">FALSE</td>
</tr>
<tr>
<td align="left">FlagsForUpdateAndCopySeenByDriver</td>
<td align="left">TRUE</td>
<td align="left">TRUE</td>
<td align="left">FALSE</td>
</tr>
<tr>
<td align="left">ClearView</td>
<td align="left">TRUE</td>
<td align="left">TRUE</td>
<td align="left">FALSE</td>
</tr>
<tr>
<td align="left">CopyWithOverlap</td>
<td align="left">TRUE</td>
<td align="left">TRUE</td>
<td align="left">FALSE</td>
</tr>
<tr>
<td align="left">ConstantBufferPartialUpdate</td>
<td align="left">TRUE</td>
<td align="left">TRUE</td>
<td align="left">FALSE</td>
</tr>
<tr>
<td align="left">ConstantBufferOffsetting</td>
<td align="left">TRUE</td>
<td align="left">TRUE</td>
<td align="left">FALSE</td>
</tr>
<tr>
<td align="left">MapNoOverwriteOnDynamicConstantBuffer</td>
<td align="left">TRUE</td>
<td align="left">TRUE</td>
<td align="left">FALSE</td>
</tr>
<tr>
<td align="left">MapNoOverwriteOnDynamicBufferSRV</td>
<td align="left">TRUE</td>
<td align="left">TRUE</td>
<td align="left">FALSE</td>
</tr>
<tr>
<td align="left">MultisampleRTVWithForcedSampleCountOne</td>
<td align="left">TRUE</td>
<td align="left">TRUE</td>
<td align="left">FALSE</td>
</tr>
<tr>
<td align="left">SAD4ShaderInstructions</td>
<td align="left">TRUE</td>
<td align="left">FALSE</td>
<td align="left">FALSE</td>
</tr>
<tr>
<td align="left">ExtendedDoublesShaderInstructions</td>
<td align="left">TRUE</td>
<td align="left">FALSE</td>
<td align="left">FALSE</td>
</tr>
<tr>
<td align="left">ExtendedResourceSharing</td>
<td align="left">TRUE</td>
<td align="left">TRUE</td>
<td align="left">FALSE</td>
</tr>
</tbody>
</table>
<h2>参考文献</h2>
<ul>
<li><a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ff476457.aspx">ID3D11DeviceContext::Map method</a></li>
<li><a href="https://msdn.microsoft.com/en-us/library/windows/desktop/hh404457.aspx">D3D11_FEATURE_DATA_D3D11_OPTIONS structure</a></li>
</ul>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>実行時にバッファの内容を何度も CPU 側から書き換えるという意味で <strong>動的な (dynamic)</strong> という言葉を使っています。対して、バッファの初期化時 (<code>ID3D11Device::CreateBuffer</code> を呼び出したとき) にのみ内容を更新するものを <strong>静的な(immutable/static)</strong> とここではしています。 <a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:2">
<p>ここでは省略していますが、もとより、動的な定数バッファーを Map するときに <code>D3D11_MAP_WRITE_DISCARD</code> の代わりに <code>D3D11_MAP_WRITE_NO_OVERWRITE</code> を必ずしも使う必要があるのかどうかは考える余地があります。また、アプリケーション上でシビアなバッファーの扱いが必要な場合は DirectX 12 を使ったほうがいいでしょう。 <a class="footnote-backref" href="#fnref:2" rev="footnote" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:3">
<p><a href="https://msdn.microsoft.com/en-us/library/ff476150(v=vs.85).aspx#ID3D11Device_CreateVertexShader">ID3D11Device::CreateVertexShader</a> を参照。 <a class="footnote-backref" href="#fnref:3" rev="footnote" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
</ol>
</div>G-Buffer の深度値からワールド空間の位置を復元した秋 20162016-10-07T10:54:00+09:00mogemimitag:enginetrouble.net,2016-10-07:2016/10/reconstructing-world-position-from-depth-2016.html<p><a href="/2013/09/summer-vacation-2013.html">2年前の秋に遅延シェーディングのお話</a>をしましたが、あらためて読み返してみたところ、ビュー空間の深度から求めたジオメトリの位置がおかしいことに気づきました<sup id="fnref:1"><a class="footnote-ref" href="#fn:1" rel="footnote">1</a></sup>。
今日は、今秋にふさわしい深度の再構築方法 (reconstructing position from depth) について見直していきます。</p>
<h2>前回のシェーダー(悪い例)</h2>
<p>前回の日記では G-Buffer にビュー空間の深度値を書き込み、必要に応じて深度から
ライティングパスでポイントライトなどを実装するためには、ワールド空間(またはビュー空間)のジオメトリの位置が必要です。
これを G-Buffer の深度値から求めるために次のようにしていました:</p>
<div class="highlight"><pre><span></span><span class="c1">// The following code is bad example.</span>
<span class="k">vec3</span> <span class="n">DepthToPosition</span><span class="p">(</span><span class="k">in</span> <span class="k">vec2</span> <span class="n">textureCoord</span><span class="p">,</span> <span class="k">in</span> <span class="k">float</span> <span class="n">depth</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">vec4</span> <span class="n">projectedPosition</span> <span class="o">=</span> <span class="k">vec4</span><span class="p">(</span><span class="n">textureCoord</span><span class="p">.</span><span class="n">xy</span> <span class="o">*</span> <span class="k">vec2</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="o">-</span><span class="mi">2</span><span class="p">)</span> <span class="o">+</span> <span class="k">vec2</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
<span class="k">vec3</span> <span class="n">viewPosition</span> <span class="o">=</span> <span class="p">(</span><span class="n">projectionToView</span> <span class="o">*</span> <span class="n">projectedPosition</span><span class="p">).</span><span class="n">xyz</span><span class="p">;</span>
<span class="k">vec3</span> <span class="n">viewRay</span> <span class="o">=</span> <span class="k">vec3</span><span class="p">(</span><span class="n">viewPosition</span><span class="p">.</span><span class="n">xy</span> <span class="o">/</span> <span class="n">projectedPosition</span><span class="p">.</span><span class="n">z</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span> <span class="c1">// maybe bug or typo</span>
<span class="k">return</span> <span class="n">viewRay</span> <span class="o">*</span> <span class="n">depth</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">void</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">float</span> <span class="n">viewDepth</span> <span class="o">=</span> <span class="n">DecodeDepth</span><span class="p">(</span><span class="n">texture</span><span class="p">(</span><span class="n">tex3_DepthSampler</span><span class="p">,</span> <span class="n">In</span><span class="p">.</span><span class="n">TextureCoord</span><span class="p">.</span><span class="n">xy</span><span class="p">).</span><span class="n">x</span><span class="p">,</span> <span class="n">FarClip</span><span class="p">);</span>
<span class="k">vec3</span> <span class="n">viewPosition</span> <span class="o">=</span> <span class="n">DepthToPosition</span><span class="p">(</span><span class="n">In</span><span class="p">.</span><span class="n">TextureCoord</span><span class="p">.</span><span class="n">xy</span><span class="p">,</span> <span class="n">viewDepth</span><span class="p">);</span>
<span class="k">vec3</span> <span class="n">worldPosition</span> <span class="o">=</span> <span class="p">(</span><span class="n">matrices</span><span class="p">.</span><span class="n">ViewToWorld</span> <span class="o">*</span> <span class="k">vec4</span><span class="p">(</span><span class="n">viewPosition</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">)).</span><span class="n">xyz</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>上記の <code>DepthToPosition()</code> の実装を見てみると、 <code>projectedPosition.z</code> で割っているところがあります。
ところが <code>projectedPosition</code> の z 値は <code>0.0</code> です。
常にゼロ除算をしていて、どうみてもジオメトリの位置を求められそうにありません。
書いた当時にどうやって式を導出していたのかわからなくなったので、2 年ぶりに G-Buffer のレイアウトも含めて遅延シェーディングを考え直してみます。</p>
<h2>G-Buffer</h2>
<p>今年の G-Buffer のレイアウトは次のようにしました。</p>
<p><img alt="" src="https://enginetrouble.net/images/2016/10/g-buffer-layout.png" /><br />
<em>(図 1) G-Buffer のレイアウト</em></p>
<p>前回の日記とほとんど同じレイアウトです。
アルベドは RGB のみ書き込み、RT0 の A 成分は今回未使用です。法線は、ワールド空間の法線を正規化したものを格納しています。
一昨年と異なるのは RT1 の A 成分に Stencil を書き込んでいるところです。
また RT2 に書き込んだ深度値は、ビュー空間の深度ではなく射影変換後の深度値を書き込んでいます。</p>
<p>このページの後半に実際に書き込んだ G-Buffer のアルベド・法線・深度・ステンシルの各スクリーンショット (図 3) を載せています。</p>
<blockquote>
<p><strong>NOTE:</strong><br />
RT0 から RT2 のレンダーターゲットとは別に、デプスステンシルバッファーも利用しています。デプスステンシルバッファーのフォーマットは D24S8 です。今回はこのデプスステンシルバッファーの代わりに、別途レンダーターゲットに書き込んだ深度とステンシル値をその後のライティングパスやポストエフェクトパスで使っています。もちろんデプスステンシルバッファーを利用することでより帯域を節約できます。</p>
</blockquote>
<h2>定数バッファー</h2>
<p>遅延シェーディングのレンダリングパイプラインで共通して使う定数バッファーを先に紹介します。</p>
<div class="highlight"><pre><span></span><span class="k">uniform</span> <span class="n">DeferredShaderParameters</span> <span class="p">{</span>
<span class="k">mat4x4</span> <span class="n">ViewProjection</span><span class="p">;</span>
<span class="k">mat4x4</span> <span class="n">InverseView</span><span class="p">;</span>
<span class="k">mat4x4</span> <span class="n">InverseViewProjection</span><span class="p">;</span>
<span class="k">mat4x4</span> <span class="n">TransposedInverseProjection</span><span class="p">;</span>
<span class="p">}</span> <span class="n">matrices</span><span class="p">;</span>
</pre></div>
<p>ビュー座標変換行列 <code>View</code> と射影変換行列 <code>Projection</code> とするとそれぞれ次のようにして求められます。</p>
<div class="highlight"><pre><span></span><span class="k">mat4x4</span> <span class="n">ViewProjection</span> <span class="o">=</span> <span class="n">Projection</span> <span class="o">*</span> <span class="n">View</span><span class="p">;</span>
<span class="k">mat4x4</span> <span class="n">InverseView</span> <span class="o">=</span> <span class="n">invert</span><span class="p">(</span><span class="n">View</span><span class="p">);</span>
<span class="k">mat4x4</span> <span class="n">InverseViewProjection</span> <span class="o">=</span> <span class="n">invert</span><span class="p">(</span><span class="n">Projection</span> <span class="o">*</span> <span class="n">View</span><span class="p">);</span>
<span class="k">mat4x4</span> <span class="n">TransposedInverseProjection</span> <span class="o">=</span> <span class="n">transpose</span><span class="p">(</span><span class="n">invert</span><span class="p">(</span><span class="n">Projection</span><span class="p">));</span>
</pre></div>
<p>これらの行列をピクセルごとに計算するのはとても大変<sup id="fnref:2"><a class="footnote-ref" href="#fn:2" rel="footnote">2</a></sup>なので、事前に CPU 側で計算しておき定数バッファーを経由して各シェーダーに渡すことにします。</p>
<h2>法線のエンコードとデコード</h2>
<p>法線のエンコードとデコードは前回の日記で紹介したものと同じです。
正規化されたワールド空間の法線を G-Buffer に格納します。</p>
<div class="highlight"><pre><span></span><span class="k">vec3</span> <span class="n">EncodeNormal</span><span class="p">(</span><span class="k">in</span> <span class="k">vec3</span> <span class="n">normal</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">normalize</span><span class="p">(</span><span class="n">normal</span><span class="p">)</span> <span class="o">*</span> <span class="mf">0.5</span> <span class="o">+</span> <span class="mf">0.5</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">vec3</span> <span class="n">DecodeNormal</span><span class="p">(</span><span class="k">in</span> <span class="k">vec3</span> <span class="n">normal</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">normal</span> <span class="o">*</span> <span class="mf">2.0</span> <span class="o">-</span> <span class="mf">1.0</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<h2>深度を格納する</h2>
<h3>G-Buffer を作成するパスの頂点シェーダー</h3>
<p>まず頂点シェーダーのコードを載せます。</p>
<div class="highlight"><pre><span></span><span class="n">layout</span><span class="p">(</span><span class="n">location</span> <span class="o">=</span> <span class="mo">0</span><span class="p">)</span> <span class="k">in</span> <span class="k">vec3</span> <span class="n">Position</span><span class="p">;</span>
<span class="n">layout</span><span class="p">(</span><span class="n">location</span> <span class="o">=</span> <span class="mi">1</span><span class="p">)</span> <span class="k">in</span> <span class="k">vec3</span> <span class="n">Normal</span><span class="p">;</span>
<span class="n">layout</span><span class="p">(</span><span class="n">location</span> <span class="o">=</span> <span class="mi">2</span><span class="p">)</span> <span class="k">in</span> <span class="k">vec2</span> <span class="n">TextureCoord</span><span class="p">;</span>
<span class="k">uniform</span> <span class="n">InstancedBlock</span> <span class="p">{</span>
<span class="n">mat4</span> <span class="n">LocalToWorld</span><span class="p">;</span>
<span class="p">}</span> <span class="n">instance</span><span class="p">;</span>
<span class="k">out</span> <span class="n">VertexData</span> <span class="p">{</span>
<span class="n">smooth</span> <span class="k">vec3</span> <span class="n">WolrdNormal</span><span class="p">;</span>
<span class="n">smooth</span> <span class="k">vec2</span> <span class="n">TextureCoord</span><span class="p">;</span>
<span class="k">vec2</span> <span class="n">DepthZW</span><span class="p">;</span>
<span class="p">}</span> <span class="n">Out</span><span class="p">;</span>
<span class="k">void</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">vec4</span> <span class="n">worldPosition</span> <span class="o">=</span> <span class="n">instance</span><span class="p">.</span><span class="n">LocalToWorld</span> <span class="o">*</span> <span class="n">Position</span><span class="p">;</span>
<span class="n">gl_Position</span> <span class="o">=</span> <span class="n">matrices</span><span class="p">.</span><span class="n">ViewProjection</span> <span class="o">*</span> <span class="n">worldPosition</span><span class="p">;</span>
<span class="n">Out</span><span class="p">.</span><span class="n">WolrdNormal</span> <span class="o">=</span> <span class="p">(</span><span class="k">mat3x3</span><span class="p">(</span><span class="n">instance</span><span class="p">.</span><span class="n">LocalToWorld</span><span class="p">)</span> <span class="o">*</span> <span class="n">Normal</span><span class="p">.</span><span class="n">xyz</span><span class="p">);</span>
<span class="n">Out</span><span class="p">.</span><span class="n">TextureCoord</span> <span class="o">=</span> <span class="n">TextureCoord</span><span class="p">.</span><span class="n">xy</span><span class="p">;</span>
<span class="n">Out</span><span class="p">.</span><span class="n">DepthZW</span> <span class="o">=</span> <span class="n">gl_Position</span><span class="p">.</span><span class="n">zw</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>オブジェクトの頂点シェーダーでは特に難しいことはせず、通常のローカル空間(またはオブジェクト空間<sup id="fnref:2"><a class="footnote-ref" href="#fn:2" rel="footnote">2</a></sup>)から射影空間へ変換を行います。</p>
<div class="highlight"><pre><span></span><span class="k">vec4</span> <span class="n">worldPosition</span> <span class="o">=</span> <span class="n">instance</span><span class="p">.</span><span class="n">LocalToWorld</span> <span class="o">*</span> <span class="n">Position</span><span class="p">;</span>
<span class="n">gl_Position</span> <span class="o">=</span> <span class="n">matrices</span><span class="p">.</span><span class="n">ViewProjection</span> <span class="o">*</span> <span class="n">worldPosition</span><span class="p">;</span>
</pre></div>
<p>このとき、射影変換後のオブジェクトの位置 <code>gl_Position</code> の z と w をピクセルシェーダーに出力として渡します。</p>
<div class="highlight"><pre><span></span><span class="n">Out</span><span class="p">.</span><span class="n">DepthZW</span> <span class="o">=</span> <span class="n">gl_Position</span><span class="p">.</span><span class="n">zw</span><span class="p">;</span>
</pre></div>
<h3>G-Buffer を作成するパスのピクセルシェーダー</h3>
<p>頂点シェーダーの出力を元に、次のピクセルシェーダーで G-Buffer を作ります。</p>
<div class="highlight"><pre><span></span><span class="k">in</span> <span class="n">VertexData</span> <span class="p">{</span>
<span class="n">smooth</span> <span class="k">vec3</span> <span class="n">WolrdNormal</span><span class="p">;</span>
<span class="n">smooth</span> <span class="k">vec2</span> <span class="n">TextureCoord</span><span class="p">;</span>
<span class="k">vec2</span> <span class="n">DepthZW</span><span class="p">;</span>
<span class="p">}</span> <span class="n">In</span><span class="p">;</span>
<span class="c1">// {xyz_} = Diffuse Albedo RGB (R8G8B8)</span>
<span class="c1">// {___w} = Unused (A8)</span>
<span class="n">layout</span><span class="p">(</span><span class="n">location</span> <span class="o">=</span> <span class="mo">0</span><span class="p">)</span> <span class="k">out</span> <span class="k">vec4</span> <span class="n">AlbedoColorOut</span><span class="p">;</span>
<span class="c1">// {xyz_} = World-space normal (R10G10B10)</span>
<span class="c1">// {___w} = Stencil (A2)</span>
<span class="n">layout</span><span class="p">(</span><span class="n">location</span> <span class="o">=</span> <span class="mi">1</span><span class="p">)</span> <span class="k">out</span> <span class="k">vec4</span> <span class="n">NormalStencilOut</span><span class="p">;</span>
<span class="c1">// {x___} = Depth represented by the formula "z/w" (FP32)</span>
<span class="c1">// {_yzw} = Unused</span>
<span class="n">layout</span><span class="p">(</span><span class="n">location</span> <span class="o">=</span> <span class="mi">2</span><span class="p">)</span> <span class="k">out</span> <span class="k">vec4</span> <span class="n">DepthOut</span><span class="p">;</span>
<span class="k">uniform</span> <span class="k">sampler2D</span> <span class="n">tex0_AlbedoSampler</span><span class="p">;</span>
<span class="k">void</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">vec4</span> <span class="n">textureColor</span> <span class="o">=</span> <span class="n">texture</span><span class="p">(</span><span class="n">tex0_AlbedoSampler</span><span class="p">,</span> <span class="n">In</span><span class="p">.</span><span class="n">TextureCoord</span><span class="p">.</span><span class="n">xy</span><span class="p">);</span>
<span class="k">vec3</span> <span class="n">normal</span> <span class="o">=</span> <span class="n">EncodeNormal</span><span class="p">(</span><span class="n">In</span><span class="p">.</span><span class="n">WolrdNormal</span><span class="p">.</span><span class="n">xyz</span><span class="p">);</span>
<span class="n">AlbedoColorOut</span> <span class="o">=</span> <span class="k">vec4</span><span class="p">(</span><span class="n">textureColor</span><span class="p">.</span><span class="n">rgb</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
<span class="n">NormalStencilOut</span> <span class="o">=</span> <span class="k">vec4</span><span class="p">(</span><span class="n">normal</span><span class="p">.</span><span class="n">xyz</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
<span class="n">DepthOut</span> <span class="o">=</span> <span class="k">vec4</span><span class="p">(</span><span class="n">In</span><span class="p">.</span><span class="n">DepthZW</span><span class="p">.</span><span class="n">x</span> <span class="o">/</span> <span class="n">In</span><span class="p">.</span><span class="n">DepthZW</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>深度は、次のように頂点シェーダーで計算した z を w で割ったものを書き出します。</p>
<div class="highlight"><pre><span></span><span class="n">DepthOut</span> <span class="o">=</span> <span class="k">vec4</span><span class="p">(</span><span class="n">In</span><span class="p">.</span><span class="n">DepthZW</span><span class="p">.</span><span class="n">x</span> <span class="o">/</span> <span class="n">In</span><span class="p">.</span><span class="n">DepthZW</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
</pre></div>
<h2>G-Buffer の深度からワールド空間の位置を求める</h2>
<p>作成した G-Buffer の深度からワールド空間のジオメトリの位置を取り出すには、ビュープロジェクション変換の逆行列を使います。
次の <code>ReconstructWorldPositionFromDepth()</code> 関数として実装しました。</p>
<div class="highlight"><pre><span></span><span class="k">vec3</span> <span class="n">ReconstructWorldPositionFromDepth</span><span class="p">(</span>
<span class="k">in</span> <span class="k">vec2</span> <span class="n">textureCoord</span><span class="p">,</span>
<span class="k">in</span> <span class="k">float</span> <span class="n">depth</span><span class="p">,</span>
<span class="k">in</span> <span class="k">mat4x4</span> <span class="n">inverseViewProjection</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">vec4</span> <span class="n">projectedPosition</span> <span class="o">=</span> <span class="k">vec4</span><span class="p">(</span>
<span class="n">textureCoord</span><span class="p">.</span><span class="n">xy</span> <span class="o">*</span> <span class="mf">2.0</span> <span class="o">-</span> <span class="k">vec2</span><span class="p">(</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">),</span> <span class="n">depth</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
<span class="k">vec4</span> <span class="n">position</span> <span class="o">=</span> <span class="n">inverseViewProjection</span> <span class="o">*</span> <span class="n">projectedPosition</span><span class="p">;</span>
<span class="k">return</span> <span class="n">position</span><span class="p">.</span><span class="n">xyz</span> <span class="o">/</span> <span class="n">position</span><span class="p">.</span><span class="n">w</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>G-Buffer の UV 空間のサンプリング位置とサンプリングした深度、変換行列の逆行列を使って求めることができます。
使い方は次のようにします。</p>
<div class="highlight"><pre><span></span><span class="k">in</span> <span class="n">QuadVertexShaderOutput</span> <span class="p">{</span>
<span class="k">vec2</span> <span class="n">TextureCoord</span><span class="p">;</span>
<span class="p">}</span> <span class="n">In</span><span class="p">;</span>
<span class="k">uniform</span> <span class="k">sampler2D</span> <span class="n">depthSampler</span><span class="p">;</span>
<span class="k">void</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">float</span> <span class="n">depth</span> <span class="o">=</span> <span class="n">texture</span><span class="p">(</span><span class="n">depthSampler</span><span class="p">,</span> <span class="n">In</span><span class="p">.</span><span class="n">TextureCoord</span><span class="p">.</span><span class="n">xy</span><span class="p">).</span><span class="n">x</span><span class="p">;</span>
<span class="k">vec3</span> <span class="n">worldPosition</span> <span class="o">=</span> <span class="n">ReconstructWorldPositionFromDepth</span><span class="p">(</span>
<span class="n">In</span><span class="p">.</span><span class="n">TextureCoord</span><span class="p">.</span><span class="n">xy</span><span class="p">,</span> <span class="n">depth</span><span class="p">,</span> <span class="n">matrices</span><span class="p">.</span><span class="n">InverseViewProjection</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<h2>ポイントライト</h2>
<p>前回の日記では、ディレクショナルライトしか使っていなかったので一切ワールド空間の座標を使っていませんでした。
今回は、ポイントライトを実装してジオメトリの位置がちゃんと復元できているか確認しました。</p>
<blockquote class="twitter-video" data-lang="en"><p lang="en" dir="ltr">WIP I'll look up how to reconstruct a world position of geometry from depth in deferred shading, instead of using FP32 position buffer <a href="https://t.co/PKGrnXomJl">pic.twitter.com/PKGrnXomJl</a></p>— mogemimi (@mogemimi) <a href="https://twitter.com/mogemimi/status/779355328743743488">September 23, 2016</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-video" data-lang="en"><p lang="en" dir="ltr">Screen space shadows using deferred rendering wip <a href="https://t.co/q6ZtaHU9BA">pic.twitter.com/q6ZtaHU9BA</a></p>— mogemimi (@mogemimi) <a href="https://twitter.com/mogemimi/status/780810458877927425">September 27, 2016</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<p><em>(図 2) ポイントライトを実装してみた動画</em></p>
<h2>ビュー空間の深度か、射影変換後の深度か</h2>
<p>前回の日記で、なぜ射影変換後の深度ではなくビュー空間の深度を G-Buffer に書き込んでいたのかというと SSAO で利用したかったからです。
SSAO の実装方法にもよりますが、僕が試してみたところ、頂点シェーダーから書き出される通常の深度値 (射影空間の z を w で割ったもの) よりもビュー空間の z 値を利用したほうが綺麗な AO を計算できました。
今回は、射影変換後の深度を G-Buffer に書き込みます。そこで SSAO の計算をするときに射影変換後の深度からビュー空間の深度へ再構築する必要があります。</p>
<blockquote>
<p><strong>NOTE:</strong><br />
これといって深度の精度などは考えず、 G-Buffer を 32-bit の浮動小数点にし、 z/w を書き込むように今回はしてみましたが、ひとえに <strong>深度 (depth)</strong> といっても G-Buffer に格納する深度は実装によって様々です。
フォーマットも 16-bit, 24-bit, 32-bit が選択できますし、対数 (logarithmic) だけでなく線形 (linear) の深度計算方法<sup id="fnref:3"><a class="footnote-ref" href="#fn:3" rel="footnote">3</a></sup>も知られています。
また、広大な世界を表現するために、できるだけ遠くにあるジオメトリの深度まで表現したいものです。参考文献に載せたページで紹介されている <strong>Reversed-Z mapping</strong> や <strong>Near-far swapped</strong> と呼ばれるテクニックがこれに該当します。最新の Unity 5.5 でも Reversed-Z が活用されているそうです。</p>
</blockquote>
<h2>G-Buffer の z/w 深度からビュー空間の深度を求める</h2>
<p>G-Buffer に書き込んだ深度から SSAO で利用するビュー空間の深度を求めます。
ワールド空間の位置を求めたときと同じ手順です。
今度はビュー空間の深度(または、位置の z 成分)を求めたいので、射影変換の逆行列を用います。</p>
<div class="highlight"><pre><span></span><span class="k">float</span> <span class="n">ReconstructViewSpaceDepthFromProjectionDepth</span><span class="p">(</span>
<span class="k">in</span> <span class="k">float</span> <span class="n">depth</span><span class="p">,</span>
<span class="k">in</span> <span class="k">vec2</span> <span class="n">textureCoord</span><span class="p">,</span>
<span class="k">in</span> <span class="k">mat4x4</span> <span class="n">inverseProjection</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">vec4</span> <span class="n">projectedPosition</span> <span class="o">=</span> <span class="k">vec4</span><span class="p">(</span>
<span class="n">textureCoord</span><span class="p">.</span><span class="n">xy</span> <span class="o">*</span> <span class="mf">2.0</span> <span class="o">-</span> <span class="k">vec2</span><span class="p">(</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">),</span> <span class="n">depth</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
<span class="k">vec4</span> <span class="n">position</span> <span class="o">=</span> <span class="n">inverseProjection</span> <span class="o">*</span> <span class="n">projectedPosition</span><span class="p">;</span>
<span class="k">vec3</span> <span class="n">viewSpacePosition</span> <span class="o">=</span> <span class="n">position</span><span class="p">.</span><span class="n">xyz</span> <span class="o">/</span> <span class="n">position</span><span class="p">.</span><span class="n">w</span><span class="p">;</span>
<span class="k">return</span> <span class="n">viewSpacePosition</span><span class="p">.</span><span class="n">z</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p><code>position</code> の <code>z</code> と <code>w</code> 成分さえわかれば良いので、次のように行列とベクトルの積を各列と行のベクトルの内積 (dot) で置き換えることもできます。
GLSL で行列の列ベクトルにアクセスしやすくするため射影変換の逆行列を転置しています。</p>
<div class="highlight"><pre><span></span><span class="k">float</span> <span class="n">ReconstructViewSpaceDepthFromProjectionDepth</span><span class="p">(</span>
<span class="k">in</span> <span class="k">float</span> <span class="n">depth</span><span class="p">,</span>
<span class="k">in</span> <span class="k">vec2</span> <span class="n">textureCoord</span><span class="p">,</span>
<span class="k">in</span> <span class="k">mat4x4</span> <span class="n">inverseProjection</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">vec4</span> <span class="n">projectedPosition</span> <span class="o">=</span> <span class="k">vec4</span><span class="p">(</span>
<span class="n">textureCoord</span><span class="p">.</span><span class="n">xy</span> <span class="o">*</span> <span class="mf">2.0</span> <span class="o">-</span> <span class="k">vec2</span><span class="p">(</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">),</span> <span class="n">depth</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
<span class="k">mat4x4</span> <span class="n">transposedInverseProjection</span> <span class="o">=</span> <span class="n">transpose</span><span class="p">(</span><span class="n">inverseProjection</span><span class="p">);</span>
<span class="k">float</span> <span class="n">z</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">transposedInverseProjection</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="n">projectedPosition</span><span class="p">);</span>
<span class="k">float</span> <span class="n">w</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">transposedInverseProjection</span><span class="p">[</span><span class="mi">3</span><span class="p">],</span> <span class="n">projectedPosition</span><span class="p">);</span>
<span class="k">return</span> <span class="n">z</span> <span class="o">/</span> <span class="n">w</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>あらかじめ転置(transpose)しておいた行列を定数バッファにいれておき、関数のパラメータとして渡すようにしました。
次の関数でビュー空間の深度を求めます。</p>
<div class="highlight"><pre><span></span><span class="k">float</span> <span class="n">ReconstructViewSpaceDepthFromProjectionDepth</span><span class="p">(</span>
<span class="k">in</span> <span class="k">float</span> <span class="n">depth</span><span class="p">,</span>
<span class="k">in</span> <span class="k">vec2</span> <span class="n">textureCoord</span><span class="p">,</span>
<span class="k">in</span> <span class="k">mat4x4</span> <span class="n">transposedInverseProjection</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">vec4</span> <span class="n">projectedPosition</span> <span class="o">=</span> <span class="k">vec4</span><span class="p">(</span>
<span class="n">textureCoord</span><span class="p">.</span><span class="n">xy</span> <span class="o">*</span> <span class="mf">2.0</span> <span class="o">-</span> <span class="k">vec2</span><span class="p">(</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">),</span> <span class="n">depth</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
<span class="k">float</span> <span class="n">z</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">transposedInverseProjection</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="n">projectedPosition</span><span class="p">);</span>
<span class="k">float</span> <span class="n">w</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">transposedInverseProjection</span><span class="p">[</span><span class="mi">3</span><span class="p">],</span> <span class="n">projectedPosition</span><span class="p">);</span>
<span class="k">return</span> <span class="n">z</span> <span class="o">/</span> <span class="n">w</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>使い方は次の通りです。</p>
<div class="highlight"><pre><span></span><span class="k">uniform</span> <span class="k">sampler2D</span> <span class="n">depthSampler</span><span class="p">;</span>
<span class="k">void</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">float</span> <span class="n">depth</span> <span class="o">=</span> <span class="n">texture</span><span class="p">(</span><span class="n">depthSampler</span><span class="p">,</span> <span class="n">In</span><span class="p">.</span><span class="n">TextureCoord</span><span class="p">.</span><span class="n">xy</span><span class="p">).</span><span class="n">x</span><span class="p">;</span>
<span class="k">float</span> <span class="n">viewSpaceDepth</span> <span class="o">=</span> <span class="n">ReconstructViewSpaceDepthFromProjectionDepth</span><span class="p">(</span>
<span class="n">depth</span><span class="p">,</span> <span class="n">In</span><span class="p">.</span><span class="n">TextureCoord</span><span class="p">.</span><span class="n">xy</span><span class="p">,</span> <span class="n">matrices</span><span class="p">.</span><span class="n">TransposedInverseProjection</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<h2>G-Buffer の可視化と Linear Depth (線形な深度値)</h2>
<p>G-Buffer のスクリーンショットを図 3 に示します。</p>
<p><img alt="" src="https://enginetrouble.net/images/2016/10/10_gbuffer_albedo.png" />
<img alt="" src="https://enginetrouble.net/images/2016/10/07_gbuffer_normal.png" />
<img alt="" src="https://enginetrouble.net/images/2016/10/08_gbuffer_depth.png" />
<img alt="" src="https://enginetrouble.net/images/2016/10/09_gbuffer_stencil.png" /></p>
<p><em>(図 3) G-Buffer の内容を可視化したもの。上から (A) アルベド, (B) ワールド空間の法線, (C) 深度(実際は z/w の値を書き込んでいるが可視化するためにここでは線形な深度に変換してから表示している), (D) ステンシル</em></p>
<p>法線は正規化された 3 次元のベクトルで x, y, z の各成分は -1.0f から 1.0f の値をとります。 G-Buffer に格納する際に 0.0f から 1.0f の範囲におさまるようエンコードするため、図 3 のようにワールド空間の法線を RGB の色として可視化できました。
ところが z/w で計算される深度バッファーは浮動小数点で 0.0f から 1.0f の範囲内に収まるとはかぎりません。</p>
<p>深度バッファーを図 3 のように色の明暗で可視化するためには、 0.0f から 1.0f の範囲に深度をエンコードする必要があります。深度のエンコード方法のひとつに <strong>Linear Depth</strong> や <strong>Linearized Depth</strong> というテクニック<sup id="fnref:3"><a class="footnote-ref" href="#fn:3" rel="footnote">3</a></sup>があります。実際に図 3 の深度バッファーのスクリーンショットは、この Linear Depth を使用して撮影しています。次のシェーダーコードを用いました。</p>
<div class="highlight"><pre><span></span><span class="k">float</span> <span class="n">LinearizeDepth</span><span class="p">(</span><span class="k">float</span> <span class="n">depth</span><span class="p">,</span> <span class="k">float</span> <span class="n">near</span><span class="p">,</span> <span class="k">float</span> <span class="n">far</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="p">(</span><span class="mf">2.0</span> <span class="o">*</span> <span class="n">near</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="n">far</span> <span class="o">+</span> <span class="n">near</span> <span class="o">-</span> <span class="n">depth</span> <span class="o">*</span> <span class="p">(</span><span class="n">far</span> <span class="o">-</span> <span class="n">near</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">uniform</span> <span class="k">sampler2D</span> <span class="n">depthSampler</span><span class="p">;</span>
<span class="k">void</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">float</span> <span class="n">depth</span> <span class="o">=</span> <span class="n">texture</span><span class="p">(</span><span class="n">depthSampler</span><span class="p">,</span> <span class="n">In</span><span class="p">.</span><span class="n">TextureCoord</span><span class="p">.</span><span class="n">xy</span><span class="p">).</span><span class="n">x</span><span class="p">;</span>
<span class="k">float</span> <span class="n">linearZ</span> <span class="o">=</span> <span class="n">LinearizeDepth</span><span class="p">(</span><span class="n">depth</span><span class="p">,</span> <span class="n">NearClip</span><span class="p">,</span> <span class="n">FarClip</span><span class="p">);</span>
<span class="n">FragColor</span> <span class="o">=</span> <span class="k">vec4</span><span class="p">(</span><span class="k">vec3</span><span class="p">(</span><span class="n">linearZ</span><span class="p">),</span> <span class="mf">1.0</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<h2>スクリーンショット</h2>
<h4>SSAO (Screen Space Ambient Occlusion)</h4>
<p>今年も SSAO のお世話になっています。一昨年の日記から改良を加えており、綺麗な AO に近づいたと思います (図 4 参照) 。
サンプリング数は 1 ピクセルにつき 12 回 (= 6 個のベクトル × 左右 1 回ずつのサンプリング) です。
サンプリング半径を大きくすると、粗が目立つ欠点はありますが柔らかい自然な影になります (図 5 参照) 。
反対にサンプリング半径を小さくすると、くっきりとした影が表現できます (図 6 参照) 。
そうして深度をサンプリングして計算した AO (図 5, 6 参照) に、最終的にブラーをかけたものが図 4 になります。
深度値を使って垂直方向と水平方向にわけてバイラテラルフィルターでブラーをかけています。</p>
<p><img alt="" src="https://enginetrouble.net/images/2016/10/05_ssao.png" />
<em>(図 4) SSAO で計算した AO 影</em></p>
<p><img alt="" src="https://enginetrouble.net/images/2016/10/04_ssao_sampling1.png" />
<em>(図 5) ブラーをかける前の AO</em></p>
<p><img alt="" src="https://enginetrouble.net/images/2016/10/03_ssao_sampling2.png" />
<img alt="" src="https://enginetrouble.net/images/2016/10/02_ssao_sampling3.png" />
<em>(図 6) サンプリング半径を小さくした例。上から (A)サンプリング半径を図 5 の半分にしたもの, (B) サンプリング半径を可能なかぎり小さくしたもの</em></p>
<h4>ハードウェアインスタンシング</h4>
<p>一昨年と同じように、ハードウェアインスタンシングでたくさんのボクセルを表示しています。
一昨年は 6 面からなる正立方体の頂点列を元にインスタンシングしていましたが、今回はサーフェイス(ポリゴンを 2 つ組み合わせた正方形の面)をインスタンシングすることにしました。ワイヤーフレーム表示するとよくわかるかと思います (図 7 参照) 。 <a href="http://enginetrouble.net/2013/09/summer-vacation-2013.html">2013 年の日記</a>ではボクセルをそのままインスタンシングしたものをワイヤーフレーム表示しているのでぜひ見比べてみてください。</p>
<p><img alt="" src="https://enginetrouble.net/images/2016/10/01_wireframe.png" />
<em>(図 7) ワイヤーフレーム表示した例。ワイヤーフレームのために背面カリングを無効にしているが、実際にポリゴンで表示するときは反時計回りの頂点列に対して背面カリングをしている</em></p>
<h4>大気散乱シミュレーション</h4>
<p>背景がコーンフラワーブルーで悲しかったので、レイリー散乱とミー散乱をピクセルシェーダーで計算した大気散乱シミュレーションをしてみました。半球状のポリゴンなどは用意せずに、半球やレイの衝突などの計算はすべてスクリーンスペースのピクセルシェーダー上でやっています (図 8 参照)。
ディレクショナルライトの向きを半球状の太陽の軌道と捉えて、昼から夜までの天体を表現できます(図 9 参照)。</p>
<p>サンプル数を増やすと SSAO や FXAA の比ではないほどに重たくなるので 1/8 サイズのレンダリングターゲットで計算して、リニアサンプラーで最終的に合成しています。スクリーンスペースのシェーダーなので地面を向いているときも全画面で計算されてしまいます。半球ドームのポリゴンを用意して Z pre-pass の恩恵を受けたり、可能なら頂点シェーダーで散乱のシミュレーションをやってみてもいいかもしれません。
大気散乱シミュレーションについては機会があったら日記に書こうと思います。</p>
<p><img alt="" src="https://enginetrouble.net/images/2016/10/06_atmosphere.png" />
<em>(図 8) 背景のレンダリング。ピクセルシェーダーで大気散乱シミュレーションをしている。</em></p>
<p><img alt="" src="https://enginetrouble.net/images/2016/10/00_atmosphere_twilight.png" />
<em>(図 9) 夕暮れ時の表現。太陽の位置を変えることで昼から夜まで表現できる。</em></p>
<h4>コンポジションとポストエフェクト</h4>
<p>図 10 はディレクショナルライトでランバートライティングしたものです。これに SSAO のパスで計算しておいた AO をライティングパスの段階で付け足し (図 11 参照)、ポストエフェクトとして FXAA (図 12 参照)、Exponential Fog (図 13 参照)を順に適用し、被写界深度ブラーをかけて最終的なレンダリング結果となります(図 14 参照)。</p>
<p><img alt="" src="https://enginetrouble.net/images/2016/10/14_without_ssao.png" />
<em>(図 10) ディレクショナルライトによるライティング結果。SSAO と FXAA が無効になっている。</em></p>
<p><img alt="" src="https://enginetrouble.net/images/2016/10/13_without_fxaa.png" />
<em>(図 11) SSAO を有効にしたライティング</em></p>
<p><img alt="" src="https://enginetrouble.net/images/2016/10/12_fxaa.png" />
<em>(図 12) FXAA を有効にしたスクリーンショット</em></p>
<p><img alt="" src="https://enginetrouble.net/images/2016/10/11_exponential_fog.png" />
<em>(図 13) Exponential Fog を有効にしたスクリーンショット</em></p>
<p><img alt="" src="https://enginetrouble.net/images/2016/10/15_result.png" />
<em>(図 14) 被写界深度ブラーをかけた、最終的なレンダリング結果</em></p>
<h2>参考文献</h2>
<ul>
<li><a href="http://outerra.blogspot.jp/2012/11/maximizing-depth-buffer-range-and.html">Maximizing Depth Buffer Range and Precision</a> - 深度バッファーで表現できる範囲とその精度を最大化する考察記事です。 16 bit/24 bit でも十分な範囲の深度を格納するテクニックが考察されています。</li>
<li><a href="https://developer.nvidia.com/content/depth-precision-visualized">Depth Precision Visualized</a> - NVIDIA GameWorks のブログ記事。深度の精度を目に見えるグラフにして Reversed-Z mapping などを考察しています。</li>
</ul>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>Lee Hyukjae (@namoeye) さんから教えていただき気づきました。ありがとうございます! Thank you @namoeye for pointing out! <a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:2">
<p>Autodesk Maya だとオブジェクト空間とローカル空間は厳密に言葉の定義がされていて、この場合だとオブジェクト空間というのがふさわしいです。Houdini だとローカル空間で統一されているようです。 <a class="footnote-backref" href="#fnref:2" rev="footnote" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:3">
<p>深度バッファーを可視化するために Linear Depth を紹介している次の記事を参考にしました。 <a href="http://www.geeks3d.com/20091216/geexlab-how-to-visualize-the-depth-buffer-in-glsl/">[GeeXLab] How to Visualize the Depth Buffer in GLSL</a> <a class="footnote-backref" href="#fnref:3" rev="footnote" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
</ol>
</div>Clang の LibTooling をプログラムに組み込もうとしてリンクエラー2015-11-01T17:21:00+09:00mogemimitag:enginetrouble.net,2015-11-01:2015/11/trying-to-link-clang-libtooling-into-my-program.html<p>(この漫画は左から右に読みます。)
<img alt="(この漫画は左から右に読みます。)" src="https://enginetrouble.net/images/2015/11/trying-to-link-clang-libtooling.png" /> </p>
<p>C++ のソースコードを解析しようと思い、Clang の <strong>LibTooling</strong> を使ってみることにしました。
ところがぱっと見でわからないリンクエラーにはまり丸一日ほど費やしてしまいました。
今日は Clang の LibTooling を最小限の労力でリンクし、C++ プログラムに組みこむまでの手順をおぼえ書きとして記します。</p>
<blockquote>
<p><strong>NOTE:</strong><br />
LLVM/Clang のウェブページには、LibTooling の使い方を説明したチュートリアルページが用意してあります。</p>
<ul>
<li><a href="http://clang.llvm.org/docs/LibASTMatchersTutorial.html">Tutorial for building tools using LibTooling and LibASTMatchers</a></li>
</ul>
<p>体系的に学びたい方は、こちらのチュートリアルを参照することをおすすめします。
上記のチュートリアルでは CMake や ninja を使って、最新の Clang のソースコードを git clone するところから書かれています。
僕は、CMake や ninja は使わず、すでにビルドされた LLVM/Clang のバイナリをダウンロードし Xcode でさくっと動かすことにしました。</p>
</blockquote>
<p>今回は、Xcode 上で<sup id="fnref:1"><a class="footnote-ref" href="#fn:1" rel="footnote">1</a></sup> C++ プログラムに LibTooling をリンクして動かしてみます。手順は次の通りです。</p>
<ol>
<li>Clang の Pre-Built Binaries をダウンロード</li>
<li>ライブラリサーチパスとインクルードサーチパスを通す</li>
<li>Clang のライブラリをリンクする</li>
<li>Clang を動かすために必要なシステムライブラリをリンクする</li>
<li>LLVM/Clang の警告を無視する</li>
<li>プリプロセッサマクロの定義</li>
<li><code>-fno-exceptions</code> と <code>-fno-rtti</code> を C++ フラグに追加する <sup id="fnref:2"><a class="footnote-ref" href="#fn:2" rel="footnote">2</a></sup></li>
<li>実行時に libclang の動的ライブラリを実行ファイルから見つけられるようにする</li>
</ol>
<p>それぞれ順にかいつまんでみていきます。</p>
<h2>Clang の Pre-Built Binaries をダウンロード</h2>
<p>ビルド済み<sup id="fnref:3"><a class="footnote-ref" href="#fn:3" rel="footnote">3</a></sup>の Clang を<a href="http://llvm.org/releases/download.html">公式ページ</a>からダウンロードします。
ここでは、 <strong>LLVM 3.7.0 - Clang for Mac OS X</strong> をダウンロードし、適当なディレクトリに解凍しました<sup id="fnref:4"><a class="footnote-ref" href="#fn:4" rel="footnote">4</a></sup>。
説明のために、ディレクトリパスは <code>/path/to/clang+llvm-3.7.0/</code> とします。</p>
<h2>ライブラリサーチパスとインクルードサーチパスを通す</h2>
<p>ライブラリサーチパスとインクルードサーチパスを通すためにそれぞれ Xcode プロジェクトに設定します:</p>
<ul>
<li><em><code>Xcode > Build Setting > Header Search Paths</code></em> に <code>/path/to/clang+llvm-3.7.0/include</code> を追加</li>
<li><em><code>Xcode > Build Setting > Library Search Paths</code></em> に <code>/path/to/clang+llvm-3.7.0/lib</code> を追加</li>
</ul>
<p>Xcode を使わず g++ や clang++ を直接使う場合は、<code>-I</code> オプションや <code>-L</code> オプションに読み替えてください:</p>
<div class="highlight"><pre><span></span>-I/path/to/clang+llvm-3.7.0/include
-L/path/to/clang+llvm-3.7.0/lib
</pre></div>
<p>後述する <code>llvm-config</code> ツールを使うと、これらのコンパイルオプションを自動生成することができます。</p>
<h2>Clang のライブラリをリンクする</h2>
<p>Xcode プロジェクトに、<code>libclang.a</code> などのライブラリファイルを追加します。
<em><code>Xcode > Build Phases > Link Binary With Libraries</code></em> に必要なライブラリファイルをすべて追加します。</p>
<p>プラットフォームや Clang のバージョンごとに必要なライブラリは異なってきます。
Clang に同梱されている <code>llvm-config</code> を使うと列挙することができます。</p>
<div class="highlight"><pre><span></span>$ <span class="nb">cd</span> /path/to/clang+llvm-3.7.0/bin
$ ./llvm-config --libs
</pre></div>
<p>こんなふうにもれなく出力してくれます:</p>
<div class="highlight"><pre><span></span>libLLVMLTO.a libLLVMObjCARCOpts.a libLLVMLinker.a libLLVMBitWriter.a libLLVMIRReader.a libLLVMBPFCodeGen.a libLLVMBPFDesc.a libLLVMBPFInfo.a
...
</pre></div>
<p>g++ や clang++ を直接使う場合は、次のコマンドで必要なコンパイルオプションを自動的に出力してくれるので便利です。</p>
<div class="highlight"><pre><span></span>$ ./llvm-config --cxxflags --ldflags --libs
-I/path/to/clang+llvm-3.7.0/include -DNDEBUG -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -O3 -std<span class="o">=</span>c++11 -fvisibility-inlines-hidden -fno-exceptions -fno-rtti -fno-common -Wcast-qual
-L/path/to/clang+llvm-3.7.0/lib
-lLLVMLTO -lLLVMObjCARCOpts -lLLVMLinker -lLLVMBitWriter
...
</pre></div>
<p>ちなみに LLVM/Clang がリンクできていないと次のリンクエラーが出ます。</p>
<div class="highlight"><pre><span></span>Undefined symbols for architecture x86_64:
"llvm::cl::OptionCategory::registerCategory()", referenced from:
llvm::cl::OptionCategory::OptionCategory(char const*, char const*) in hello.o
"llvm::cl::extrahelp::extrahelp(char const*)", referenced from:
___cxx_global_var_init1 in hello.o
___cxx_global_var_init2 in hello.o
"clang::FrontendAction::FrontendAction()", referenced from:
...
"vtable for clang::tooling::ToolAction", referenced from:
clang::tooling::ToolAction::ToolAction() in hello.o
NOTE: a missing vtable usually means the first non-inline virtual member function has no definition.
"vtable for clang::tooling::FrontendActionFactory", referenced from:
clang::tooling::FrontendActionFactory::FrontendActionFactory() in hello.o
NOTE: a missing vtable usually means the first non-inline virtual member function has no definition.
</pre></div>
<h2>Clang を動かすために必要なシステムライブラリをリンクする</h2>
<p>OS X で Clang を使う場合は次のシステムライブラリが必要になります。</p>
<div class="highlight"><pre><span></span>libz
libpthread
libedit
libcurses
libm
</pre></div>
<p>他のライブラリと同様に <em><code>Xcode > Build Phases > Link Binary With Libraries</code></em> で追加します。
Clang のバージョンやプラットフォームごとによって、これまた必要なシステムライブラリは異なってくるので次のコマンドで確認できます。</p>
<div class="highlight"><pre><span></span>$ ./llvm-config --system-libs
</pre></div>
<p>こんなふうに出力してくれます:</p>
<div class="highlight"><pre><span></span>-lz -lpthread -ledit -lcurses -lm
</pre></div>
<h2>LLVM/Clang の警告を無視する</h2>
<p>さっそく LLVM/Clang をソースコードに include してみたところ、次のような警告がたくさん出てきてコンパイルできないことがあります。</p>
<div class="highlight"><pre><span></span>clang/Basic/VersionTuple.h:41:34: Declaration shadows a field of 'clang::VersionTuple'
clang/Basic/VersionTuple.h:45:34: Declaration shadows a field of 'clang::VersionTuple'
clang/Basic/VersionTuple.h:45:50: Declaration shadows a field of 'clang::VersionTuple'
llvm/ADT/iterator_range.h:35:28: Declaration shadows a field of 'iterator_range<IteratorT>'
llvm/Support/MathExtras.h:469:13: Implicit conversion loses integer precision: 'unsigned long' to 'unsigned int'
...
fatal error: too many errors emitted, stopping now [-ferror-limit=]
20 errors generated.
</pre></div>
<p>コンパイラの警告レベルを下げるか、または次のように <code>#pragma GCC diagnostic</code> で警告を部分的に無視できます。</p>
<div class="highlight"><pre><span></span><span class="cp">#pragma GCC diagnostic push</span>
<span class="cp">#pragma GCC diagnostic ignored "-Wshadow"</span>
<span class="cp">#pragma GCC diagnostic ignored "-Wsign-compare"</span>
<span class="cp">#pragma GCC diagnostic ignored "-Wconversion"</span>
<span class="cp">#include</span> <span class="cpf">"clang/Frontend/FrontendActions.h"</span><span class="cp"></span>
<span class="cp">#include</span> <span class="cpf">"clang/Tooling/CommonOptionsParser.h"</span><span class="cp"></span>
<span class="cp">#include</span> <span class="cpf">"clang/Tooling/Tooling.h"</span><span class="cp"></span>
<span class="cp">#include</span> <span class="cpf">"llvm/Support/CommandLine.h"</span><span class="cp"></span>
<span class="cp">#pragma GCC diagnostic pop</span>
</pre></div>
<p><code>#pragma GCC diagnostic</code> は GCC または Clang で有効です。
Visual Studio の場合は、代わりに <code>#pragma warning</code> を使います。</p>
<p>完全を期すため、今回 Xcode で動かした LibTooling アプリケーションのソースコードを載せます。
このコードは、<a href="http://clang.llvm.org/docs/LibTooling.html">LibTooling — Clang 3.8 documentation</a> で紹介されているサンプルコードを参考にしました。</p>
<div class="highlight"><pre><span></span><span class="cp">#pragma GCC diagnostic push</span>
<span class="cp">#pragma GCC diagnostic ignored "-Wshadow"</span>
<span class="cp">#pragma GCC diagnostic ignored "-Wsign-compare"</span>
<span class="cp">#pragma GCC diagnostic ignored "-Wconversion"</span>
<span class="cp">#include</span> <span class="cpf">"clang/Frontend/FrontendActions.h"</span><span class="cp"></span>
<span class="cp">#include</span> <span class="cpf">"clang/Tooling/CommonOptionsParser.h"</span><span class="cp"></span>
<span class="cp">#include</span> <span class="cpf">"clang/Tooling/Tooling.h"</span><span class="cp"></span>
<span class="cp">#include</span> <span class="cpf">"llvm/Support/CommandLine.h"</span><span class="cp"></span>
<span class="cp">#pragma GCC diagnostic pop</span>
<span class="k">using</span> <span class="k">namespace</span> <span class="n">clang</span><span class="o">::</span><span class="n">tooling</span><span class="p">;</span>
<span class="k">using</span> <span class="k">namespace</span> <span class="n">llvm</span><span class="p">;</span>
<span class="k">static</span> <span class="n">cl</span><span class="o">::</span><span class="n">OptionCategory</span> <span class="n">MyToolCategory</span><span class="p">(</span><span class="s">"my-tool options"</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">**</span> <span class="n">argv</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">CommonOptionsParser</span> <span class="n">options</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">,</span> <span class="n">MyToolCategory</span><span class="p">);</span>
<span class="n">ClangTool</span> <span class="n">tool</span><span class="p">(</span><span class="n">options</span><span class="p">.</span><span class="n">getCompilations</span><span class="p">(),</span> <span class="n">options</span><span class="p">.</span><span class="n">getSourcePathList</span><span class="p">());</span>
<span class="k">return</span> <span class="n">tool</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">newFrontendActionFactory</span><span class="o"><</span><span class="n">clang</span><span class="o">::</span><span class="n">SyntaxOnlyAction</span><span class="o">></span><span class="p">().</span><span class="n">get</span><span class="p">());</span>
<span class="p">}</span>
</pre></div>
<h2>プリプロセッサマクロの定義</h2>
<p>コンパイルするときに次の 2 つのプリプロセッサマクロを定義しておく必要があります。</p>
<div class="highlight"><pre><span></span>__STDC_CONSTANT_MACROS
__STDC_LIMIT_MACROS
</pre></div>
<p><em><code>Xcode > Build Settings > Preprocessor Macros</code></em> にこれらのマクロを追加してください。
定義していない場合は次のようなコンパイルエラーが出ます:</p>
<div class="highlight"><pre><span></span>llvm/Support/DataTypes.h:58:3: "Must #define __STDC_LIMIT_MACROS before #including Support/DataTypes.h"
llvm/Support/DataTypes.h:62:3: "Must #define __STDC_CONSTANT_MACROS before " "#including Support/DataTypes.h"
</pre></div>
<p>必要であれば <code>__STDC_FORMAT_MACROS</code> も定義します。</p>
<h2>-fno-exceptions と -fno-rtti を C++ フラグに追加する</h2>
<p><em><code>Xcode > Build Settings > Other C++ Flags</code></em> (OTHER_CPLUSPLUSFLAGS) に次のフラグを追加します。</p>
<div class="highlight"><pre><span></span>-fno-exceptions -fno-rtti
</pre></div>
<p>これらのフラグの指定がないと、次のような <code>typeinfo</code> に関するリンクエラーが発生します:</p>
<div class="highlight"><pre><span></span>Undefined symbols for architecture x86_64:
"typeinfo for clang::tooling::FrontendActionFactory", referenced from:
typeinfo for std::__1::unique_ptr<clang::tooling::FrontendActionFactory, std::__1::default_delete<clang::tooling::FrontendActionFactory> > clang::tooling::newFrontendActionFactory<clang::SyntaxOnlyAction>()::SimpleFrontendActionFactory in MyApp.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
</pre></div>
<p>必要な場合は <code>-fno-common</code> も指定します。次のコマンドでコンパイラフラグを確認することができます。</p>
<div class="highlight"><pre><span></span>$ ./llvm-config --cxxflags
-I/path/to/clang+llvm-3.7.0/include -DNDEBUG -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -O3 -std<span class="o">=</span>c++11 -fvisibility-inlines-hidden -fno-exceptions -fno-rtti -fno-common -Wcast-qual
</pre></div>
<blockquote>
<p><strong>NOTE</strong><br />
この typeinfo のリンクエラーについては当初原因の見当がつかず、解決したときはうれしかったです。
解決方法は先に示した通り、例外テーブルの生成と RTTI を無効にすることです。
<code>nm -o libclangTooling.a | grep FrontendActionFactory</code> するとシンボルは見つかるし、ライブラリもリンクしている、それなのにリンクエラーが起きるのはなんでだろう、と途方にくれていたところでした。
RTTI を無効にするなんてめったに思いつきません…。
そう言われてみれば、少し前 Visual Studio で Visual C++ Compiler の代わりに Clang を使ったときに、「例外と RTTI は使えないよ」とエラーが出力されたことを思い出しました。</p>
</blockquote>
<h2>実行時に libclang の動的ライブラリを実行ファイルから見つけられるようにする</h2>
<p>ここまでくれば、リンクも問題なくできて LibTooling を使ったアプリケーションをビルドできるはずです。
実行時にアプリケーションは libclang の動的ライブラリをロードするので、適当なところに動的ライブラリをコピーまたはアプリケーションにバンドルしてください。
OS X の場合は <code>libclang.dylib</code> を、Windows の場合は <code>libclang.dll</code> を、Linux の場合は <code>libclang.so</code> が該当します。
もっとも手軽なのは実行ファイルと同じディレクトリに、ライブラリファイルをコピーすることです。</p>
<p>実行時に <code>libclang.dylib</code> がうまくロードできていない場合、次のような実行時エラーが出ます。</p>
<div class="highlight"><pre><span></span><span class="n">dyld</span><span class="o">:</span> <span class="n">Library</span> <span class="n">not</span> <span class="n">loaded</span><span class="o">:</span> <span class="err">@</span><span class="n">rpath</span><span class="o">/</span><span class="n">libclang</span><span class="o">.</span><span class="na">dylib</span>
<span class="n">Referenced</span> <span class="n">from</span><span class="o">:</span> <span class="o">~/</span><span class="n">Library</span><span class="sr">/Developer/Xcode/DerivedData/MyApp/Build/Products/Debug/</span><span class="n">MyApp</span>
<span class="n">Reason</span><span class="o">:</span> <span class="n">image</span> <span class="n">not</span> <span class="n">found</span>
<span class="n">Program</span> <span class="n">ended</span> <span class="k">with</span> <span class="n">exit</span> <span class="n">code</span><span class="o">:</span> <span class="mi">9</span>
</pre></div>
<h2>その他・おぼえ書き</h2>
<p>シェルのバッククォート表現と <code>llvm-config</code> を使って、clang++ や g++ に簡単にコンパイルオプションを渡すことができます。</p>
<div class="highlight"><pre><span></span>$ clang++ main.cpp -std<span class="o">=</span>c++14 -stdlib<span class="o">=</span>libc++ <span class="se">\</span>
<span class="sb">`</span>/path/to/clang+llvm-3.7.0/bin/llvm-config --cxxflags --ldflags --libs --system-libs<span class="sb">`</span>
</pre></div>
<p>静的ライブラリに、任意のクラスのシンボル情報が含まれているかどうかを調べるには <code>nm</code> コマンドを使います。</p>
<div class="highlight"><pre><span></span>$ nm -o lib/libclangTooling.a <span class="p">|</span> grep FrontendActionFactory
</pre></div>
<h2>参考文献</h2>
<ul>
<li><a href="http://clang.llvm.org/docs/LibTooling.html">LibTooling — Clang 3.8 documentation</a></li>
<li><a href="http://clang.llvm.org/docs/LibASTMatchersTutorial.html">Tutorial for building tools using LibTooling and LibASTMatchers — Clang 3.8 documentation</a></li>
</ul>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>試していないのではっきりとは書けませんが、Visual Studio や、直接 g++ コマンドや clang++ コマンドからビルドした場合も同じ手順で組み込むことができると思います。 <a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:2">
<p>僕は、このコンパイラフラグを設定し忘れていて今回リンクエラーにはまってしまいました。 <a class="footnote-backref" href="#fnref:2" rev="footnote" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:3">
<p>時間と説明を省略するためにビルド済みのバイナリを使いました。少し時間はかかりますが、リポジトリから最新のソースコードを手に入れてビルドするのは想像よりも難しくありません。ninja コマンドを入力するだけです。 <a class="footnote-backref" href="#fnref:3" rev="footnote" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
<li id="fn:4">
<p>このときお好みで <code>/usr/local/lib</code> 以下にインストールしたり、シンボリックリンクを追加してもかまいません。 <a class="footnote-backref" href="#fnref:4" rev="footnote" title="Jump back to footnote 4 in the text">↩</a></p>
</li>
</ol>
</div>CHICKEN Scheme で Eggs の openssl が Mac にインストールできないときの対処法2015-10-27T00:00:00+09:00mogemimitag:enginetrouble.net,2015-10-18:2015/10/chicken-install-openssl-on-mac.html<p>Mac OS X 10.11 (El Capitan) にアップグレードしました。
それが災いしてかどうかはわかりませんが、CHICKEN Scheme で Eggs の <a href="http://wiki.call-cc.org/eggref/4/openssl">openssl ライブラリ</a>を Mac にインストールしようとしたところ、次のようなコンパイルエラーが出て <code>chicken-install</code> に失敗しました。</p>
<div class="highlight"><pre><span></span>$ chicken-install openssl
...
installing openssl:1.7.0 ...
changing current directory to /var/folders/m4/T/temp/openssl
<span class="s1">'/usr/local/Cellar/chicken/4.9.0.1/bin/csi'</span> -bnq -setup-mode -e <span class="s2">"(require-library setup-api)"</span> -e <span class="s2">"(import setup-api)"</span> -e <span class="s2">"(setup-error-handling)"</span> -e <span class="s2">"(extension-name-and-version '(\"openssl\" \"1.7.0\"))"</span> <span class="s1">'openssl.setup'</span>
<span class="s1">'/usr/local/Cellar/chicken/4.9.0.1/bin/csc'</span> -feature compiling-extension -setup-mode -O2 -d0 -s -j openssl openssl.scm -lssl -lcrypto
openssl.c:33:10: fatal error: <span class="s1">'openssl/err.h'</span> file not found
<span class="c1">#include <openssl/err.h></span>
^
<span class="m">1</span> error generated.
Error: shell <span class="nb">command</span> terminated with non-zero <span class="nb">exit</span> status 256: <span class="s1">'clang'</span> <span class="s1">'openssl.c'</span> -o <span class="s1">'openssl.o'</span> -c -fno-strict-aliasing -fwrapv -fno-common -DHAVE_CHICKEN_CONFIG_H -m64 -DC_ENABLE_PTABLES -Os -fomit-frame-pointer -fPIC -DPIC -DC_SHARED -I<span class="s2">"/usr/local/Cellar/chicken/4.9.0.1/include/chicken"</span>
...
Error: shell <span class="nb">command</span> terminated with nonzero <span class="nb">exit</span> code
17920
<span class="s2">"'/usr/local/Cellar/chicken/4.9.0.1/bin/csi' -bnq -setup-mode -e \"(require-libr...</span>
</pre></div>
<p>どうやら、Eggs の openssl をコンパイルするときに OpenSSL のヘッダーファイルが上手く見つけられないようです。
そこで次の手順を試してみました:</p>
<ol>
<li>Homebrew で OpenSSL をインストール</li>
<li><code>CPATH</code> と <code>LIBRARY_PATH</code> でヘッダーファイルとライブラリファイルのディレクトリパスを指定する</li>
<li><code>chicken-install openssl</code> を実行する</li>
</ol>
<div class="highlight"><pre><span></span>brew install openssl
<span class="nb">export</span> <span class="nv">CPATH</span><span class="o">=</span><span class="nv">$CPATH</span>:/usr/local/opt/openssl/include
<span class="nb">export</span> <span class="nv">LIBRARY_PATH</span><span class="o">=</span><span class="nv">$LIBRARY_PATH</span>:/usr/local/opt/openssl/lib
chicken-install openssl
</pre></div>
<p>これで無事にコンパイルとリンクが通り、インストールできました。
事前に <code>CPATH</code> と <code>LIBRARY_PATH</code> でパスを外からコンパイラに教えてあげることで、<code>chicken-install</code> の中で clang のコンパイルが走るときにサーチパスの解決が上手くできたようです。</p>
<p>変なところでつまづいてしまったので忘れないよう、こまめに日記をつけていこうと思います。</p>
<h2>追記 (Oct 27, 2015)</h2>
<p><code>CSC_OPTIONS</code> という環境変数に C コンパイラのコンパイルオプションを直接指定できるそうです。</p>
<div class="highlight"><pre><span></span><span class="nb">export</span> <span class="nv">CSC_OPTIONS</span><span class="o">=</span><span class="s1">'-I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib'</span>
chicken-install openssl
</pre></div>
<p>または、次のように環境変数 <code>CHICKEN_C_INCLUDE_PATH</code> と <code>CHICKEN_C_LIBRARY_PATH</code> にそれぞれインクルードパスとライブラリパスを渡しても同様のようです。</p>
<div class="highlight"><pre><span></span><span class="nb">export</span> <span class="nv">CHICKEN_C_INCLUDE_PATH</span><span class="o">=</span>/usr/local/opt/openssl/include
<span class="nb">export</span> <span class="nv">CHICKEN_C_LIBRARY_PATH</span><span class="o">=</span>/usr/local/opt/openssl/lib
chicken-install openssl
</pre></div>
<p>後からドキュメント<sup id="fnref:1"><a class="footnote-ref" href="#fn:1" rel="footnote">1</a></sup>を読んでいたら見つけました。(今度からちゃんと確認します…。)</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p><a href="http://wiki.call-cc.org/man/4/Extensions#installing-extensions">http://wiki.call-cc.org/man/4/Extensions#installing-extensions</a> - "Installing extensions that use libraries" の項目に載っています。 <a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
</ol>
</div>GYP を使って Xcode プロジェクトを作ってみよう2015-10-29T00:00:00+09:00mogemimitag:enginetrouble.net,2015-09-27:2015/09/gyp-generate-xcode-project.html<p>今回は、ビルドツールの GYP (Generate Your Projects) を使って、Xcode プロジェクト (.xcodeproj) を作成する方法を紹介します。
GYP では Xcode Settings (<code>xcode_settings</code>) を使って Xcode に関するビルド設定を記述することができます。
そのほかにアプリケーションのバンドルに関する <code>mac_bundle</code> や <code>mac_bundle_resources</code> といった設定も存在します。
これらの設定項目について、備忘録をかねてざっくり紹介していきます。</p>
<p>GYP の基本的な使い方については前回の記事 <a href="/2013/07/getting-started-with-gyp.html">"ビルドオートメーションツールGYPを使おう"</a> をご参照ください。</p>
<hr />
<h5>アプリケーションやリソースファイルのバンドルに関する設定</h5>
<ul>
<li><a href="#mac_bundle">アプリケーションまたはフレームワークをビルドする (mac_bundle)</a></li>
<li><a href="#mac_bundle_resources">アプリケーションにバンドルしたいリソースファイルを追加する (mac_bundle_resources)</a></li>
<li><a href="#linking-frameworks">依存するフレームワーク (.framework) をプロジェクトに追加する</a></li>
</ul>
<h5>GYP を使った Xcode プロジェクトの作成について</h5>
<ul>
<li><a href="#how-to-install-gyp-locally">gyp コマンドをどうやって手に入れる?</a></li>
<li><a href="#xcode-settings-examples">簡単な Cocoa アプリケーションのビルド設定例</a></li>
</ul>
<h5>Architecture - アーキテクチャの設定</h5>
<ul>
<li><a href="#SUPPORTED_PLATFORMS">サポートするプラットフォームの設定 (Supported Platforms)</a></li>
<li><a href="#SDKROOT">ベース SDK のバージョンを設定 (Base SDK)</a></li>
<li><a href="#ARCHS">アーキテクチャの設定 (Architectures)</a></li>
<li><a href="#VALID_ARCHS">ターゲットアーキテクチャの設定 (Valid Architectures)</a></li>
<li><a href="#ONLY_ACTIVE_ARCH">アクティブなアーキテクチャのみターゲットにしてビルドする (Build Active Architecture Only)</a></li>
</ul>
<h5>Assets</h5>
<ul>
<li><a href="#EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE">Embed Asset Packs in Product Bundle</a></li>
</ul>
<h5>Build Options</h5>
<ul>
<li><a href="#DEBUG_INFORMATION_FORMAT">デバッグ情報のフォーマット (Debug Infomation Format)</a></li>
<li><a href="#VALIDATE_PRODUCT">Validate Built Product</a></li>
</ul>
<h5>Code Signing</h5>
<ul>
<li><a href="#CODE_SIGN_IDENTITY">コード署名 ID の設定 (Code Signing identity)</a></li>
</ul>
<h5>Deployment</h5>
<ul>
<li><a href="#MACOSX_DEPLOYMENT_TARGET">Mac OS X のターゲットバージョンの設定 (OS X Deployment Target)</a></li>
<li><a href="#IPHONEOS_DEPLOYMENT_TARGET">iOS のターゲットバージョンの設定 (iOS Deployment Target)</a></li>
<li><a href="#WATCHOS_DEPLOYMENT_TARGET">watchOS のターゲットバージョンの設定 (watchOS Deployment Target)</a></li>
<li><a href="#SKIP_INSTALL">Skip Install</a></li>
<li><a href="#COPY_PHASE_STRIP">Strip debug Symbols During Copy</a></li>
<li><a href="#LOCAL_LIBRARY_DIR">Installation Directory</a></li>
</ul>
<h5>Linking</h5>
<ul>
<li><a href="#DYLIB_INSTALL_NAME_BASE">Dynamic Library Install Name Base</a></li>
<li><a href="#DYLIB_COMPATIBILITY_VERSION">Compatibility Version</a></li>
<li><a href="#DYLIB_CURRENT_VERSION">Current Library Version</a></li>
<li><a href="#LD_RUNPATH_SEARCH_PATHS">Runpath Search Paths</a></li>
</ul>
<h5>Packaging</h5>
<ul>
<li><a href="#INFOPLIST_FILE">Info.plist ファイルの指定 (Info.plist File)</a></li>
<li><a href="#PRODUCT_BUNDLE_IDENTIFIER">Product Bundle Identifier</a></li>
<li><a href="#PRODUCT_NAME">Product Name</a></li>
<li><a href="#DEFINES_MODULE">Defines Module</a></li>
<li><a href="#FRAMEWORK_VERSION">Framework Version</a></li>
</ul>
<h5>Search Paths</h5>
<ul>
<li><a href="#ALWAYS_SEARCH_USER_PATHS">Always Search User Paths</a></li>
<li><a href="#LIBRARY_SEARCH_PATHS">ライブラリのディレクトリパスを追加する (Library Search Paths)</a></li>
</ul>
<h5>Versioning</h5>
<ul>
<li><a href="#VERSIONING_SYSTEM">Versioning System</a></li>
</ul>
<h5>Apple LLVM - Code Generation</h5>
<ul>
<li><a href="#GCC_OPTIMIZATION_LEVEL">コンパイラの最適化レベル (Optimization Level)</a></li>
<li><a href="#GCC_SYMBOLS_PRIVATE_EXTERN">シンボルのデフォルトの Visibility 属性を設定する (Symbols Hidden by Default)</a></li>
<li><a href="#GCC_INLINES_ARE_PRIVATE_EXTERN">インライン関数の Visibility 属性を設定する (Inline Methods Hidden)</a></li>
</ul>
<h5>Apple LLVM - Custom Compiler Flags</h5>
<ul>
<li><a href="#OTHER_CPLUSPLUSFLAGS">C++ のコンパイラフラグを設定する (OTHER_CPLUSPLUSFLAGS)</a></li>
<li><a href="#WARNING_CFLAGS">警告のレベルを指定する (Other Warning Flags)</a></li>
</ul>
<h5>Apple LLVM - Language</h5>
<ul>
<li><a href="#switching-between-gcc-and-clang-llvm">コンパイラを LLVM Clang または GCC に切り替える (Compiler for C/C++/Objective-C)</a></li>
<li><a href="#GCC_C_LANGUAGE_STANDARD">C コンパイラのバージョンを変更する (C Language Dialect)</a></li>
<li><a href="#compiling-with-libcxx-or-gnustd">標準ライブラリとして libc++ または libstdc++ を使う</a></li>
<li><a href="#compiling-cxx11-or-cxx14">C++11 または C++14 でコンパイルする</a></li>
<li><a href="#CLANG_ENABLE_MODULES">Enable Modules (C and Objective-C)</a></li>
<li><a href="#CLANG_ENABLE_OBJC_ARC">Objective-C の ARC を有効にする (Objective-C Automatic Reference Counting)</a></li>
</ul>
<h5>Apple LLVM - Preprocessing</h5>
<ul>
<li><a href="#ENABLE_NS_ASSERTIONS">Enable Foundation Assertions</a></li>
</ul>
<h5>Apple LLVM - Warnings</h5>
<ul>
<li><a href="#GCC_TREAT_WARNINGS_AS_ERRORS">コンパイラの警告をエラーとして扱う (Warning Policies - Treat Warnings as Errors)</a></li>
<li><a href="#warnings-in-xcode">コンパイラの警告を有効または無効にする (Warnings)</a><ul>
<li><a href="#warnings-all-languages">言語共通の警告 (Warnings - All languages)</a></li>
<li><a href="#warnings-cxx">C++ に関する警告の設定 (Warnings - C++)</a></li>
<li><a href="#warnings-objective-c">Objective-C に関する警告の設定 (Warnings - Objective C)</a></li>
<li><a href="#warnings-objective-c-and-arc">Objective-C と ARC に関する警告の設定 (Warnings - Objective C and ARC)</a></li>
</ul>
</li>
</ul>
<h5>Asset Catalog Compiler</h5>
<ul>
<li><a href="#ASSETCATALOG_COMPILER_APPICON_NAME">Asset Catalog App Icon Set Name</a></li>
</ul>
<h5>User-Defined</h5>
<ul>
<li><a href="#MTL_ENABLE_DEBUG_INFO">Metal のデバッグ情報を有効または無効にする (MTL_ENABLE_DEBUG_INFO)</a></li>
</ul>
<hr />
<h2><a name="mac_bundle"></a>アプリケーションまたはフレームワークをビルドする (mac_bundle)</h2>
<p>リソースファイルやアセットなどのバンドルを必要とするアプリケーションやフレームワークをビルドする場合は GYP の <code>mac_bundle</code> を設定する必要があります。
<code>.app</code> 拡張子から始まるような OS X のアプリケーションやフレームワーク (<code>.framework</code>) を作成する場合は次のように <code>'mac_bundle': 1</code> を指定する必要があります。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'MyApp'</span><span class="p">,</span>
<span class="s1">'product_name'</span><span class="p">:</span> <span class="s1">'MyApp'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'executable'</span><span class="p">,</span>
<span class="s1">'mac_bundle'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">],</span>
<span class="p">}</span>
</pre></div>
<p>静的ライブラリ (<code>.a</code>) やフレームワーク以外の動的ライブラリ (<code>.dylib</code>) をビルドする場合は指定する必要はありません。</p>
<h2><a name="mac_bundle_resources"></a>アプリケーションにバンドルしたいリソースファイルを追加する (mac_bundle_resources)</h2>
<p>アプリケーションやフレームワークと一緒にバンドルしたいリソースファイルをプロジェクトに追加する場合は、<code>mac_bundle_resources</code> を使います。
次の例では以下に示すリソースファイルをアプリケーションにバンドルするように指定しています。</p>
<ul>
<li><code>Content</code> ディレクトリ以下のリソースファイルすべて</li>
<li><code>Assets.xcassets/</code> ディレクトリ以下のリソースファイルすべて</li>
<li>Cocoa のストーリーボードファイル (<code>Base.lproj/LaunchScreen.storyboard</code>, <code>Base.lproj/Main.storyboard</code>)</li>
</ul>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'MyApp'</span><span class="p">,</span>
<span class="s1">'product_name'</span><span class="p">:</span> <span class="s1">'MyApp'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'executable'</span><span class="p">,</span>
<span class="s1">'mac_bundle'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="s1">'mac_bundle_resources'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'Content/'</span><span class="p">,</span>
<span class="s1">'Assets.xcassets/'</span><span class="p">,</span>
<span class="s1">'Base.lproj/LaunchScreen.storyboard'</span><span class="p">,</span>
<span class="s1">'Base.lproj/Main.storyboard'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="p">],</span>
<span class="p">}</span>
</pre></div>
<p>OS X アプリケーションだけでなく、iOS アプリケーションや watchOS アプリケーションについても同様に <code>mac_bundle_resources</code> でバンドルするリソースファイルを指定します。</p>
<h2><a name="linking-frameworks"></a>依存するフレームワーク (.framework) をプロジェクトに追加する</h2>
<p><code>Cocoa.framework</code> や <code>UIKit.framework</code> といったフレームワークまたは静的・動的ライブラリをリンクするときは、GYP の <code>link_settings.libraries</code> を利用します。
次の例では Cocoa アプリケーションで利用するいくつかのフレームワークを追加しています。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'link_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'libraries'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'$(SDKROOT)/System/Library/Frameworks/Cocoa.framework'</span><span class="p">,</span>
<span class="s1">'$(SDKROOT)/System/Library/Frameworks/AudioToolBox.framework'</span><span class="p">,</span>
<span class="s1">'$(SDKROOT)/System/Library/Frameworks/OpenAL.framework'</span><span class="p">,</span>
<span class="s1">'$(SDKROOT)/System/Library/Frameworks/OpenGL.framework'</span><span class="p">,</span>
<span class="s1">'$(SDKROOT)/System/Library/Frameworks/QuartzCore.framework'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>CocoaTouch や iOS のフレームワークを追加する場合も同様です。
次の例では Metal アプリケーションを作るときに必要なフレームワークを追加しています。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'link_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'libraries'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'$(SDKROOT)/System/Library/Frameworks/Foundation.framework'</span><span class="p">,</span>
<span class="s1">'$(SDKROOT)/System/Library/Frameworks/UIKit.framework'</span><span class="p">,</span>
<span class="s1">'$(SDKROOT)/System/Library/Frameworks/Metal.framework'</span><span class="p">,</span>
<span class="s1">'$(SDKROOT)/System/Library/Frameworks/MetalKit.framework'</span><span class="p">,</span>
<span class="s1">'$(SDKROOT)/System/Library/Frameworks/ModelIO.framework'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="how-to-install-gyp-locally"></a>gyp コマンドをどうやって手に入れる?</h2>
<p>Superuser の権限がなくてインストールできない場合や、<code>gyp</code> コマンドのグローバルインストールに心理的抵抗がある方向けに、
GYP を手軽に使用する方法をここでは紹介します。
GYP のグローバルインストールについては <a href="/2013/07/getting-started-with-gyp.html">前回の記事(ビルドオートメーションツールGYPを使おう)</a> をご参照ください。
また、お使いのマシンにすでに <code>gyp</code> コマンドがインストールされていれば、この項はスキップしていただいてかまいません。</p>
<p>まず、Git を使って gyp のソースコードを手に入れます。
リポジトリをすべて git clone するのは時間がかかるので、今回は shallow clone しています。</p>
<div class="highlight"><pre><span></span>git clone --depth<span class="o">=</span><span class="m">1</span> https://chromium.googlesource.com/external/gyp.git gyp
</pre></div>
<p>GYP は Python 2 系で動作するので、Python のバージョンも確認しておいてください。</p>
<div class="highlight"><pre><span></span>python --version
</pre></div>
<p>これでインストールせずに GYP が使えるようになります。
<code>gyp</code> コマンドの代わりに <code>python gyp/gyp_main.py</code> もしくは <code>gyp/gyp</code> で実行できます。
もちろん、Windows のコマンドプロンプト、MinGW、Ubuntu のコンソールについても同様に <code>python gyp/gyp_main.py</code> は有効です。</p>
<h2><a name="xcode-settings-examples"></a>簡単な Cocoa アプリケーションのビルド設定例</h2>
<p>実際にどんな GYP ファイルを書けばいいのかわからない方のために、簡単な Xcode プロジェクトを作成する GYP ファイルの例を紹介します。
最初に OS X や iOS などのプラットフォームや、ライブラリやフレームワーク、アプリケーションといった各ビルドターゲットに限らず、C++ や Objective-C を使う Xcode プロジェクトで共通する設定を以下に <code>target_defaults</code> として示します。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1"># Language</span>
<span class="s1">'GCC_VERSION'</span><span class="p">:</span> <span class="s1">'com.apple.compilers.llvm.clang.1_0'</span><span class="p">,</span>
<span class="s1">'CLANG_CXX_LANGUAGE_STANDARD'</span><span class="p">:</span> <span class="s1">'c++14'</span><span class="p">,</span>
<span class="s1">'CLANG_CXX_LIBRARY'</span><span class="p">:</span> <span class="s1">'libc++'</span><span class="p">,</span>
<span class="s1">'CLANG_ENABLE_OBJC_ARC'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="c1"># Warnings</span>
<span class="s1">'GCC_WARN_SHADOW'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_UNUSED_VARIABLE'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_TREAT_WARNINGS_AS_ERRORS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'WARNING_CFLAGS'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'-Wall'</span><span class="p">],</span>
<span class="p">},</span>
<span class="s1">'configurations'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'Debug'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'defines'</span><span class="p">:[</span><span class="s1">'DEBUG=1'</span><span class="p">],</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_OPTIMIZATION_LEVEL'</span><span class="p">:</span> <span class="s1">'0'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="s1">'Release'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_OPTIMIZATION_LEVEL'</span><span class="p">:</span> <span class="s1">'3'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<p>この例では C++14, C, Objective-C または Objective-C++ のソースコードをコンパイルするための Xcode の設定をしています。ビルドコンフィグレーションとして Debug と Release を追加しています。
また、コンパイラの警告として <code>GCC_WARN_SHADOW</code> と <code>GCC_WARN_UNUSED_VARIABLE</code> を追加しています。
警告内容はプロジェクトごとに適宜必要な項目を追加してください。
以降の例では、この <code>target_defaults</code> を記述した前提で話を進めていきます。</p>
<blockquote>
<p><strong>NOTE: 警告を無視すると後で大変なことになる</strong><br />
Xcode のプロジェクトに限らず、できるだけ多くのケースについてコンパイラの警告を出すようにしたり、警告レベルを高く設定したりすることをオススメします。
また警告は可能な限りエラー扱いにしておくことも合わせて推奨します。Xcode では <code>GCC_TREAT_WARNINGS_AS_ERRORS</code> を <code>YES</code> に設定することでエラーとして扱えます。
もし Visual Studio でビルドするときは <code>WarnAsError (/WX)</code> オプションを、Make と Clang を使ってコンパイルするときは <code>Treat Warnings as Errors (-Werror)</code> オプションをつけます。
警告が出力されるということには必ず意味があります。
「コンパイラの警告を無視して後で痛い目をみた」という経験を誰しも一度や二度したことがあるのではないでしょうか。</p>
</blockquote>
<p>次に、アプリケーションのビルドターゲット例を示します。
ソースコードやリソースファイルの位置については適宜読み替えてください。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'MyApp'</span><span class="p">,</span>
<span class="s1">'product_name'</span><span class="p">:</span> <span class="s1">'MyApp'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'executable'</span><span class="p">,</span>
<span class="s1">'mac_bundle'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="s1">'sources'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'AppDelegate.h'</span><span class="p">,</span>
<span class="s1">'AppDelegate.m'</span><span class="p">,</span>
<span class="s1">'main.m'</span><span class="p">,</span>
<span class="p">],</span>
<span class="s1">'mac_bundle_resources'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'Assets.xcassets/'</span><span class="p">,</span>
<span class="s1">'Base.lproj/MainMenu.storyboard'</span><span class="p">,</span>
<span class="p">],</span>
<span class="s1">'link_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'libraries'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'$(SDKROOT)/System/Library/Frameworks/Cocoa.framework'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'MACOSX_DEPLOYMENT_TARGET'</span><span class="p">:</span> <span class="s1">'10.9'</span><span class="p">,</span>
<span class="s1">'ASSETCATALOG_COMPILER_APPICON_NAME'</span><span class="p">:</span> <span class="s1">'AppIcon'</span><span class="p">,</span>
<span class="s1">'INFOPLIST_FILE'</span><span class="p">:</span> <span class="s1">'Info.plist'</span><span class="p">,</span>
<span class="s1">'DYLIB_INSTALL_NAME_BASE'</span><span class="p">:</span> <span class="s1">'@rpath'</span><span class="p">,</span>
<span class="p">},</span>
<span class="s1">'configurations'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'Debug'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'ONLY_ACTIVE_ARCH'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'DEBUG_INFORMATION_FORMAT'</span><span class="p">:</span> <span class="s1">'dwarf'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">],</span>
<span class="p">}</span>
</pre></div>
<p>上記の例では、"MyApp" というデスクトップアプリケーションをビルドする簡単な GYP の設定を示しています。
実際に、リソースファイルなどを用意して次のコマンドで OS X 10.9 以上をターゲットにした Cocoa アプリケーションの Xcode プロジェクトを作成することができます。<sup id="fnref:1"><a class="footnote-ref" href="#fn:1" rel="footnote">1</a></sup></p>
<div class="highlight"><pre><span></span>gyp MyApp.gyp --depth<span class="o">=</span>. -f xcode
</pre></div>
<p>例の中で出てきた <code>xcode_settings</code> の各項目についてはこれからざっくり紹介していきます。<sup id="fnref:2"><a class="footnote-ref" href="#fn:2" rel="footnote">2</a></sup></p>
<h2><a name="SUPPORTED_PLATFORMS"></a>サポートするプラットフォームの設定 (Supported Platforms)</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'SUPPORTED_PLATFORMS'</span><span class="p">:</span> <span class="s1">'iphonesimulator iphoneos'</span><span class="p">,</span> <span class="c1"># iOS</span>
<span class="c1">#'SUPPORTED_PLATFORMS': 'macosx', # OS X</span>
<span class="c1">#'SUPPORTED_PLATFORMS': 'watchsimulator watchos', # watchOS</span>
<span class="p">},</span>
</pre></div>
<h2><a name="SDKROOT"></a>ベース SDK のバージョンを設定 (Base SDK)</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'SDKROOT'</span><span class="p">:</span> <span class="s1">'iphoneos'</span><span class="p">,</span> <span class="c1"># Latest iOS</span>
<span class="c1">#'SDKROOT': 'iphoneos9.0', # iOS 9.0</span>
<span class="c1">#'SDKROOT': 'macosx', # Latest OS X</span>
<span class="c1">#'SDKROOT': 'macosx10.11', # OS X 10.11</span>
<span class="c1">#'SDKROOT': 'watchos', # Latest watchOS</span>
<span class="c1">#'SDKROOT': 'watchos2.0', # watchOS 2.0</span>
<span class="p">},</span>
</pre></div>
<h2><a name="ARCHS"></a>アーキテクチャの設定 (Architectures)</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'ARCHS'</span><span class="p">:</span> <span class="p">[</span>
<span class="c1"># Standard architectures (armv7k)</span>
<span class="s1">'$(ARCHS_STANDARD)'</span><span class="p">,</span>
<span class="c1"># Standard Architectures (including 64-bit)</span>
<span class="c1">#'$(ARCHS_STANDARD_INCLUDING_64_BIT)',</span>
<span class="p">],</span>
<span class="p">},</span>
</pre></div>
<h2><a name="VALID_ARCHS"></a>ターゲットアーキテクチャの設定 (Valid Architectures)</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VALID_ARCHS'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'arm64'</span><span class="p">,</span>
<span class="s1">'armv7'</span><span class="p">,</span>
<span class="s1">'armv7s'</span><span class="p">,</span>
<span class="c1">#'$(ARCHS_STANDARD)',</span>
<span class="p">],</span>
<span class="p">},</span>
</pre></div>
<h2><a name="ONLY_ACTIVE_ARCH"></a>アクティブなアーキテクチャのみターゲットにしてビルドする (Build Active Architecture Only)</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'ONLY_ACTIVE_ARCH'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<p>デバッグビルド時のみ有効にする場合は次のようにします。</p>
<div class="highlight"><pre><span></span><span class="s1">'configurations'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'Debug'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'ONLY_ACTIVE_ARCH'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
</pre></div>
<h2><a name="EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE"></a>Embed Asset Packs in Product Bundle</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="DEBUG_INFORMATION_FORMAT"></a>デバッグ情報のフォーマット (Debug Infomation Format)</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'DEBUG_INFORMATION_FORMAT'</span><span class="p">:</span> <span class="s1">'dwarf'</span><span class="p">,</span> <span class="c1"># DWARF</span>
<span class="c1">#'DEBUG_INFORMATION_FORMAT': 'dwarf-with-dsym', # DWARF with dSYM File</span>
<span class="p">},</span>
</pre></div>
<p>各ビルドコンフィグレーション (Debug, Release) ごとにデバッグ情報のフォーマットを変更する場合は、次のようにします。</p>
<div class="highlight"><pre><span></span><span class="s1">'configurations'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'Debug'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'DEBUG_INFORMATION_FORMAT'</span><span class="p">:</span> <span class="s1">'dwarf'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="s1">'Release'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'DEBUG_INFORMATION_FORMAT'</span><span class="p">:</span> <span class="s1">'dwarf-with-dsym'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
</pre></div>
<h2><a name="VALIDATE_PRODUCT"></a>Validate Built Product</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VALIDATE_PRODUCT'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="CODE_SIGN_IDENTITY"></a>コード署名 ID の設定 (Code Signing identity)</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1"># Any iOS SDK: iOS Developer</span>
<span class="s1">'CODE_SIGN_IDENTITY[sdk=iphoneos*]'</span><span class="p">:</span> <span class="s1">'iPhone Developer'</span><span class="p">,</span>
<span class="c1"># Any OS X SDK: Don't Code Sign</span>
<span class="s1">'CODE_SIGN_IDENTITY[sdk=macosx*]'</span><span class="p">:</span> <span class="s1">''</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="MACOSX_DEPLOYMENT_TARGET"></a>Mac OS X のターゲットバージョンの設定 (OS X Deployment Target)</h2>
<p>Mac で動くアプリケーションを開発する場合、動作対象にする OS X のバージョンを指定することができます。
<code>MACOSX_DEPLOYMENT_TARGET</code> に OS X バージョンを指定します。
次の例では、OS X 10.9 以降をアプリケーションの動作対象に指定しています。</p>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'MACOSX_DEPLOYMENT_TARGET'</span><span class="p">:</span> <span class="s1">'10.9'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<p>上記の設定例でビルドしたアプリケーションは、古い 10.8 (Mountain Lion) などでは実行できず、10.9 (Mavericks) 以降の Mac で動かすことができます。</p>
<blockquote>
<p><strong>NOTE</strong><br />
OpenGL 3.3 以降(または OpenGL 4)を使ったアプリケーションを開発する場合は、OS X 10.9 (Mavericks) 以降を動作対象として指定しておくといいでしょう。
OS X 10.8 (Mountain Lion) では OpenGL 3.2 までしか利用できませんが 10.9 以降では OpenGL 4.1 まで対応できます。
また Mavericks や Yosemite (OS X 10.10), El Capitan (10.11) は無償でアップグレードできるため、アプリケーションの動作対象を最新の OS X に引き上げても差支えないでしょう。</p>
</blockquote>
<h2><a name="IPHONEOS_DEPLOYMENT_TARGET"></a>iOS のターゲットバージョンの設定 (iOS Deployment Target)</h2>
<p>次の例では iOS 9.0 以上をアプリケーションの動作対象となるターゲットに設定しています。</p>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'IPHONEOS_DEPLOYMENT_TARGET'</span><span class="p">:</span> <span class="s1">'9.0'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="WATCHOS_DEPLOYMENT_TARGET"></a>watchOS のターゲットバージョンの設定 (watchOS Deployment Target)</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'WATCHOS_DEPLOYMENT_TARGET'</span><span class="p">:</span> <span class="s1">'2.0'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="SKIP_INSTALL"></a>Skip Install</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'SKIP_INSTALL'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="c1">#'SKIP_INSTALL': 'NO',</span>
<span class="p">},</span>
</pre></div>
<h2><a name="COPY_PHASE_STRIP"></a>Strip debug Symbols During Copy</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'COPY_PHASE_STRIP': 'YES',</span>
<span class="s1">'COPY_PHASE_STRIP'</span><span class="p">:</span> <span class="s1">'NO'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="DYLIB_INSTALL_NAME_BASE"></a>Dynamic Library Install Name Base</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'DYLIB_INSTALL_NAME_BASE'</span><span class="p">:</span> <span class="s1">'@rpath'</span><span class="p">,</span>
<span class="c1">#'DYLIB_INSTALL_NAME_BASE': '@executable_path/../../..',</span>
<span class="p">},</span>
</pre></div>
<h2><a name="LD_RUNPATH_SEARCH_PATHS"></a>Runpath Search Paths</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'LD_RUNPATH_SEARCH_PATHS'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'$(inherited)'</span><span class="p">,</span>
<span class="s1">'@executable_path/../Frameworks'</span><span class="p">,</span>
<span class="s1">'@loader_path/Frameworks'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">},</span>
</pre></div>
<h2><a name="LOCAL_LIBRARY_DIR"></a>Installation Directory</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'INSTALL_PATH'</span><span class="p">:</span> <span class="s1">'$(LOCAL_LIBRARY_DIR)/Frameworks'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="DYLIB_COMPATIBILITY_VERSION"></a>Compatibility Version</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'DYLIB_COMPATIBILITY_VERSION'</span><span class="p">:</span> <span class="s1">'1'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="DYLIB_CURRENT_VERSION"></a>Current Library Version</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'DYLIB_CURRENT_VERSION'</span><span class="p">:</span> <span class="s1">'1'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="INFOPLIST_FILE"></a> Info.plist ファイルの指定 (Info.plist File)</h2>
<p>GYP ファイルからの <code>.plist</code> ファイルへの相対パスを指定します。</p>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'INFOPLIST_FILE'</span><span class="p">:</span> <span class="s1">'MyApp/Info.plist'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="PRODUCT_BUNDLE_IDENTIFIER"></a>Product Bundle Identifier</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'PRODUCT_BUNDLE_IDENTIFIER'</span><span class="p">:</span> <span class="s1">'com.example.MyApplication'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="PRODUCT_NAME"></a>Product Name</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'PRODUCT_NAME'</span><span class="p">:</span> <span class="s1">'$(TARGET_NAME)'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="DEFINES_MODULE"></a>Defines Module</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'DEFINES_MODULE'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="FRAMEWORK_VERSION"></a>Framework Version</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'FRAMEWORK_VERSION'</span><span class="p">:</span> <span class="s1">'A'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="ALWAYS_SEARCH_USER_PATHS"></a>Always Search User Paths</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'ALWAYS_SEARCH_USER_PATHS'</span><span class="p">:</span> <span class="s1">'NO'</span><span class="p">,</span>
<span class="c1">#'ALWAYS_SEARCH_USER_PATHS': 'YES',</span>
<span class="p">},</span>
</pre></div>
<h2><a name="LIBRARY_SEARCH_PATHS"></a>ライブラリのディレクトリパスを追加する (Library Search Paths)</h2>
<p><code>LIBRARY_SEARCH_PATHS</code> に追加します。</p>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'LIBRARY_SEARCH_PATHS'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'$(SDKROOT)/System/Library/Frameworks'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">},</span>
</pre></div>
<p>パスが1つの場合は、次のような記述でもかまいません。</p>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'LIBRARY_SEARCH_PATHS'</span><span class="p">:</span> <span class="s1">'$(SDKROOT)/System/Library/Frameworks'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<p>また <code>LIBRARY_SEARCH_PATHS[sdk=macosx*]</code> とすることでターゲットプラットフォームごとに設定をパスを指定することも可能です。
次の例では OS X 向けのビルドと、iOS 向けのビルドでライブラリパスを変更しています。</p>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'LIBRARY_SEARCH_PATHS[sdk=macosx*]'</span><span class="p">:</span> <span class="s1">'~/MacOSX10.11.sdk/System/Library/Frameworks'</span><span class="p">,</span>
<span class="s1">'LIBRARY_SEARCH_PATHS[sdk=iphoneos*]'</span><span class="p">:</span> <span class="s1">'$(SDKROOT)/System/Library/Frameworks'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="VERSIONING_SYSTEM"></a>Versioning System</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'VERSIONING_SYSTEM': '', # None</span>
<span class="s1">'VERSIONING_SYSTEM'</span><span class="p">:</span> <span class="s1">'apple-generic'</span><span class="p">,</span> <span class="c1"># Apple Generic</span>
<span class="p">},</span>
</pre></div>
<h2><a name="GCC_OPTIMIZATION_LEVEL"></a>コンパイラの最適化レベル (Optimization Level)</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1"># None [-O0]</span>
<span class="c1">#'GCC_OPTIMIZATION_LEVEL': '0',</span>
<span class="c1"># Fast [-O, O1]</span>
<span class="c1">#'GCC_OPTIMIZATION_LEVEL': '1',</span>
<span class="c1"># Faster [-O2]</span>
<span class="c1">#'GCC_OPTIMIZATION_LEVEL': '2',</span>
<span class="c1"># Fastest [-O3]</span>
<span class="s1">'GCC_OPTIMIZATION_LEVEL'</span><span class="p">:</span> <span class="s1">'3'</span><span class="p">,</span>
<span class="c1"># Fastest, Smallest [-Os]</span>
<span class="c1">#'GCC_OPTIMIZATION_LEVEL': 's',</span>
<span class="c1"># Fastest, Aggressive Optimizations [-Ofast]</span>
<span class="c1">#'GCC_OPTIMIZATION_LEVEL': 'fast',</span>
<span class="p">},</span>
</pre></div>
<p>各ビルドコンフィグレーション (Debug, Release) ごとに最適化レベルを変更する場合は、次のようにします。</p>
<div class="highlight"><pre><span></span><span class="s1">'configurations'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'Debug'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_OPTIMIZATION_LEVEL'</span><span class="p">:</span> <span class="s1">'0'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="s1">'Release'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_OPTIMIZATION_LEVEL'</span><span class="p">:</span> <span class="s1">'s'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
</pre></div>
<h2><a name="GCC_SYMBOLS_PRIVATE_EXTERN"></a>シンボルのデフォルトの Visibility 属性を設定する (Symbols Hidden by Default)</h2>
<p>GCC および Clang における <code>-fvisibility=hidden</code> オプションに対応するビルド設定は次のようになります。</p>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_SYMBOLS_PRIVATE_EXTERN'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span> <span class="c1"># '-fvisibility=hidden'</span>
<span class="p">},</span>
</pre></div>
<h2><a name="GCC_INLINES_ARE_PRIVATE_EXTERN"></a>インライン関数の Visibility 属性を設定する (Inline Methods Hidden)</h2>
<p>GCC および Clang における <code>-fvisibility-inlines-hidden</code> オプションに対応するビルド設定は次のようになります。</p>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_INLINES_ARE_PRIVATE_EXTERN'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span> <span class="c1"># '-fvisibility-inlines-hidden'</span>
<span class="p">},</span>
</pre></div>
<h2><a name="OTHER_CPLUSPLUSFLAGS"></a> C++ のコンパイラフラグを設定する (OTHER_CPLUSPLUSFLAGS)</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'OTHER_CPLUSPLUSFLAGS'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'-fno-exceptions'</span><span class="p">,</span>
<span class="s1">'-fno-rtti'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">},</span>
</pre></div>
<h2><a name="WARNING_CFLAGS"></a>警告のレベルを指定する (Other Warning Flags)</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'WARNING_CFLAGS'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'-Wall'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">},</span>
</pre></div>
<h2><a name="switching-between-gcc-and-clang-llvm"></a>コンパイラを LLVM Clang または GCC に切り替える (Compiler for C/C++/Objective-C)</h2>
<p>Xcode ではコンパイラとして LLVM Clang と GCC を利用することができます。
コンパイラに Clang を使うには、<code>GCC_VERSION</code> を次のように指定します。</p>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_VERSION'</span><span class="p">:</span> <span class="s1">'com.apple.compilers.llvm.clang.1_0'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="GCC_C_LANGUAGE_STANDARD"></a>C コンパイラのバージョンを変更する (C Language Dialect)</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'GCC_C_LANGUAGE_STANDARD': 'ansi', # ANSI C [-ansi]</span>
<span class="c1">#'GCC_C_LANGUAGE_STANDARD': 'c89', # C89 [-std=c89]</span>
<span class="c1">#'GCC_C_LANGUAGE_STANDARD': 'gnu89', # GNU89 [-std=gnu89]</span>
<span class="c1">#'GCC_C_LANGUAGE_STANDARD': 'c99', # C99 [-std=c99]</span>
<span class="c1">#'GCC_C_LANGUAGE_STANDARD': 'gnu99', # GNU99 [-std=gnu99]</span>
<span class="s1">'GCC_C_LANGUAGE_STANDARD'</span><span class="p">:</span> <span class="s1">'c11'</span><span class="p">,</span> <span class="c1"># C11</span>
<span class="c1">#'GCC_C_LANGUAGE_STANDARD': 'gnu11', # GNU11</span>
<span class="c1">#'GCC_C_LANGUAGE_STANDARD': 'compiler-default', # Compiler Default</span>
<span class="p">},</span>
</pre></div>
<h2><a name="compiling-with-libcxx-or-gnustd"></a>標準ライブラリとして libc++ または libstdc++ を使う</h2>
<p>Xcode 7 からは <code>CLANG_CXX_LIBRARY</code> の項目がなくなったため特に指定する必要はありません。</p>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'CLANG_CXX_LIBRARY': 'libstdc++',</span>
<span class="s1">'CLANG_CXX_LIBRARY'</span><span class="p">:</span> <span class="s1">'libc++'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="compiling-cxx11-or-cxx14"></a>C++11 または C++14 でコンパイルする</h2>
<p>Xcode で C++11 または C++14 を対象にする場合は、<code>CLANG_CXX_LANGUAGE_STANDARD</code> を指定します。</p>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'CLANG_CXX_LANGUAGE_STANDARD': 'c++0x',</span>
<span class="c1">#'CLANG_CXX_LANGUAGE_STANDARD': 'c++11',</span>
<span class="c1">#'CLANG_CXX_LANGUAGE_STANDARD': 'c++1y',</span>
<span class="s1">'CLANG_CXX_LANGUAGE_STANDARD'</span><span class="p">:</span> <span class="s1">'c++14'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<p>C++11 以降を利用する場合は、コンパイラに LLVM Clang を、C++ の標準ライブラリに libc++ を指定する必要があります。
また libc++ を使うには OS X 10.7 以上を利用する必要があるので注意してください。</p>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_VERSION'</span><span class="p">:</span> <span class="s1">'com.apple.compilers.llvm.clang.1_0'</span><span class="p">,</span>
<span class="s1">'CLANG_CXX_LANGUAGE_STANDARD'</span><span class="p">:</span> <span class="s1">'c++14'</span><span class="p">,</span>
<span class="s1">'CLANG_CXX_LIBRARY'</span><span class="p">:</span> <span class="s1">'libc++'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="CLANG_ENABLE_MODULES"></a>Enable Modules (C and Objective-C)</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'CLANG_ENABLE_MODULES': 'NO',</span>
<span class="s1">'CLANG_ENABLE_MODULES'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="CLANG_ENABLE_OBJC_ARC"></a>Objective-C の ARC を有効にする (Objective-C Automatic Reference Counting)</h2>
<p>Objective-C の ARC (Automatic Reference Counting) を有効にするには次のように <code>CLANG_ENABLE_OBJC_ARC</code> を <code>YES</code> に設定します。</p>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'CLANG_ENABLE_OBJC_ARC'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="ENABLE_NS_ASSERTIONS"></a>Enable Foundation Assertions</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'ENABLE_NS_ASSERTIONS'</span><span class="p">:</span> <span class="s1">'NO'</span><span class="p">,</span>
<span class="c1">#'ENABLE_NS_ASSERTIONS': 'YES',</span>
<span class="p">},</span>
</pre></div>
<p>Release ビルド時のみアサーションを無効にする場合は、次のようにします。</p>
<div class="highlight"><pre><span></span><span class="s1">'configurations'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'Release'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'ENABLE_NS_ASSERTIONS'</span><span class="p">:</span> <span class="s1">'NO'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
</pre></div>
<h2><a name="GCC_TREAT_WARNINGS_AS_ERRORS"></a>コンパイラの警告をエラーとして扱う (Warning Policies - Treat Warnings as Errors)</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_TREAT_WARNINGS_AS_ERRORS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<p>そのほかコンパイラの警告に関する設定 (Warning Policies) には以下の項目があります。</p>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1"># Inhibit All Warnings</span>
<span class="s1">'GCC_WARN_INHIBIT_ALL_WARNINGS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="c1"># Pedantic Warnings</span>
<span class="s1">'GCC_WARN_PEDANTIC'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="warnings-in-xcode"></a>コンパイラの警告を有効または無効にする (Warnings)</h2>
<p>Visual Studio ではコンパイラの警告レベルを指定することができましたが、Xcode では警告の内容ごとに細かく指定することができます。
<code>GCC_</code> のプリフィクスを持つ設定については GCC だけでなくコンパイラの LLVM Clang についても有効です。</p>
<h4><a name="warnings-all-languages"></a>言語共通の警告 (Warnings - All languages)</h4>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'CLANG_WARN_ASSIGN_ENUM'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'CLANG_WARN_BOOL_CONVERSION'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'CLANG_WARN_CONSTANT_CONVERSION'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'CLANG_WARN_DOCUMENTATION_COMMENTS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'CLANG_WARN_EMPTY_BODY'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'CLANG_WARN_IMPLICIT_SIGN_CONVERSION'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'CLANG_WARN_UNREACHABLE_CODE'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_64_TO_32_BIT_CONVERSION'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_ABOUT_MISSING_NEWLINE'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_ABOUT_MISSING_PROTOTYPES'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_CHECK_SWITCH_STATEMENTS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_FOUR_CHARACTER_CONSTANTS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_MISSING_PARENTHESES'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_NON_VIRTUAL_DESTRUCTOR'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_SHADOW'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_SIGN_COMPARE'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_TYPECHECK_CALLS_TO_PRINTF'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_UNKNOWN_PRAGMAS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_UNUSED_FUNCTION'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_UNUSED_LABEL'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_UNUSED_PARAMETER'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_UNUSED_VALUE'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_UNUSED_VARIABLE'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="c1">#'GCC_WARN_ABOUT_RETURN_TYPE': 'NO', # No</span>
<span class="c1">#'GCC_WARN_ABOUT_RETURN_TYPE': 'YES', # Yes</span>
<span class="s1">'GCC_WARN_ABOUT_RETURN_TYPE'</span><span class="p">:</span> <span class="s1">'YES_ERROR'</span><span class="p">,</span> <span class="c1"># Yes (treat as error)</span>
<span class="c1">#'GCC_WARN_UNINITIALIZED_AUTOS': 'NO', # No</span>
<span class="c1">#'GCC_WARN_UNINITIALIZED_AUTOS': 'YES', # Yes</span>
<span class="s1">'GCC_WARN_UNINITIALIZED_AUTOS'</span><span class="p">:</span> <span class="s1">'YES_AGGRESSIVE'</span><span class="p">,</span> <span class="c1"># Yes (Aggressive)</span>
<span class="c1"># Treat Missing Function Ptorotypes as Errors</span>
<span class="s1">'GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="c1"># Treat Incompatible Pointer Type Warnings as Errors</span>
<span class="s1">'GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h4><a name="warnings-cxx"></a>C++ に関する警告の設定 (Warnings - C++)</h4>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1"># Exit-Time C++ Destructors</span>
<span class="s1">'CLANG_WARN__EXIT_TIME_DESTRUCTORS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="c1"># Nonvirtual Destructor</span>
<span class="s1">'GCC_WARN_NON_VIRTUAL_DESTRUCTOR'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="c1"># Overloaded Virtual Functions</span>
<span class="s1">'GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="c1"># Undefined use of offsetof Macro</span>
<span class="c1">#'GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO': 'YES',</span>
<span class="c1"># Using C++11 extensions in earlier versions of C++</span>
<span class="s1">'CLANG_WARN_CXX0X_EXTENSIONS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h4><a name="warnings-objective-c"></a>Objective-C に関する警告の設定 (Warnings - Objective C)</h4>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'CLANG_WARN__DUPLICATE_METHOD_MATCH'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="c1">#'GCC_WARN_ALLOW_INCOMPLETE_PROTOCOL': 'YES',</span>
<span class="s1">'CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_STRICT_SELECTOR_MATCH'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'GCC_WARN_UNDECLARED_SELECTOR'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="c1">#'CLANG_WARN_DIRECT_OBJC_ISA_USAGE': 'NO', # No</span>
<span class="c1">#'CLANG_WARN_DIRECT_OBJC_ISA_USAGE': 'YES', # Yes</span>
<span class="s1">'CLANG_WARN_DIRECT_OBJC_ISA_USAGE'</span><span class="p">:</span> <span class="s1">'YES_ERROR'</span><span class="p">,</span> <span class="c1"># Yes (treat as error)</span>
<span class="c1">#'CLANG_WARN_OBJC_ROOT_CLASS': 'NO', # No</span>
<span class="c1">#'CLANG_WARN_OBJC_ROOT_CLASS': 'YES', # Yes</span>
<span class="s1">'CLANG_WARN_OBJC_ROOT_CLASS'</span><span class="p">:</span> <span class="s1">'YES_ERROR'</span><span class="p">,</span> <span class="c1"># Yes (treat as error)</span>
<span class="p">},</span>
</pre></div>
<h4><a name="warnings-objective-c-and-arc"></a>Objective-C と ARC に関する警告の設定 (Warnings - Objective C and ARC)</h4>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="s1">'CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="c1">#'CLANG_WARN__ARC_BRIDGE_CAST_NONARC': 'YES',</span>
<span class="p">},</span>
</pre></div>
<h2><a name="ASSETCATALOG_COMPILER_APPICON_NAME"></a>Asset Catalog App Icon Set Name</h2>
<div class="highlight"><pre><span></span><span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'ASSETCATALOG_COMPILER_APPICON_NAME'</span><span class="p">:</span> <span class="s1">'AppIcon'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2><a name="MTL_ENABLE_DEBUG_INFO"></a> Metal のデバッグ情報を有効または無効にする (MTL_ENABLE_DEBUG_INFO)</h2>
<p>Release デバッグ時のみ有効にする場合は、次のようにします。</p>
<div class="highlight"><pre><span></span><span class="s1">'configurations'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'Debug'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'MTL_ENABLE_DEBUG_INFO'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="s1">'Release'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'MTL_ENABLE_DEBUG_INFO'</span><span class="p">:</span> <span class="s1">'NO'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
</pre></div>
<h2>最後に</h2>
<p>一昨年の夏くらいでしょうか、Xcode や Cocoa のプロジェクトを GYP で作成する方法を簡単にまとめてみようと画策していたのですが、なかなか筆が進まず今日になってようやく公開することができました。</p>
<p>GYP を使わなくても、直接 Xcode のプロジェクトを管理したり、CocoaPods や CMake を使ったり、他にもいろいろなプロジェクトの進め方や管理方法があります。
Chromium プロジェクトについても GYP に代わるビルドツール <a href="https://chromium.googlesource.com/chromium/src/+/master/tools/gn/docs/cookbook.md">GN</a> に少しずつ置き換えられているようです。
とは言っても、JavaScript エンジンである V8 をはじめとして Node.js や Electron (Atom Shell) でまだまだ GYP が使われています。
また、今回は触れませんでしたが、最新の GYP では watchOS 向けのビルドターゲットもサポートしているようです。
この記事が、Mac のデスクトップアプリや Node.js モジュールを作成する際などにお役立ていただければ幸いです。</p>
<blockquote>
<p><strong>NOTE: GYP 以外のビルドツール・ライブラリマネージャーの選択肢</strong><br />
Xcode のプロジェクトファイルを作ったり、依存するライブラリを管理してくれるツールや方法はいくつかあります。
GYP, CMake, premake の他に Objective-C や Swift のプロジェクトに特化したライブラリマネージャー CocoaPods があります。
CocoaPods は、プロジェクトに必要なライブラリを簡単に追加することができ、依存するパッケージのアップデートに追従することができます。
また多くの Objective-C で書かれたライブラリ(例えば AFNetworking など)は、CocoaPods に対応しています。
CocoaPods はその性質上クロスプラットフォームではありませんが、Mac/iOS のみをターゲットにする場合は、今のところ最適な手段のように思えます。
Xcode プロジェクトに関しては他にも Carthage というライブラリマネージャーなどがあります。
GYP も含めて、各ビルドツール・ライブラリマネージャーについて広く選択肢を持っておくのは良さそうです。</p>
</blockquote>
<h2>参考文献</h2>
<ul>
<li><a href="https://chromium.googlesource.com/external/gyp/+/master/docs/UserDocumentation.md">GYP (Generate Your Projects) - User Documentation</a></li>
<li><a href="https://developer.apple.com/library/mac/documentation/DeveloperTools/Reference/XcodeBuildSettingRef/0-Introduction/introduction.html">Xcode Build Setting Reference</a></li>
</ul>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>ソースコードやリソースファイルについては <strong>Xcode > "Create a new Xcode project" > OS X > Cocoa Application</strong> から作成することができます。 <a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:2">
<p>Xcode 7 の ”Build Settings” の順に掲載しています。 <a class="footnote-backref" href="#fnref:2" rev="footnote" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
</ol>
</div>今週の進み具合 #19 - 趣味のゲームエンジン開発 2015 初夏2015-05-19T07:04:00+09:00mogemimitag:enginetrouble.net,2015-05-19:2015/05/gamedev-weekly-2015-0519.html<p>前回の日記から半年以上経ってしまいました。
前回の進捗日記から今の今まで一体何をしていたのかコミットログをさかのぼりながら振り返ってみます。</p>
<h2>進み具合 (2014年9月23日 - 5月19日)</h2>
<p>大きく分けて3つのことをやりました。</p>
<ul>
<li>いろいろ動かしてみたり、試してみたりしました</li>
<li>Windows (DirectX, OpenGL) でも動かせるようにしました</li>
<li>Travis CI, AppVeyor, Gitter, Trello などの外部サービスを利用し始めました</li>
</ul>
<h2>SSAO を実装しました</h2>
<p>詳しい話を書くと長くなるので、いまはスクリーンショットを載せておきます。
図はそれぞれ、SSAO 適用前 (図1) と SSAO 適用後 (図2) です。</p>
<p><img alt="(図 1) SSAO なし" src="https://enginetrouble.net/images/2015/05/ssao-off.png" /></p>
<p><em>(図 1) SSAO 適用前</em></p>
<p><img alt="(図 2) SSAO 適用後" src="https://enginetrouble.net/images/2015/05/ssao-on.png" /></p>
<p><em>(図 2) SSAO 適用後</em></p>
<p>SSAO については、いずれまとまったお話を書きたいところです。</p>
<h2>MagicaVoxel に対応しました</h2>
<p>去年の 10 月頃にサポートしたきり、日記にこの話を書いていなかったのを思い出しました。
MagicaVoxel が提供している VOX フォーマットに対応しました。
モデルの読み込みやレンダリング、VOX ファイルへの書き出しができるようになりました。
レンダリングについては、ハードウェアインスタンシングを使って、ボクセル1つ1つをリアルタイムに描画しています。
先ほど載せた SSAO のスクリーンショット (図1, 2 参照) でも使っています。</p>
<h2>Clang のバージョンが 3.4 => 3.6 になりました</h2>
<p>Xcode に含まれている Apple LLVM Compiler で採用されている Clang のバージョンが 3.6 ベースになりました。
前回の日記の時までは Clang 3.4 で C++1y オプションをつけてビルドしていましたが、現在は C++14 オプションをつけてビルドしています。</p>
<h2>Windows と Visual Studio への対応について</h2>
<p>実は Windows での実装はだいぶ早い時期に完了していました。昨年の 10 月頃には動いていたと思います。
いま作っているエンジンは Mac と Xcode を使って実装し始めましたが、元々 Win32 と DirectX でゲームを作っていたこともあって、過去の遺産がここで役に立っています。</p>
<p>Windows に対応する上で問題となったのは C++ コンパイラです。
Visual Studio はまだ完全に C++11/C++14 へ対応していません。
また警告やエラーの細かな振る舞いが Clang とは異なっています。
そのため Xcode 上で動いていたコードが Visual Studio ではコンパイルが通らないということが多々ありました。</p>
<p>Visual Studio については 14 CTP, 2015 Technical Preview, 2015 RC と使ってきて、少しずつ C++11/C++14 の機能が利用できることを確認しています。
ゲームエンジンのコアにあたるソースコードについては、(2015 年 5 月現在)最新の Visual Studio 2015 RC でビルドできるようになりました。
ゲームプログラムを書く分には、C++11/C++14 の対応状況はだいぶ良くなってきた印象を受けます。</p>
<h2>DirectInput と RawInputDevice</h2>
<p>これまで Win32 でキーボードとマウスまたはゲームパッドの入力を取得するために DirectInput を利用していました。
DirectInput はだいぶ前から DirectInput 8 を最後に更新が止まっており、代わりに XInput が登場しました。
XInput ではキーボードやマウスの入力は取得できず、ゲームパッドの入力のみとなっています。
<a href="https://msdn.microsoft.com/en-us/library/ee418864.aspx">DirectX 9 の時代にアナウンス</a>があったようで、「DirectInput を使用するのはゲームパッドやジョイスティックの入力を取るときのみにしてください」とのことです。
Win32 でキーボードやマウスの入力を取得するには代わりに RawInputDevice という API を利用します。</p>
<p>そういう経緯もあって、DirectInput で実装していたものをつい先日、WM_INPUT と RawInputDevice に置き換えました。</p>
<h2>Direct3D 11 と HLSL の対応</h2>
<p>Direct3D 11 で動くようになりました。
Direct3D 11 に対応するにあたって、HLSL によるカスタムシェーダもサポートしています。
コンパイル済みのバイナリと、HLSL で書かれたソースコードの文字列のどちらも利用できます。
もちろん HLSL の <code>#include</code> にも対応してます。
HLSL をコンパイルして、バイナリ (shader blob) を C++ のソースコードに埋め込めるツールも Python で用意しました。</p>
<h2>DirectX 12 を試しました</h2>
<p>ちょっと不安定ですが、なんとなく動いてます。
DirectX 12 の API 自体がころころ変わっているので、master ブランチにはマージしていません。</p>
<h2>BlendState, DepthStencilState, RasterizerState がなくなり、新たに PipelineState が加わりました</h2>
<p>BlendState, DepthStencilState, RasterizerState といった DirectX 11 世代ではおなじみだったレンダリングステートを廃止しました。
DirectX 12 の API に沿うように、新たに PipelineState を追加しました。
PipelineState は上記の BlendState, DepthStencilState, RasterizerState
の他に、各ステージごとのプログラマブルシェーダ(頂点シェーダやピクセルシェーダ、ジオメトリシェーダなど)や入力レイアウトなどを統合したものになります。
Pomdog はこれまで Direct3D 11 に沿ったグラフィックス API を提供してきましたが、今後は Direct3D 12 に沿うようになります。
もちろん今まで通り、Direct3D 11 や OpenGL 環境でもエミュレートして動くので、安心してください。</p>
<h2>GLEW を使いました</h2>
<p>GLEW を使って、Windows 環境でも OpenGL が利用できるようになりました。
よって、Windows 環境では今のところ OpenGL 3.3/4.0 と Direct3D 11 の両方が利用できます。
コンパイル時ではなくランタイム時に、どちらをレンダリングシステムとして使うか選択することができます。
もちろん、ビルドするときに OpenGL と Direct3D のどちらかをビルドから外すこともできます。</p>
<p>また、X11 環境で OpenGL を使う場合も GLEW を使うことになります。</p>
<h2>X11 で動かしてみました</h2>
<p>現在、Windows では Win32, Mac OS X では Cocoa というウィンドウシステムまたはプラットフォームを利用しています。
Linux デスクトップ環境で動かす場合は、X11 を(おそらくほとんどの場合)利用することになります。
X11 の API 自体は Mac でも利用することが可能で XQuartz という実装が用意されています。</p>
<p>この X11 を動かせるかどうか試してみました。
こちらは、OpenGL 3 のコンテキストさえ作ることができれば、問題なく動かすことができました。
X11 自体は Win32 によく似ています。OpenGL コンテキストの作成も非常に既視感がありました。
問題が出てくるとしたら、動作環境を作るのが少し大変なところでしょうか。
OS X の XQuartz では OpenGL 2 までのコンテキストしか利用できません。
また、VirtualBox などを使って作った仮想の Ubuntu 環境でも OpenGL のドライバーが 3 以降に対応していません。</p>
<p>X11 と OpenGL 3.3/4.0 を使ってゲームを作るには、適当なラップトップかデスクトップに Linux をインストールする必要がありそうです。
Intel の Linux 向けグラフィックスドライバーは現在のところ OpenGL 3.3 にしか対応していないので、4.0 の機能をゲームで使うのはちょっと難しそうです。
ちなみに NVIDIA と AMD による Linux 向けのドライバーは 4 以降に対応しています。</p>
<p>X11 向けの実装は master ブランチにはまだマージしていないので、GitHub に公開しているリポジトリには含まれていません。
気が向いたら、Ubuntu でもゲームが動くようにしてみたいと思います。</p>
<h2>OpenAL と XAudio2 で実装しました</h2>
<p>対応しました。Windows でも Mac でも音が鳴ります。
一部オーディオリスナーやエミッターといった 3D オーディオの機能にも対応しています。</p>
<h2>CoreAudio を試しました</h2>
<p>OpenAL は非常に使い勝手のいい API で実装も大変楽でしたが、その分制約も厳しく、使えるフォーマットやチャンネル数などが限られていました。
そこで Mac の CoreAudio に含まれる AudioUnit.framwork を使ってみることにしました。
オーディオグラフを作成することができ、OpenAL に比べてより詳細な操作ができます。
ただし、実装しなければいけないことがちょっと多いです。これについては、少しずつ実装中です。</p>
<h2>Cocoa と OpenGL のスレッド間の処理が安定するようになりました</h2>
<p>Mac でゲームを動かすことになると、Cocoa の NSView が動いているメインスレッドとは別に、60 fps で描画するために毎フレーム、ゲームの処理を呼び出すスレッド(描画スレッド)を使うことになります。
この描画スレッドは Cocoa が提供している <code>NSThread</code> や C++11 の <code>std::thread</code> を使って while と sleep を使って実装してもかまいません。
60 fps 以上で動かしたい場合は、これらの方法が適していると思います。
今回は 60 fps または 30 fps で動かし、オーバードローを起こさないように Cocoa の <code>CVDisplayLink</code> を使ってディスプレイの更新に同期するようにしました。</p>
<p>OpenGL コンテキストを lock したり、コンテキストに描画先となる NSView を指定したり、リソースに mutex をつけたり、ついつい忘れがちになる処理が多くて安定するまで骨が折れました。
この NSView と OpenGL のスレッド間の話についてはまたどこかでお話したいところです。</p>
<blockquote>
<p><strong>NOTE</strong><br />
Mac OS X と Cocoa を使ってゲームを開発する場合に、方法によってはメインスレッドだけで完結することができます。
エントリーポイントとなる main 関数で <code>NSApplicationMain</code> を呼ばずにプログラム側で NSWindow や NSView を作り、自力でメインループを呼び出すことで描画スレッドをたてずに済みます。
例として SDL2 や SFML, GLFW がそのような実装になっています。
こうすると XIB ファイルを用意する必要がないので、ライブラリやフレームワークを使い始めやすい利点があります。
ただし、Interface Builder との連携が難しくなる短所もあります。</p>
</blockquote>
<h2>NSFont によるフォントレンダリング</h2>
<p>フォントのレンダリングをいろいろ試していました。
日本語のように使用する文字の数が多く、漢字のように文字の形が複雑だと、レンダリングは、それはもう大変です。
Windows だと DirectWrite を使って高品質なフォントのレンダリングができますが、Mac では NSFont と Core Graphics の機能を利用することで実現できます。
あるいは、freetype2 を使うのもいいかもしれません。</p>
<h2>iOS と OpenGL ES 3.0 対応</h2>
<p>iOS で動かせるかどうか試してみました。なんとなく動きました。
ただ OpenGL ES 3.0 でジオメトリインスタンシングやサンプラーステートに対応したとはいえ、
どこまでいってもモバイルをターゲットにするというのは制約が強く、デスクトップ向けのゲームに比べて少し大変そうでした。
今は Windows と Mac OS X で安定した API と実装を提供することがひとまずの目標なので、モバイル対応についてはひとまず置いておきます。</p>
<h2>Metal 始めてみました</h2>
<p>これまたなんとなくゲームエンジン上で動かせそうでした。
OpenGL ES 3.0 よりは Metal のほうが DirectX 12 が提供している機能と似ているところがあって、実装しやすかったです。
ただ、signing しないとビルドすらできないということがわかったので、そっと Xcode を閉じました。
エミュレータでは動作せず、iPhone 5S や iPhone 6 以上の実機がないと動かせないこともわかったので「これにておさらば」といったところです。</p>
<h2>UTF-8 BOM をソースコードから消しました</h2>
<p>日本語コメントに対応するためにソースコードの先頭に UTF-8 BOM をつけていました。
そうすることで、Visual Studio では UTF-8 で書かれたソースコードだと正しく認識してくれます。
BOM をつけないと日本語環境の Visual Studio では Shift-JIS で読み込まれることもあり、読み込み時や保存時に大変なことになります。
特に Xcode やそのほかのエディタで読み込むと、頻繁に文字化けを起こします。
BOM をつけていて大変だったのは、Xcode が保存時に余計なおせっかいで BOM を削除することでした。
そこで BOM のない UTF-8 のソースコードに対して BOM を自動的につける Python スクリプトを書いて動かしていましたが、これらをすっぱりやめ C++ のソースコードについて BOM をつけないようにしました。
BOM を外したかわりに、日本語(英語以外)によるコメントアウトをすべて削除しました。</p>
<p>日本語のコメントアウトは主に、Doxygen 用のドキュメンテーションコメントでした。
Doxygen では <code>@~English</code> や <code>@~Japanese</code> でドキュメントを多言語に対応することができます。
英語や日本語以外にドキュメントで扱う言語が増えていく予定は今のところありませんが、もし今後、中国語やロシア語、アラビア語などに対応していくなら、と考えた時に
Doxygen が提供するこのドキュメンテーションコメントの記述の仕方はあまり現実的ではないと思いました。
ゲームエンジンのソースコード自体の更新頻度とドキュメントのローカライズの更新頻度も一致しないので、C++ のソースコードに多言語によるコメントを含めるべきではないと至りました。</p>
<h2>ハードタブからソフトタブにしました</h2>
<p>Linux Kernel も Go 言語もインデントはハードタブですが、調べてみたところ C++ のプロジェクトの大半がインデントにスペース(ソフトタブ)を用いているようです。
長いものに巻かれる思いで、ハードタブから 4 スペースにすべて置換しました。
こういった変更はコミットログが非常に大きくなるので、早い段階でインデントやコーディングスタイルについては取り決めをしておいたほうがよさそうです。</p>
<h2>Clang Format 始めました</h2>
<p>Clang Format を使うことにしました。
Clang Format はその名の通りフォーマッターです。C++ だけでなく Java や JavaScript にも対応しています。
リポジトリのルートディレクトリに <code>.clang-format</code> ファイルとして、コーディングスタイルを記述しておくことで、そのスタイルに沿ってソースコードをフォーマットしてくれます。</p>
<p>C++ の良いところであり悪いところは、コーディングスタイルが言語仕様(あるいはそれに相当するコミュニティ内)で決まってないところです。
関数名は Upper Camel Case (UCC) か LCC にするのか、メンバー変数名はアンダースコアをつけるべきか、UCC にするべきか、プリフィクスに <code>m_</code> をつけるべきか、といった些細なことや、
インデントをハードタブにするかスペースにするかといったことや、const は型の右につけるのか左につけるのかといったことなど細かいところを挙げればきりがありません。</p>
<p>C++ で開発しているオープンソースリポジトリについてあちらこちら覗いてみても、スタイルに関しては各プロジェクトごと多種多様です。
ポインタやリファレンスの位置は右に寄せるか左に寄せるかといった本当に些細なスタイルを文書化するのはとても大変です。
<code>.clang-format</code> ファイルでは、そういった細かなスタイルについて記述することができるので、ドキュメントとして役立ちます。
フォーマットも機械的に行ってくれるので、些細なコーディングスタイルについて争うことがほとんどなくなります。</p>
<h2>Travis CI と AppVeyor を使い始めました</h2>
<p>GitHub で公開しているリポジトリに push するたびに Windows と Mac 向けのビルドと単体テストを実行するようにしました。
特に Visual Studio と Xcode の両方でビルドが通るかどうかをコミット毎に確認するのは非常に大変です。
そこで、Windows 向けのビルドに AppVeyor を、Mac 向けのビルドに Travis を利用することにしました。
どちらもオープンソースプロジェクトだと無料で利用できます。
AppVeyor は Visual Studio の最新版を含めた各バージョンや PowerShell が使えるので重宝しております。
Travis は Xcode を利用することができ、またタスクが実行されるのが速く、すぐに結果を通知してくれるので大変ありがたいです。</p>
<h2>Gitter のチャットルームを作りました</h2>
<p>GitHub の Issues や Pull Requests の機能以外で、質問とか雑談とかできる場所が欲しかったので <a href="https://gitter.im/mogemimi/pomdog">Gitter</a> を使ってみることにしました。
freenode で IRC チャンネルを作るか、どこかにフォーラムを作るか迷いましたが Gitter で落ち着きました。
GitHub のアカウントがあればどなたでも参加できます。ぜひ書き込んでみてください。</p>
<h2>Trello のボードを公開しました</h2>
<p>今どんな作業をしているのか、あるいは今後どんな機能を追加する予定があるのかといった情報を目に見える形にするため、<a href="https://trello.com/b/lqd3nwrK/pomdog-game-engine">Trello のボード</a> としてロードマップを公開しました。
優先的に追加してほしい機能について誰でも投票ができるようになっています。よかったら投票してみてください。</p>
<h2>今週の予定</h2>
<p>だいぶ駆け足になりました。遅筆なのでこの日記を書くのも少し苦労しました。
本来はやっている最中やその直後にノートとして日記をとるのが理想ですが、なかなかうまくいってないというのが本当のところです。
こまめに日記をつけるように(できるだけ)努めていきたいです。</p>
<p>今週は、ゲームエンジンをコンパイルする時に Visual Studio で出ている警告をすべて取り除く予定です。
もっぱら、先週末は、Qt を使ってエンジンとは関係のない趣味のデスクトップアプリを作っていたので、今度は Qt の話を書けたらいいなあ、と。</p>Qt 5.4 ことはじめ #22015-03-19T23:29:00+09:00mogemimitag:enginetrouble.net,2015-03-19:2015/03/qt-devlog-2015-0319.html<p><a href="/2015/03/qt-devlog-2015-0309.html">前回の日記</a>に続き、Qt を触っていました。
Qt 5.4 で C++ のコーディングをしていたので忘れないうちにノートしておきます。
今回やったことは:</p>
<ul>
<li>C++14 に対応する</li>
<li>Qt でプラットフォーム特有のコードを書く</li>
<li>Qt のバージョンを識別する</li>
<li>QMake でインクルードパスの指定</li>
<li>QMake に依存するライブラリとして libcurl (cURL) を追加する</li>
<li>Qt Network を使う</li>
<li>QTimer::timeout のシグナルを一回だけ呼ばれるようにする</li>
<li>QString から UTF-8 の std::string に変換する</li>
<li>Qt で JSON をパーシングする</li>
<li>Qt のアサーション</li>
<li>C++ から QML のオブジェクトを取得する</li>
</ul>
<h2>C++14 に対応する</h2>
<p>Qt 5.4 または Qt Creator で C++14 に対応するには次のように QMake ファイル (<code>.pro</code>) に追加します。</p>
<div class="highlight"><pre><span></span>CONFIG += c++14
</pre></div>
<p>同様に C++11 の場合は:</p>
<div class="highlight"><pre><span></span>CONFIG += c++11
</pre></div>
<p>ただし、OS X の場合、これだけでは C++14 を使うことができません。
以下の行を QMake ファイルに追加する必要があります。</p>
<div class="highlight"><pre><span></span>QMAKE_MACOSX_DEPLOYMENT_TARGET=10.9
</pre></div>
<p><code>QMAKE_MACOSX_DEPLOYMENT_TARGET</code> は アプリケーションの動作環境を OS X 10.9 以上に指定するビルド設定です。これは Xcode で言えば <code>MACOSX_DEPLOYMENT_TARGET</code> に相当します。</p>
<blockquote>
<p><strong>Note:</strong><br />
Qt に限らず、例えば libc++ を使う場合は OS X 10.7 以上でないと Xcode でビルドできません。今回は C++14 を使うため 10.9 を指定しています。</p>
</blockquote>
<h2>Qt でプラットフォーム特有のコードを書く</h2>
<p>Qt にはプラットフォーム特有の機能にアクセスする手段が用意されています。
プラットフォームごとのコードは次のように書くことができます:</p>
<div class="highlight"><pre><span></span><span class="cp">#ifdef Q_WS_WIN</span>
<span class="c1">// Windows</span>
<span class="cp">#endif</span>
<span class="cp">#ifdef Q_WS_MAC</span>
<span class="c1">// Mac OS X</span>
<span class="cp">#endif</span>
<span class="cp">#ifdef Q_WS_X11</span>
<span class="c1">// X11</span>
<span class="cp">#endif</span>
<span class="cp">#ifdef Q_WS_QWS</span>
<span class="c1">// QWS</span>
<span class="cp">#endif</span>
</pre></div>
<p>WS は使用しているウィンドウシステム (Window System) を示しています。
<code>Q_WS_QWS</code> というのは、組み込み Linux 向けの Qt Window System (QWS) のときに有効になるプリプロセッサです。</p>
<p>ウィンドウシステムではなく OS を識別するには同様に:</p>
<div class="highlight"><pre><span></span><span class="cp">#ifdef Q_OS_WIN32</span>
<span class="c1">// Windows</span>
<span class="cp">#endif</span>
<span class="cp">#ifdef Q_OS_MAC</span>
<span class="c1">// Mac OS (= Darwin)</span>
<span class="cp">#endif</span>
<span class="cp">#ifdef Q_OS_LINUX</span>
<span class="c1">// Linux</span>
<span class="cp">#endif</span>
</pre></div>
<p>他にも <code>Q_OS_UNIX</code> や <code>Q_OS_OPENBSD</code>, <code>Q_OS_CYGWIN</code> などが識別可能です。OS に関しては環境によって複数定義されるようです。例えば次のコードは Mac OS X で有効です。</p>
<div class="highlight"><pre><span></span><span class="cp">#if defined(Q_OS_UNIX) && defined(Q_OS_MAC) && defined(Q_OS_DARWIN)</span>
<span class="n">qDebug</span><span class="p">()</span> <span class="o"><<</span> <span class="s">"This platform is unix"</span><span class="p">;</span>
<span class="cp">#endif</span>
</pre></div>
<h2>Qt のバージョンを識別する</h2>
<p><code>QT_VERSION</code> を使って、使用している Qt のバージョンを識別することができます。次の例では Qt のバージョンが 5.4.1 以上かどうかを判断しています。</p>
<div class="highlight"><pre><span></span><span class="cp">#if QT_VERSION >= 0x050401</span>
<span class="n">qDebug</span><span class="p">()</span> <span class="o"><<</span> <span class="n">QString</span><span class="o">::</span><span class="n">number</span><span class="p">(</span><span class="n">QT_VERSION</span><span class="p">,</span> <span class="mi">16</span><span class="p">);</span>
<span class="cp">#endif</span>
</pre></div>
<h2>QMake でインクルードパスの指定</h2>
<p>Qt が提供するモジュール以外のライブラリを使うことがあります。インクルードパスを通すには QMake ファイルに次のように指定します。</p>
<div class="highlight"><pre><span></span>INCLUDEPATH += path/to/rapidjson/include
</pre></div>
<p>複数のパスを指定する場合は:</p>
<div class="highlight"><pre><span></span>INCLUDEPATH += path/to/rapidjson/include path/to/entityx/include
</pre></div>
<p>また空白 (white space) を含む行を指定する場合は次のように指定します:</p>
<div class="highlight"><pre><span></span>INCLUDEPATH += "path/to/my headers"
</pre></div>
<p>プラットフォームごとにパスを指定することもできます。次の例は、win32 と unix 環境でパスを指定しています。</p>
<div class="highlight"><pre><span></span><span class="n">win32</span><span class="o">:</span><span class="n">INCLUDEPATH</span> <span class="o">+=</span> <span class="n">C</span><span class="o">:/</span><span class="n">path</span><span class="sr">/to/i</span><span class="n">nclude</span>
<span class="n">unix</span><span class="o">:</span><span class="n">INCLUDEPATH</span> <span class="o">+=</span> <span class="n">usr</span><span class="o">/</span><span class="k">include</span>
</pre></div>
<h2>QMake に依存するライブラリとして libcurl (cURL) を追加する</h2>
<p>QMake ファイル (<code>.pro</code>) の <code>LIBS</code> に <code>-lcurl</code> を追加します。</p>
<div class="highlight"><pre><span></span>LIBS += -lcurl
</pre></div>
<p>ライブラリパスが通ってない環境も考えられます。例えば Win32 環境などに対応するには次のように記述します。</p>
<div class="highlight"><pre><span></span>win32 {
lessThan(QT_MAJOR_VERSION, 5) {
INCLUDEPATH += /path/to/curl
}
} else {
LIBS += -lcurl
}
</pre></div>
<p>これで Qt Creator 上で libcurl が使えようになります。試しに次のコードで動作確認できます。</p>
<div class="highlight"><pre><span></span><span class="k">extern</span> <span class="s">"C"</span> <span class="p">{</span>
<span class="cp">#include</span> <span class="cpf"><curl/curl.h></span><span class="cp"></span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
<span class="n">CURL</span><span class="o">*</span> <span class="n">curl</span> <span class="o">=</span> <span class="n">curl_easy_init</span><span class="p">();</span>
<span class="n">curl_easy_cleanup</span><span class="p">(</span><span class="n">curl</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>別途 libcurl を追加せずとも、実のところ Qt にはネットワークライブラリとして <a href="http://doc.qt.io/qt-5/qtnetwork-index.html"><strong>Qt Network</strong></a> が用意されています。</p>
<h2>Qt Network を使う</h2>
<p>Qt 5 にはネットワークプログラミングを簡単に扱うことができる <a href="http://doc.qt.io/qt-5/qtnetwork-module.html">Qt Network モジュール</a> が用意されています。</p>
<p>HTTP, TCP, UDP, SSL とよく使われるプロトコルはサポートしているようです。
何が嬉しいかというと、Qt 5 の開発環境さえ入れていればこの Qt Network の機能がどこでも使えることです。
ビルドするときに、あれがない・これがないと悩む必要はありません。Qt SDK へのファイルパスがわかれば、依存関係は QMake が解決してくれます。ポータビリティに優れています。</p>
<p>Qt Network モジュールを使うには QMake に <code>network</code> を追加します。</p>
<div class="highlight"><pre><span></span>QT += network
</pre></div>
<h2>QTimer::timeout のシグナルを一回だけ呼ばれるようにする</h2>
<p><code>QTimer</code> クラスを使うと簡単にネットワークのタイムアウト処理などを実装することができます。<code>QTimer::timeout</code> のシグナルでイベントを受け取ることができます。デフォルトでは <code>QTimer::setInterval</code> で指定したインターバル毎に何度もこのシグナルは呼び出されます。一度だけ呼び出されるようにするには <code>QTimer::setSingleShot(true)</code> を指定します。例えば次のようになります。</p>
<div class="highlight"><pre><span></span><span class="n">QTimer</span><span class="o">*</span> <span class="n">timer</span> <span class="o">=</span> <span class="k">new</span> <span class="n">QTimer</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
<span class="n">timer</span><span class="o">-></span><span class="n">setInterval</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">chrono</span><span class="o">::</span><span class="n">milliseconds</span><span class="p">(</span><span class="mi">100</span><span class="p">).</span><span class="n">count</span><span class="p">());</span>
<span class="n">timer</span><span class="o">-></span><span class="n">setSingleShot</span><span class="p">(</span><span class="nb">true</span><span class="p">);</span>
<span class="n">connect</span><span class="p">(</span><span class="n">timer</span><span class="p">,</span> <span class="o">&</span><span class="n">QTimer</span><span class="o">::</span><span class="n">timeout</span><span class="p">,</span> <span class="p">[</span><span class="o">=</span><span class="p">]{</span>
<span class="n">qDebug</span><span class="p">()</span> <span class="o"><<</span> <span class="s">"Timeout"</span><span class="p">;</span>
<span class="p">});</span>
<span class="n">timer</span><span class="o">-></span><span class="n">start</span><span class="p">();</span>
</pre></div>
<h2>QString から UTF-8 の std::string に変換する</h2>
<p><code>QString</code> を UTF-8 の std::string に変換します。</p>
<div class="highlight"><pre><span></span><span class="n">QString</span> <span class="n">source</span><span class="p">;</span>
<span class="n">QByteArray</span> <span class="n">byteArray</span> <span class="o">=</span> <span class="n">source</span><span class="p">.</span><span class="n">toUtf8</span><span class="p">();</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">text</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="p">(</span><span class="n">byteArray</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span> <span class="n">byteArray</span><span class="p">.</span><span class="n">size</span><span class="p">());</span>
</pre></div>
<h2>Qt で JSON をパーシングする</h2>
<p><a href="http://doc.qt.io/qt-5/qjsondocument.html">QJsonDocument</a> を使うと JSON をパーシングしたり、JSON オブジェクトに変換したりすることができます。</p>
<div class="highlight"><pre><span></span><span class="n">QString</span> <span class="n">jsonString</span><span class="p">;</span>
<span class="n">QJsonParseError</span> <span class="n">parseError</span><span class="p">;</span>
<span class="k">auto</span> <span class="n">jsonDocument</span> <span class="o">=</span> <span class="n">QJsonDocument</span><span class="o">::</span><span class="n">fromJson</span><span class="p">(</span><span class="n">jsonString</span><span class="p">,</span> <span class="o">&</span><span class="n">parseError</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">parseError</span><span class="p">.</span><span class="n">error</span> <span class="o">!=</span> <span class="n">QJsonParseError</span><span class="o">::</span><span class="n">NoError</span><span class="p">)</span> <span class="p">{</span>
<span class="n">qDebug</span><span class="p">()</span> <span class="o"><<</span> <span class="s">"Error: invalid Json format"</span><span class="p">;</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<h2>Qt のアサーション</h2>
<p>Qt にはデバッグ時の簡易テストとして <code>Q_ASSERT</code> が用意されています。</p>
<div class="highlight"><pre><span></span><span class="n">Q_ASSERT</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="p">(</span><span class="s">"42"</span><span class="p">)</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">to_string</span><span class="p">(</span><span class="mi">42</span><span class="p">));</span>
</pre></div>
<p>リリースビルドなどにアサートを含めない場合は、<code>QT_NO_DEBUG</code> を定義します。</p>
<h2>C++ から QML のオブジェクトを取得する</h2>
<p>C++ から取得したい Qt Quick オブジェクトに QML 上で <code>objectName</code> プロパティを追加します。</p>
<div class="highlight"><pre><span></span><span class="nx">ApplicationWindow</span> <span class="p">{</span>
<span class="k">objectName:</span> <span class="s2">"MyWindow"</span>
<span class="k">title:</span> <span class="nx">qsTr</span><span class="p">(</span><span class="s2">"himarinkolshizukuesu"</span><span class="p">)</span>
<span class="p">}</span>
</pre></div>
<p>C++ から QML オブジェクトを見つけるには <code>QQmlApplicationEngine::rootObjects()</code> と <code>Object::objectName()</code> を使います。
次の例では、<code>main.qml</code> から <code>MyWindow</code> オブジェクトを取得して、<code>title</code> プロパティの値を変更しています。</p>
<div class="highlight"><pre><span></span><span class="n">QQmlApplicationEngine</span> <span class="n">engine</span><span class="p">;</span>
<span class="n">engine</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">QUrl</span><span class="p">(</span><span class="n">QStringLiteral</span><span class="p">(</span><span class="s">"qrc:/main.qml"</span><span class="p">)));</span>
<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="nl">rootObject</span><span class="p">:</span> <span class="n">engine</span><span class="p">.</span><span class="n">rootObjects</span><span class="p">())</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">rootObject</span><span class="o">-></span><span class="n">objectName</span><span class="p">()</span> <span class="o">==</span> <span class="s">"MyWindow"</span><span class="p">)</span> <span class="p">{</span>
<span class="n">rootObject</span><span class="o">-></span><span class="n">setProperty</span><span class="p">(</span><span class="s">"title"</span><span class="p">,</span> <span class="s">"beam"</span><span class="p">);</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>オブジェクトが見つからない場合、または子オブジェクトを取ってくる場合は <code>QObject::findChild<T></code> で子オブジェクトを探します。</p>
<div class="highlight"><pre><span></span><span class="n">QObject</span><span class="o">*</span> <span class="n">rootObject</span> <span class="o">=</span> <span class="n">engine</span><span class="p">.</span><span class="n">rootObjects</span><span class="p">().</span><span class="n">front</span><span class="p">();</span>
<span class="n">QObject</span><span class="o">*</span> <span class="n">qmlObject</span> <span class="o">=</span> <span class="n">rootObject</span><span class="o">-></span><span class="n">findChild</span><span class="o"><</span><span class="n">QObject</span><span class="o">*></span><span class="p">(</span><span class="s">"MyWindow"</span><span class="p">);</span>
</pre></div>
<p>例えば次のような関数を定義しておくと、QML のルートのほうにあるオブジェクトの中から任意の名前のオブジェクトを見つけることができます:</p>
<div class="highlight"><pre><span></span><span class="k">auto</span> <span class="n">findFromQml</span> <span class="o">=</span> <span class="p">[](</span><span class="n">QQmlApplicationEngine</span> <span class="o">&</span> <span class="n">engine</span><span class="p">,</span> <span class="n">QString</span> <span class="k">const</span><span class="o">&</span> <span class="n">objectName</span><span class="p">)</span><span class="o">-></span> <span class="n">QObject</span><span class="o">*</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="nl">rootObject</span><span class="p">:</span> <span class="n">engine</span><span class="p">.</span><span class="n">rootObjects</span><span class="p">())</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">rootObject</span><span class="o">-></span><span class="n">objectName</span><span class="p">()</span> <span class="o">==</span> <span class="n">objectName</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">rootObject</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">QObject</span><span class="o">*</span> <span class="n">qmlObject</span> <span class="o">=</span> <span class="n">rootObject</span><span class="o">-></span><span class="n">findChild</span><span class="o"><</span><span class="n">QObject</span><span class="o">*></span><span class="p">(</span><span class="n">objectName</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">qmlObject</span> <span class="o">!=</span> <span class="k">nullptr</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">qmlObject</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">nullptr</span><span class="p">;</span>
<span class="p">};</span>
<span class="n">QObject</span><span class="o">*</span> <span class="n">mainWindow</span> <span class="o">=</span> <span class="n">findFromQml</span><span class="p">(</span><span class="n">engine</span><span class="p">,</span> <span class="s">"MyWindow"</span><span class="p">);</span>
<span class="n">mainWindow</span><span class="o">-></span><span class="n">setProperty</span><span class="p">(</span><span class="s">"title"</span><span class="p">,</span> <span class="s">"mybeam"</span><span class="p">);</span>
</pre></div>
<h2>ListView の Model を C++ で記述する</h2>
<p>Qt Quick ではビュー (QML) で表示したいデータを <strong>モデル</strong> (<code>model</code>) として指定することができます。
QML 上で次のようにモデルを定義します。</p>
<div class="highlight"><pre><span></span><span class="nx">ListModel</span> <span class="p">{</span>
<span class="kd">id: myModel</span>
<span class="nx">ListElement</span> <span class="p">{</span>
<span class="k">name:</span> <span class="s2">"himarinko"</span>
<span class="k">color:</span> <span class="s2">"red"</span>
<span class="p">}</span>
<span class="nx">ListElement</span> <span class="p">{</span>
<span class="k">name:</span> <span class="s2">"l"</span>
<span class="k">color:</span> <span class="s2">"green"</span>
<span class="p">}</span>
<span class="nx">ListElement</span> <span class="p">{</span>
<span class="k">name:</span> <span class="s2">"shizukuesu"</span>
<span class="k">color:</span> <span class="s2">"blue"</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>上のデータを ListView で表示するには次のように QML 上で <code>model</code> プロパティを指定します。</p>
<div class="highlight"><pre><span></span>ListView {
id: myListView
model: myModel
delegate: Item {
Row {
id: row1
spacing: 10
Rectangle {
width: 40
height: 40
color: model.modelData.color
}
Text {
text: model.modelData.name
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
</pre></div>
<p>このモデルは、C++ から指定することもできます。</p>
<div class="highlight"><pre><span></span><span class="n">QQmlApplicationEngine</span> <span class="n">engine</span><span class="p">;</span>
<span class="n">QList</span><span class="o"><</span><span class="n">QObject</span><span class="o">*></span> <span class="n">dataList</span><span class="p">;</span>
<span class="n">dataList</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="k">new</span> <span class="n">MyDataObject</span><span class="p">(</span><span class="s">"himarinko"</span><span class="p">,</span> <span class="s">"red"</span><span class="p">));</span>
<span class="n">dataList</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="k">new</span> <span class="n">MyDataObject</span><span class="p">(</span><span class="s">"l"</span><span class="p">,</span> <span class="s">"green"</span><span class="p">));</span>
<span class="n">dataList</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="k">new</span> <span class="n">MyDataObject</span><span class="p">(</span><span class="s">"shizukuesu"</span><span class="p">,</span> <span class="s">"blue"</span><span class="p">));</span>
<span class="k">auto</span> <span class="n">context</span> <span class="o">=</span> <span class="n">engine</span><span class="p">.</span><span class="n">rootContext</span><span class="p">();</span>
<span class="n">context</span><span class="o">-></span><span class="n">setContextProperty</span><span class="p">(</span><span class="s">"myModel"</span><span class="p">,</span> <span class="n">QVariant</span><span class="o">::</span><span class="n">fromValue</span><span class="p">(</span><span class="n">dataList</span><span class="p">));</span>
<span class="n">engine</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">QUrl</span><span class="p">(</span><span class="n">QStringLiteral</span><span class="p">(</span><span class="s">"qrc:/main.qml"</span><span class="p">)));</span>
</pre></div>
<p>QML での <code>ListElement</code> にあたるのは、この例では <code>MyDataObject</code> になります。
定義例を次に示します。</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">MyDataObject</span> <span class="o">:</span> <span class="k">public</span> <span class="n">QObject</span> <span class="p">{</span>
<span class="n">Q_OBJECT</span>
<span class="k">public</span><span class="o">:</span>
<span class="k">explicit</span> <span class="n">MyDataObject</span><span class="p">(</span><span class="n">QObject</span> <span class="o">*</span><span class="n">parent</span> <span class="o">=</span> <span class="mi">0</span><span class="p">);</span>
<span class="o">~</span><span class="n">MyDataObject</span><span class="p">();</span>
<span class="n">MyDataObject</span><span class="p">(</span><span class="n">QString</span> <span class="k">const</span><span class="o">&</span> <span class="n">nameIn</span><span class="p">,</span> <span class="n">QString</span> <span class="k">const</span><span class="o">&</span> <span class="n">colorIn</span><span class="p">)</span>
<span class="o">:</span> <span class="n">name_</span><span class="p">(</span><span class="n">nameIn</span><span class="p">),</span> <span class="n">color_</span><span class="p">(</span><span class="n">colorIn</span><span class="p">)</span>
<span class="p">{}</span>
<span class="n">Q_PROPERTY</span><span class="p">(</span><span class="n">QString</span> <span class="n">name</span> <span class="n">READ</span> <span class="n">name</span> <span class="n">WRITE</span> <span class="n">setName</span> <span class="n">NOTIFY</span> <span class="n">nameChanged</span><span class="p">)</span>
<span class="n">Q_PROPERTY</span><span class="p">(</span><span class="n">QString</span> <span class="n">color</span> <span class="n">READ</span> <span class="n">color</span> <span class="n">WRITE</span> <span class="n">setColor</span> <span class="n">NOTIFY</span> <span class="n">colorChanged</span><span class="p">)</span>
<span class="k">public</span><span class="o">:</span>
<span class="n">QString</span> <span class="k">const</span><span class="o">&</span> <span class="n">name</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">name_</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">setName</span><span class="p">(</span><span class="n">QString</span> <span class="k">const</span><span class="o">&</span> <span class="n">nameIn</span><span class="p">)</span> <span class="p">{</span>
<span class="n">name_</span> <span class="o">=</span> <span class="n">nameIn</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">QString</span> <span class="k">const</span><span class="o">&</span> <span class="n">color</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">color_</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">setColor</span><span class="p">(</span><span class="n">QString</span> <span class="k">const</span><span class="o">&</span> <span class="n">colorIn</span><span class="p">)</span> <span class="p">{</span>
<span class="n">color_</span> <span class="o">=</span> <span class="n">colorIn</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">private</span><span class="o">:</span>
<span class="n">QString</span> <span class="n">name_</span><span class="p">;</span>
<span class="n">QString</span> <span class="n">color_</span><span class="p">;</span>
<span class="nl">signals</span><span class="p">:</span>
<span class="kt">void</span> <span class="n">nameChanged</span><span class="p">();</span>
<span class="kt">void</span> <span class="nf">colorChanged</span><span class="p">();</span>
<span class="k">public</span> <span class="nl">slots</span><span class="p">:</span>
<span class="p">};</span>
</pre></div>
<h2>参考文献</h2>
<ul>
<li><a href="http://doc.qt.io/qt-5/exportedfunctions.html">Platform-Specific Functions</a></li>
<li><a href="http://doc.qt.io/qt-5.4/qmake-variable-reference.html">Variables | qmake Manual</a></li>
<li><a href="http://doc.qt.io/qt-5/qtglobal.html">QtGlobal - Global Qt Declarations</a></li>
<li><a href="http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html">Using C++ Models with Qt Quick Views</a></li>
</ul>Qt 5.4 ことはじめ #12015-03-09T06:13:00+09:00mogemimitag:enginetrouble.net,2015-03-09:2015/03/qt-devlog-2015-0309.html<p><strong><a href="http://www.qt.io/">Qt (キュート)</a></strong><sup id="fnref:1"><a class="footnote-ref" href="#fn:1" rel="footnote">1</a></sup> を触り始めました。
Qt を使ってデスクトップで動く GUI アプリケーションを作っていきます。
その過程で得たことを備忘録として残しておきます。</p>
<p>今回取り扱う環境は、OS X と Xcode, Qt 5.4 です。また Qt のインストール先を <code>~/Qt</code> にしています。
Qt 自体はクロスプラットフォームな開発環境になっているので、お使いの開発環境に合わせて適宜読み替えてください。</p>
<blockquote>
<p><strong>Note:</strong>
こうしたツールやフレームワークの使い方について日記をつけても、
バージョンが上がり提供される API や機能が変わると、
あっという間に内容が古くなり、役に立たなくなることがしばしばあります。
とはいえ、文献はあるに越したことはありません。
Qt に限らず、ちょっとした備忘録を今後もこまめに残していこうと思います。</p>
</blockquote>
<h2>Qt 5.4 を手に入れる</h2>
<p>まず手始めに、Qt を手に入れます。
Qt Community を<a href="http://www.qt.io/download-open-source/">このページ</a>からダウンロードします。
オンラインインストーラになっているのでぽちぽちクリックしてお好きな場所にインストールします。
インストールにざっと 30 分ほどかかるので、コーヒーでも飲んでお待ちください。</p>
<h2>qmake のパスを通す</h2>
<p>インストールしたら、<code>qmake</code> のパスを通しておきます。
僕の環境は OS X と zsh を使っているので、次のような alias を <code>.zshrc</code> に追加しました。</p>
<div class="highlight"><pre><span></span><span class="nb">alias</span> <span class="nv">qmake</span><span class="o">=</span><span class="s2">"~/Qt/5.4/clang_64/bin/qmake"</span>
</pre></div>
<p>次のようにパスを通してもいいと思います:</p>
<div class="highlight"><pre><span></span><span class="nb">export</span> <span class="nv">QT_SDK_ROOT</span><span class="o">=</span>~/Qt/5.4
<span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="nv">$PATH</span>:<span class="nv">$QT_SDK_ROOT</span>/clang_64/bin
</pre></div>
<p>ターミナル上で <code>where qmake</code> もしくは <code>qmake -v</code> と打ち、パスが通ってるか確認しておきましょう。</p>
<div class="highlight"><pre><span></span>> qmake -v
QMake version 3.0
Using Qt version 5.4.1 in ~/Qt/5.4/clang_64/lib
</pre></div>
<h2>qmake を使って Xcode のプロジェクトファイルを生成する</h2>
<p>Xcode のプロジェクトファイル (<code>.xcodeproj</code>) を作るには、Qt プロジェクトのディレクトリで次のコマンドを実行します。</p>
<div class="highlight"><pre><span></span>qmake -spec macx-xcode
</pre></div>
<h2>Qt 5.4 の Xcode でのプリプロセス</h2>
<p><code>qmake</code> で生成された Xcode プロジェクトで、ビルドすると次のプリプロセス (Qt Preprocess) がまず走ります。</p>
<div class="highlight"><pre><span></span>make -C /path/to/MyApp -f MyApp.xcodeproj/qt_makeqmake.mak
make -C /path/to/MyApp -f MyApp.xcodeproj/qt_preprocess.mak
</pre></div>
<p>これによって、Qt 特有の signal などの記述を、C++ にバインディングしてるようです。
例えば、<code>QObject</code> を継承した <code>MyClass</code> を定義します。
すると <code>MyClass.h</code> と <code>MyClass.cpp</code> から <code>moc_MyClass.cpp</code> がプリプロセスによって生成されます。</p>
<h2>音楽を再生する</h2>
<p>Qt プロジェクトファイル(例 <code>MyApp.pro</code>)に依存するフレームワークとして <code>multimedia</code> を追加します。</p>
<div class="highlight"><pre><span></span>QT +<span class="o">=</span> multimedia
</pre></div>
<p>他に <code>qml</code>, <code>quick</code>, <code>widgets</code> に依存している場合は、このように追加します:</p>
<div class="highlight"><pre><span></span>QT +<span class="o">=</span> qml quick widgets multimedia
</pre></div>
<p>音楽を再生する場合は、<code>QMediaPlayer</code> を使います。
<code>QMediaPlayer::setMedia</code> では再生する音楽の URL を <code>QUrl</code> で指定することになります。
ローカルに置いてる音楽ファイルはもちろん、インターネット上の音楽ファイルも再生することができるようです。
簡単のため、<code>QObject</code> を継承した <code>MyClass</code> のコンストラクタに記述しました。</p>
<div class="highlight"><pre><span></span><span class="cp">#include</span> <span class="cpf">"MyClass.h"</span><span class="cp"></span>
<span class="cp">#include</span> <span class="cpf"><QMediaPlayer></span><span class="cp"></span>
<span class="n">MyClass</span><span class="o">::</span><span class="n">MyClass</span><span class="p">(</span><span class="n">QObject</span> <span class="o">*</span><span class="n">parent</span><span class="p">)</span> <span class="o">:</span> <span class="n">QObject</span><span class="p">(</span><span class="n">parent</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">QUrl</span> <span class="n">url</span> <span class="o">=</span> <span class="n">QUrl</span><span class="p">(</span><span class="s">"http://a898.phobos.apple.com/us/r1000/039/Music6/v4/"</span>
<span class="s">"13/22/67/1322678b-e40d-fb4d-8d9b-3268fe03b000/"</span>
<span class="s">"mzaf_8818596367816221008.plus.aac.p.m4a"</span><span class="p">);</span>
<span class="n">QMediaPlayer</span><span class="o">*</span> <span class="n">player</span> <span class="o">=</span> <span class="k">new</span> <span class="n">QMediaPlayer</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">QMediaPlayer</span><span class="o">::</span><span class="n">StreamPlayback</span><span class="p">);</span>
<span class="n">player</span><span class="o">-></span><span class="n">setMedia</span><span class="p">(</span><span class="n">url</span><span class="p">);</span>
<span class="n">player</span><span class="o">-></span><span class="n">setVolume</span><span class="p">(</span><span class="mi">70</span><span class="p">);</span>
<span class="n">player</span><span class="o">-></span><span class="n">play</span><span class="p">();</span>
<span class="p">}</span>
</pre></div>
<h2>ListView にスクロールバーを追加する</h2>
<p>QML で <code>ListView</code> にスクロールバーを表示する場合は、<code>ListView</code> を <code>ScrollView</code> の中に入れます。</p>
<div class="highlight"><pre><span></span><span class="nx">ScrollView</span> <span class="p">{</span>
<span class="nx">ListView</span> <span class="p">{</span>
<span class="c1">// ...</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2>ウィンドウのタイトルバーを非表示にする</h2>
<p>ウィンドウのタイトルバーを非表示にする場合は、QML で <code>ApplicationWindow.flags</code> に <code>Qt.FramelessWindowHint</code> を設定します。</p>
<div class="highlight"><pre><span></span><span class="nx">ApplicationWindow</span> <span class="p">{</span>
<span class="k">flags:</span><span class="nx">Qt</span><span class="p">.</span><span class="nx">FramelessWindowHint</span>
<span class="p">}</span>
</pre></div>
<h2>QML で GUI のイベントを受け取る</h2>
<p><a href="http://doc.qt.io/qt-5/qml-qtquick-controls-textfield.html">TextField</a> で <code>onEditingFinished</code> イベントが発生したときにそのテキストの内容ををデバッグログに表示します。
OS X だとこのイベントは、テキストを入力後 Enter キーを押したときに発生します。</p>
<div class="highlight"><pre><span></span><span class="nx">TextField</span> <span class="p">{</span>
<span class="k">onEditingFinished:</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">text</span><span class="p">)</span>
<span class="p">}</span>
</pre></div>
<p>キーボードのイベントを受け取るときは <code>Keys</code> を使います。</p>
<div class="highlight"><pre><span></span><span class="nx">TextField</span> <span class="p">{</span>
<span class="k">Keys.onReleased:</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"onReleased"</span><span class="p">)</span>
<span class="p">}</span>
</pre></div>
<p>スコープを使って、イベントを受け取ったときの処理(シグナルハンドラー)も書けます。</p>
<div class="highlight"><pre><span></span><span class="nx">TextField</span> <span class="p">{</span>
<span class="k">Keys.onPressed:</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="nx">Qt</span><span class="p">.</span><span class="nx">Key_Down</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"onPressed: Key_Down"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p><code>console.log</code> の記述をみて、お気付きになった方も多いかもしれません。
QML では、JavaScript でプログラミングロジックを書くことができます。</p>
<blockquote>
<p><strong>NOTE:</strong> <code>event.key === Qt.Key_Down</code> のようにここでは <code>==</code> 演算子ではなく、<code>===</code> 演算子を使っています。
試しに、<code>==</code> 演算子を使って比較すると、Qt Designerで警告 (M126) が表示されます。
これは <code>==</code> を使って比較するときに、2つの型が異なると強制的に型変換が行われるためです。
予期しない動作を防ぐため、代わりに <code>===</code> 演算子を使いましょう。
こういったところも JavaScript です。</p>
</blockquote>
<h2>Qt でデバッグプリント</h2>
<p>C++ の場合は <code>qDebug</code> 関数を使ってデバッグプリントができます。</p>
<div class="highlight"><pre><span></span><span class="cp">#include</span> <span class="cpf"><QDebug></span><span class="cp"></span>
<span class="kt">void</span> <span class="n">MyClass</span><span class="o">::</span><span class="n">onText</span><span class="p">(</span><span class="k">const</span> <span class="n">QString</span><span class="o">&</span> <span class="n">in</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">qDebug</span><span class="p">()</span> <span class="o"><<</span> <span class="n">in</span><span class="p">;</span>
<span class="n">qDebug</span><span class="p">(</span><span class="s">"ok"</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>QML の場合 <code>console.log</code> が使えるようです。</p>
<div class="highlight"><pre><span></span><span class="nx">Rectangle</span> <span class="p">{</span>
<span class="k">Keys.onPressed:</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="nx">Qt</span><span class="p">.</span><span class="nx">Key_Down</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"key down"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">Keys.onReleased:</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"released"</span><span class="p">)</span>
<span class="p">}</span>
</pre></div>
<h2>QML から QObject (C++) の関数を呼び出す</h2>
<p><code>QQmlApplicationEngine::rootContext</code> で <code>QQmlContext</code> を取得して、
<code>QQmlContext::setContextProperty</code> で <code>QObject</code> を設定する形になります。
余談ですが、コンテキストにオブジェクトのポインタを設定するところは JavaScriptCore や V8, Lua などのスクリプトエンジンのバインディングと似ていますね。
例を載せておきます。</p>
<p><code>main.cpp</code>:</p>
<div class="highlight"><pre><span></span><span class="cp">#include</span> <span class="cpf"><QApplication></span><span class="cp"></span>
<span class="cp">#include</span> <span class="cpf"><QQmlApplicationEngine></span><span class="cp"></span>
<span class="cp">#include</span> <span class="cpf"><QQmlContext></span><span class="cp"></span>
<span class="cp">#include</span> <span class="cpf">"MyClass.h"</span><span class="cp"></span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
<span class="n">QApplication</span> <span class="n">app</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">);</span>
<span class="n">QQmlApplicationEngine</span> <span class="n">engine</span><span class="p">;</span>
<span class="n">engine</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">QUrl</span><span class="p">(</span><span class="n">QStringLiteral</span><span class="p">(</span><span class="s">"qrc:/main.qml"</span><span class="p">)));</span>
<span class="n">MyClass</span> <span class="n">myClass</span><span class="p">;</span>
<span class="n">engine</span><span class="p">.</span><span class="n">rootContext</span><span class="p">()</span><span class="o">-></span><span class="n">setContextProperty</span><span class="p">(</span><span class="n">QStringLiteral</span><span class="p">(</span><span class="s">"myClass"</span><span class="p">),</span> <span class="o">&</span><span class="n">myClass</span><span class="p">);</span>
<span class="k">return</span> <span class="n">app</span><span class="p">.</span><span class="n">exec</span><span class="p">();</span>
<span class="p">}</span>
</pre></div>
<p><code>MyClass.h</code>:</p>
<div class="highlight"><pre><span></span><span class="cp">#include</span> <span class="cpf"><QObject></span><span class="cp"></span>
<span class="k">class</span> <span class="nc">MyClass</span> <span class="o">:</span> <span class="k">public</span> <span class="n">QObject</span> <span class="p">{</span>
<span class="n">Q_OBJECT</span>
<span class="k">public</span><span class="o">:</span>
<span class="k">explicit</span> <span class="n">MyClass</span><span class="p">(</span><span class="n">QObject</span> <span class="o">*</span><span class="n">parent</span> <span class="o">=</span> <span class="mi">0</span><span class="p">);</span>
<span class="o">~</span><span class="n">MyClass</span><span class="p">();</span>
<span class="n">Q_INVOKABLE</span> <span class="kt">void</span> <span class="nf">onEditingFinished</span><span class="p">(</span><span class="k">const</span> <span class="n">QString</span><span class="o">&</span> <span class="n">text</span><span class="p">);</span>
<span class="p">};</span>
</pre></div>
<p>QML では <code>QQmlContext::setContextProperty</code> に設定したオブジェクト名 (<code>myClass</code>) を使って、C++ の関数を呼び出すことができます。</p>
<div class="highlight"><pre><span></span><span class="nx">TextField</span> <span class="p">{</span>
<span class="k">onEditingFinished:</span> <span class="nx">myClass</span><span class="p">.</span><span class="nx">onEditingFinished</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">text</span><span class="p">)</span>
<span class="p">}</span>
</pre></div>
<h2>参考文献</h2>
<ul>
<li><a href="http://doc.qt.io/qt-5/qtqml-javascript-expressions.html">JavaScript Expressions in QML Documents | Qt QML 5.4</a> - QML の中で記述できる JavaScript について</li>
<li><a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Comparison_Operators">比較演算子 - JavaScript | MDN</a> - <code>==</code> 演算子と <code>===</code> 演算子の違いについて</li>
</ul>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>発音は "cute" (キュート)が一般的だそうですが、"CUE-TEE" あるいは "Q-T" (キューティー)という発音ももちろん通じるそうです。 <a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
</ol>
</div>Oh My GYP を GitHub で公開しました2015-03-08T00:46:00+09:00mogemimitag:enginetrouble.net,2015-03-08:2015/03/oh-my-gyp-has-been-published-in-the-github.html<p>あけましておめでとうございます。もう3月ですが…(汗)</p>
<p>GitHub で <a href="https://github.com/mogemimi/oh-my-gyp">Oh My GYP</a> を公開しました。
Oh My GYP は <a href="https://code.google.com/p/gyp/">GYP (Generate Your Projects)</a> のビルド設定ファイル集です。
基本的な使い方は <a href="https://github.com/mogemimi/oh-my-gyp/blob/master/README.md">README.md</a> のほうに書きましたのでぜひご覧ください。</p>
<p>GYP を使っていると、<code>common.gypi</code> などのビルド設定がついつい複雑になりがちです。
とりわけ、各プラットフォーム向けの設定を <code>.gyp</code> ファイルに追加していると、その構文からかネストが深くなり見通しがよくありません。
ちょっとしたアプリケーションをビルドしたいときに、毎回 GYP ファイルを書くのは億劫なものです。
そこで、よく使うイディオムを <code>.gypi</code> ファイルとしてまとめたものが Oh My GYP です。</p>
<blockquote>
<p><strong>Note:</strong>
とても小さなコンソールアプリケーションだと GYP や CMake などをわざわざ使わずとも、make で済ますのが手っ取り早いです。
もし Xcode や Visual Studio などを使って開発する場合は、ひとまず GYP や CMake に頼ってみるのは良さそうです。</p>
</blockquote>
<p>使い方は、<code>.gyp</code> ファイルの <code>includes</code> に追加するだけです。
例えば、警告をエラーとして扱いたい場合は次のように追加します。</p>
<div class="highlight"><pre><span></span><span class="s1">'includes'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'oh-my-gyp/warn-as-error.gypi'</span><span class="p">,</span>
<span class="p">],</span>
</pre></div>
<p>これだけで、Xcode や Visual Studio でビルドするときに警告をエラーとして扱ってくれます。
この設定をいままで通り GYP で記述すると次のようになります。</p>
<div class="highlight"><pre><span></span><span class="s1">'msbuild_settings'</span><span class="p">:{</span>
<span class="s1">'ClCompile'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'TreatWarningAsError'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /WX</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_TREAT_WARNINGS_AS_ERRORS'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<p>ちょっとしたコードを動かしてみたいときに、毎回これらを記述しておくのは大変です。
小さく開発を始めたいときに、この Oh My GYP は適しています。
詳細なビルド設定が必要になった場合は、適宜 <code>common.gypi</code> などを追加して、
いままで通りアプリケーションごとの設定を GYP ファイルに記述していくことになるでしょう。</p>
<p>Oh My GYP はパブリックドメインとして公開しています。
煮るなり焼くなり、安心してお使いいただけます。</p>GYP における MSBuild Settings (msbuild_settings) の使い方2014-12-11T21:01:00+09:00mogemimitag:enginetrouble.net,2014-12-11:2014/12/msbuild-settings-with-gyp.html<p>前回、<a href="/2014/10/gyp-build-settings-in-msvs2014.html">"ビルドツール GYP で Visual Studio プロジェクトのあれこれを設定しよう"</a> の中で GYP における <strong>MSVS settings</strong> (<code>msvs_settings</code>) を使った Visual Studio のビルド設定について紹介しました。</p>
<p>調べてみると、GYP には他に <strong>MSBuild settings</strong> (<code>msbuild_settings</code>) という項目が存在しており、MSVS settings を置き換えることができるようです。今回は MSBuild settings について紹介します。</p>
<h2>MSVS Settings と MSBuild Settings</h2>
<p>GYP で Visual Studio のビルド設定を行う方法は2つあります。1つは MSVS settings を使って記述する方法ともう1つは MSBuild settings を使う方法です。それぞれ次のような記述になります。</p>
<div class="highlight"><pre><span></span><span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'WarningLevel'</span><span class="p">:</span> <span class="s1">'3'</span><span class="p">,</span> <span class="c1"># /W3 (Level 3)</span>
<span class="s1">'Optimization'</span><span class="p">:</span> <span class="s1">'3'</span><span class="p">,</span> <span class="c1"># /Ox</span>
<span class="s1">'EnableFunctionLevelLinking'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /Gy</span>
<span class="p">},</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'SubSystem'</span><span class="p">:</span> <span class="s1">'1'</span><span class="p">,</span> <span class="c1"># /SUBSYSTEM:CONSOLE</span>
<span class="p">},</span>
<span class="s1">'VCLibrarianTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'LinkTimeCodeGeneration'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /LTCG</span>
<span class="p">},</span>
<span class="p">},</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="s1">'msbuild_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'ClCompile'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'WarningLevel'</span><span class="p">:</span> <span class="s1">'Level3'</span><span class="p">,</span> <span class="c1"># /W3 (Level 3)</span>
<span class="s1">'Optimization'</span><span class="p">:</span> <span class="s1">'Full'</span><span class="p">,</span> <span class="c1"># /Ox</span>
<span class="s1">'FunctionLevelLinking'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /Gy</span>
<span class="p">},</span>
<span class="s1">'Link'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'SubSystem'</span><span class="p">:</span> <span class="s1">'Console'</span><span class="p">,</span> <span class="c1"># /SUBSYSTEM:CONSOLE</span>
<span class="p">},</span>
<span class="s1">'Lib'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'LinkTimeCodeGeneration'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /LTCG</span>
<span class="p">},</span>
<span class="p">},</span>
</pre></div>
<p>それぞれ同じ Visual Studio のビルド設定を示しています。
<code>VCCLCompilerTool</code> が <code>ClCompile</code> となっている他、ところどころ項目名が変わっている点に注目です。また <code>SubSystem</code> について見てみると、列挙型の整数値で指定していた MSVS settings に比べ、MSBuild settings では <code>Console</code> のように文字列で指定しています。
比べてみると、MSBuild settings は MSVS settings より記述しやすく、読みやすいのが特徴的です。</p>
<p>もともと GYP では MSVS settings が使われてきましたが、後から MSBuild settings が加わりました。MSBuild settings では、MSBuild のスキーマに考慮した名前が各項目に用いられるようになり、また、後述するように Visual Studio 2010 以降のプロジェクトをターゲットにしています。</p>
<h2>使い分けよう</h2>
<p>MSVS settings と MSBuild settings の両者の違いはターゲットとしている Visual Studio のバージョンです。
Visual Studio 2008 を含めたそれ以前の Visual Studio のビルド設定については MSVS settings を使って記述する必要があります。Visual Studio 2010 以降をターゲットにしたビルド設定では、MSBuild Settings を指定します。</p>
<p>また、これまで通り MSVS settings で指定した内容は、Visual Studio 2010 以降のプロジェクトファイルにおいても有効です。GYP の内部では、MSVS settings の内容を MSBuild settings に変換してから Visual Studio のプロジェクトを生成しています。(実装については <code>gyp/pylib/gyp/MSVSSettings.py</code> 内の <code>ConvertToMSBuildSettings</code> のコードを参照ください。)</p>
<p>特に理由がなく、すでに最新の Visual Studio を利用している場合は、MSBuild Settings を使うことをおすすめします。</p>
<p>Visual Studio には Visual Studio 2010 以降の新たに追加されたコンパイルオプションが存在しています。例えば <a href="http://msdn.microsoft.com/en-us/library/vstudio/bb385193(v=vs.120).aspx">/MP (Build with Multiple Processes)</a> オプションや、<a href="http://msdn.microsoft.com/en-us/library/vstudio/ee207218(v=vs.120).aspx">/Fi (Preprocess Output File Name)</a> オプションなどです。これらを GYP から設定する場合は、Visual Studio 2010 以降に対応した MSBuild settings を利用する必要があります。</p>
<blockquote>
<p><strong>Note: MSVS settings はいつまで使える?</strong><br />
Chromium プロジェクトでは、今も GYP ファイル内で MSVS settings が使用されています。GYP は元々 Chromium のビルドツールとして開発された経緯があるため、Chromium の中で参照されている間は(大きな問題がない限り) MSVS settings と MSBuild settings の両方をサポートし続けるでしょう。</p>
</blockquote>
<h2>Configuration Attributes</h2>
<p>MSBuild* を冠するのは <code>msbuild_settings</code> だけではありません。
他に <code>msbuild_configuration_attributes</code> が追加されました。
これは GYP における <code>msvs_configuration_attributes</code> に相当します。</p>
<p>例えば、出力ディレクトリを Visual Studio のバージョンによって変更するような設定も可能です。</p>
<div class="highlight"><pre><span></span><span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_configuration_attributes'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'OutputDirectory'</span><span class="p">:</span> <span class="s1">'$(SolutionDir)$(ConfigurationName)VC90/'</span><span class="p">,</span>
<span class="p">},</span>
<span class="s1">'msbuild_configuration_attributes'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'OutputDirectory'</span><span class="p">:</span> <span class="s1">'$(SolutionDir)$(ConfigurationName)VC100/'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<h2>MSVS Settings と MSBuild Settings の対応表</h2>
<p>MSVS settings で記述したビルド設定を MSBuild settings に置換する場合は、次の対応表のように置き換えていきます。</p>
<table>
<thead>
<tr>
<th align="left">VS 2008 and earlier</th>
<th align="left">VS 2010+</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">msvs_settings</td>
<td align="left">msbuild_settings</td>
</tr>
<tr>
<td align="left">msvs_configuration_attributes</td>
<td align="left">msbuild_configuration_attributes</td>
</tr>
<tr>
<td align="left">VCCLCompilerTool</td>
<td align="left">ClCompile</td>
</tr>
<tr>
<td align="left">VCLinkerTool</td>
<td align="left">Link</td>
</tr>
<tr>
<td align="left">VCLibrarianTool</td>
<td align="left">Lib</td>
</tr>
</tbody>
</table>
<p>設定するビルドオプションによっては MSVS settings と MSBuild settings では名前が異なっていることがあります。
例えば、MSVS settings の <code>WarnAsError</code> は MSBuild settings において <code>TreatWarningAsError</code> に変更されています。
これは MSBuild settings の各項目は MSBuild のスキーマに沿った名前となっているためです。</p>
<table>
<thead>
<tr>
<th align="left">msvs_settings</th>
<th align="left">msbuild_settings</th>
<th align="left">Option</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">EnableFunctionLevelLinking</td>
<td align="left">FunctionLevelLinking</td>
<td align="left"><code>/Gy</code></td>
</tr>
<tr>
<td align="left">EnableIntrinsicFunctions</td>
<td align="left">IntrinsicFunctions</td>
<td align="left"><code>/Oi</code></td>
</tr>
<tr>
<td align="left">KeepComments</td>
<td align="left">PreprocessKeepComments</td>
<td align="left"><code>/C</code></td>
</tr>
<tr>
<td align="left">ObjectFile</td>
<td align="left">ObjectFileName</td>
<td align="left"><code>/Fo</code></td>
</tr>
<tr>
<td align="left">OpenMP</td>
<td align="left">OpenMPSupport</td>
<td align="left"><code>/openmp</code></td>
</tr>
<tr>
<td align="left">PrecompiledHeaderThrough</td>
<td align="left">PrecompiledHeaderFile</td>
<td align="left">filename used <code>/Yc</code>, <code>/Yu</code></td>
</tr>
<tr>
<td align="left">PrecompiledHeaderFile</td>
<td align="left">PrecompiledHeaderOutputFile</td>
<td align="left"><code>/Fp</code></td>
</tr>
<tr>
<td align="left">UsePrecompiledHeader</td>
<td align="left">PrecompiledHeader</td>
<td align="left"><code>/Yc</code>, <code>/Yu</code></td>
</tr>
<tr>
<td align="left">WarnAsError</td>
<td align="left">TreatWarningAsError</td>
<td align="left"><code>/WX</code></td>
</tr>
</tbody>
</table>
<p>MSBuild のスキーマに沿った命名のため、ClCompile, Link, Lib の各ビルドオプションについては
それぞれ次の MSDN のページが参考になります:</p>
<ul>
<li><a href="http://msdn.microsoft.com/en-us/library/ee862477.aspx">ClTask</a></li>
<li><a href="http://msdn.microsoft.com/en-us/library/ee862471.aspx">Link Task</a></li>
<li><a href="http://msdn.microsoft.com/en-us/library/ee862484.aspx">LIB Task</a></li>
</ul>
<p>せっかくなので MSBuild settings の具体的な記述例を紹介します。</p>
<h4>ClCompile (MSBuild Settings)</h4>
<div class="highlight"><pre><span></span><span class="s1">'msbuild_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'ClCompile'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'BufferSecurityCheck'</span><span class="p">:</span> <span class="s1">'false'</span><span class="p">,</span> <span class="c1"># /GS-</span>
<span class="c1">#'BufferSecurityCheck': 'true', # /GS</span>
<span class="s1">'CompileAs'</span><span class="p">:</span> <span class="s1">'Default'</span><span class="p">,</span>
<span class="c1">#'CompileAs': 'CompileAsC', # /TC</span>
<span class="c1">#'CompileAs': 'CompileAsCpp', # /TP</span>
<span class="s1">'CompileAsManaged'</span><span class="p">:</span> <span class="s1">'false'</span><span class="p">,</span>
<span class="c1">#'CompileAsManaged': 'true', # /clr</span>
<span class="c1">#'CompileAsManaged': 'Pure', # /clr:pure</span>
<span class="c1">#'CompileAsManaged': 'Safe', # /clr:safe</span>
<span class="c1">#'CompileAsManaged': 'OldSyntax', # /clr:oldSyntax</span>
<span class="s1">'CreateHotpatchableImage'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /hotpatch</span>
<span class="s1">'FloatingPointExceptions'</span><span class="p">:</span> <span class="s1">'false'</span><span class="p">,</span> <span class="c1"># /fp:except-</span>
<span class="c1">#'FloatingPointExceptions': 'true', # /fp:except</span>
<span class="s1">'FunctionLevelLinking'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /Gy</span>
<span class="s1">'InlineFunctionExpansion'</span><span class="p">:</span> <span class="s1">'Default'</span><span class="p">,</span> <span class="c1"># Default</span>
<span class="c1">#'InlineFunctionExpansion': 'OnlyExplicitInline', # /Ob1</span>
<span class="c1">#'InlineFunctionExpansion': 'AnySuitable', # /Ob2</span>
<span class="c1">#'InlineFunctionExpansion': 'Disabled', # /Ob0</span>
<span class="s1">'IntrinsicFunctions'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /Oi</span>
<span class="s1">'MinimalRebuild'</span><span class="p">:</span> <span class="s1">'false'</span><span class="p">,</span> <span class="c1"># /Gm-</span>
<span class="c1">#'MinimalRebuild': 'true', # /Gm</span>
<span class="s1">'MultiProcessorCompilation'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /MP</span>
<span class="s1">'Optimization'</span><span class="p">:</span> <span class="s1">'Disabled'</span><span class="p">,</span> <span class="c1"># /Od</span>
<span class="c1">#'Optimization': 'MinSpace', # /O1</span>
<span class="c1">#'Optimization': 'MaxSpeed', # /O2</span>
<span class="c1">#'Optimization': 'Full', # /Ox</span>
<span class="s1">'PreprocessorDefinitions'</span><span class="p">:</span> <span class="s1">'_WIN32_WINNT=0x0601;WIN32_LEAN_AND_MEAN;NOMINMAX'</span><span class="p">;</span>
<span class="c1">#'PreprocessorDefinitions': [</span>
<span class="c1"># '_WIN32_WINNT=0x0601', # Windows 7 or later</span>
<span class="c1"># 'WIN32_LEAN_AND_MEAN',</span>
<span class="c1"># 'NOMINMAX',</span>
<span class="c1">#],</span>
<span class="s1">'RuntimeLibrary'</span><span class="p">:</span> <span class="s1">'MultiThreaded'</span><span class="p">,</span> <span class="c1"># /MT</span>
<span class="c1">#'RuntimeLibrary': 'MultiThreadedDebug', # /MTd</span>
<span class="c1">#'RuntimeLibrary': 'MultiThreadedDLL', # /MD</span>
<span class="c1">#'RuntimeLibrary': 'MultiThreadedDebugDLL', # /MDd</span>
<span class="s1">'StringPooling'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /GF</span>
<span class="s1">'SuppressStartupBanner'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /nologo</span>
<span class="s1">'TreatWarningAsError'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /WX</span>
<span class="s1">'WarningLevel'</span><span class="p">:</span> <span class="s1">'TurnOffAllWarnings'</span><span class="p">,</span> <span class="c1"># /W0</span>
<span class="c1">#'WarningLevel': 'Level1', # /W1</span>
<span class="c1">#'WarningLevel': 'Level2', # /W2</span>
<span class="c1">#'WarningLevel': 'Level3', # /W3</span>
<span class="c1">#'WarningLevel': 'Level4', # /W4</span>
<span class="s1">'WholeProgramOptimization'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /GL</span>
<span class="p">},</span>
<span class="p">},</span>
</pre></div>
<h4>Link (MSBuild Settings)</h4>
<div class="highlight"><pre><span></span><span class="s1">'msbuild_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'Link'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'AdditionalDependencies'</span><span class="p">:</span> <span class="s1">'winmm.lib;ws2_32.lib;kernel32.lib'</span><span class="p">,</span>
<span class="c1">#'AdditionalDependencies': [</span>
<span class="c1"># 'winmm.lib',</span>
<span class="c1"># 'ws2_32.lib',</span>
<span class="c1"># 'kernel32.lib',</span>
<span class="c1">#],</span>
<span class="s1">'GenerateDebugInformation'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /DEBUG</span>
<span class="s1">'LinkIncremental'</span><span class="p">:</span> <span class="s1">'false'</span><span class="p">,</span> <span class="c1"># /INCREMENTAL:NO</span>
<span class="c1">#'LinkIncremental': 'true', # /INCREMENTAL</span>
<span class="s1">'LinkTimeCodeGeneration'</span><span class="p">:</span> <span class="s1">'Default'</span><span class="p">,</span>
<span class="c1">#'LinkTimeCodeGeneration': 'UseLinkTimeCodeGeneration', # /LTCG</span>
<span class="c1">#'LinkTimeCodeGeneration': 'PGInstrument', # /LTCG:PGInstrument</span>
<span class="c1">#'LinkTimeCodeGeneration': 'PGOptimization', # /LTCG:PGOptimize</span>
<span class="c1">#'LinkTimeCodeGeneration': 'PGUpdate', # /LTCG:PGUpdate</span>
<span class="s1">'OptimizeReferences'</span><span class="p">:</span> <span class="s1">'false'</span><span class="p">,</span> <span class="c1"># /OPT:NOREF</span>
<span class="c1">#'OptimizeReferences': 'true', # /OPT:REF</span>
<span class="s1">'SubSystem'</span><span class="p">:</span> <span class="s1">'Console'</span><span class="p">,</span> <span class="c1"># /SUBSYSTEM:CONSOLE</span>
<span class="c1">#'SubSystem': 'Windows', # /SUBSYSTEM:WINDOWS</span>
<span class="c1">#'SubSystem': 'Native', # /SUBSYSTEM:NATIVE</span>
<span class="c1">#'SubSystem': 'EFI Application', # /SUBSYSTEM:EFI_APPLICATION</span>
<span class="c1">#'SubSystem': 'EFI Boot Service Driver', # /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER</span>
<span class="c1">#'SubSystem': 'EFI ROM', # /SUBSYSTEM:EFI_ROM</span>
<span class="c1">#'SubSystem': 'EFI Runtime', # /SUBSYSTEM:EFI_RUNTIME_DRIVER</span>
<span class="c1">#'SubSystem': 'WindowsCE', # /SUBSYSTEM:WINDOWSCE</span>
<span class="s1">'TargetMachine'</span><span class="p">:</span> <span class="s1">'MachineX86'</span><span class="p">,</span> <span class="c1"># /MACHINE:X86</span>
<span class="c1">#'TargetMachine': 'MachineARM', # /MACHINE:ARM</span>
<span class="c1">#'TargetMachine': 'MachineEBC', # /MACHINE:EBC</span>
<span class="c1">#'TargetMachine': 'MachineIA64', # /MACHINE:IA64</span>
<span class="c1">#'TargetMachine': 'MachineMIPS', # /MACHINE:MIPS</span>
<span class="c1">#'TargetMachine': 'MachineMIPS16', # /MACHINE:MIPS16</span>
<span class="c1">#'TargetMachine': 'MachineMIPSFPU', # /MACHINE:MIPSFPU</span>
<span class="c1">#'TargetMachine': 'MachineMIPSFPU16', # /MACHINE:MIPSFPU16</span>
<span class="c1">#'TargetMachine': 'MachineSH4', # /MACHINE:SH4</span>
<span class="c1">#'TargetMachine': 'MachineTHUMB', # /MACHINE:THUMB</span>
<span class="c1">#'TargetMachine': 'MachineX64' # /MACHINE:X64</span>
<span class="p">},</span>
<span class="p">},</span>
</pre></div>
<h4>Lib (MSBuild Settings)</h4>
<div class="highlight"><pre><span></span><span class="s1">'msbuild_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'Lib'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'LinkTimeCodeGeneration'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /LTCG</span>
<span class="p">},</span>
<span class="p">},</span>
</pre></div>
<h4>MSBuild Configuration Attributes</h4>
<div class="highlight"><pre><span></span><span class="s1">'msbuild_configuration_attributes'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'CharacterSet'</span><span class="p">:</span> <span class="s1">'Unicode'</span><span class="p">,</span> <span class="c1"># Unicode Character Set</span>
<span class="c1">#'CharacterSet': 'MultiByte', # Multibyte Character Set</span>
<span class="s1">'IntermediateDirectory'</span><span class="p">:</span> <span class="s1">'$(SolutionDir)..</span><span class="se">\\</span><span class="s1">out</span><span class="se">\\</span><span class="s1">obj</span><span class="se">\\</span><span class="s1">$(ConfigurationName)</span><span class="se">\\</span><span class="s1">$(ProjectName)'</span><span class="p">,</span>
<span class="s1">'OutputDirectory'</span><span class="p">:</span> <span class="s1">'$(SolutionDir)$(ConfigurationName)'</span><span class="p">,</span>
<span class="p">},</span>
</pre></div>
<h2>最後に</h2>
<p>要点をまとめると:</p>
<ul>
<li>GYP では MSVS settings (<code>msvs_settings</code>) と MSBuild settings (<code>msbuild_settings</code>) の2つが存在している<ul>
<li>MSVS settings は GYP によって内部で MSBuild settings に変換される</li>
<li><code>msvs_configuration_attributes</code> と <code>msbuild_configuration_attributes</code> の2つについても同様</li>
<li>Visual Studio 2008 以前については MSVS settings を指定</li>
<li>Visual Studio 2010 以降については MSBuild settings を指定</li>
</ul>
</li>
<li>最新の Visual Studio をターゲットにするなら MSBuild settings を使おう</li>
</ul>
<p>GYP には、まだまだ知らない機能やビルドオプションがありそうです。<br />
最後に、この記事を書くきっかけを与えてくださった <a href="https://github.com/mitei">@mitei</a> さん、ありがとうございました。</p>古典的な遅延シェーディングの秋 20142015-11-03T00:00:00+09:00mogemimitag:enginetrouble.net,2014-10-19:2014/10/classic-deferred-shading-2014.html<p>おなじみの<strong>遅延シェーディング (Deferred Shading)</strong> を実装しました。昨年の夏に OpenGL で実装して以来、1年ぶりになります(<a href="/2013/09/summer-vacation-2013.html">こちらの記事</a>を参照)。今回実装したのは、いわゆる<strong>古典的遅延シェーディング (Classic Deferred Shading)</strong> と呼ばれ<sup id="fnref:EpicGames_2012"><a class="footnote-ref" href="#fn:EpicGames_2012" rel="footnote">1</a></sup><sup id="fnref:Crytek_2009"><a class="footnote-ref" href="#fn:Crytek_2009" rel="footnote">2</a></sup>広く知られている遅延シェーディングです。今回は触れませんが、この遅延シェーディングを発展させたテクニックには <strong>Light Pre-Pass</strong> や <strong>Forward+</strong> があります。</p>
<h2>遅延シェーディング</h2>
<p>遅延シェーディングについてざっくり説明すると:</p>
<ul>
<li>最初のパスでは、レンダーターゲット(またはフレームバッファ)にオブジェクトのアルベド(テクスチャカラー)や法線、深度、ワールド空間での位置などを書き込みます。(A)</li>
<li>次のパスでは、レンダーターゲットをサンプリングしながらシェーディングを行います。(B)</li>
</ul>
<p>最初のパス (A) でシェーディングを行わないところが肝です。このとき、オブジェクトのジオメトリ情報を書き込んだバッファを<strong>ジオメトリバッファ (Geometry Buffer)</strong> と言います。慣例的に <strong>G-Buffer</strong> と呼ばれています。次のパス (B) では、この G-Buffer を使って、シェーディングを行います。シェーディングを遅らせることから、遅延シェーディングと呼ばれています。</p>
<p><img alt="(図 1) 今回の遅延シェーディングの最終結果。Half Lambert シェーディング後に SSAO とポストプロセスエフェクト(FXAA, 色収差、ケラレ)をかけている。" src="https://enginetrouble.net/images/2014/10/ImageWithPostProcessing.png" /><br />
<em>(図 1) 今回の遅延シェーディングの最終結果。Half Lambert シェーディング後に SSAO とポストプロセスエフェクト(FXAA, 色収差、ケラレ)を追加している。</em></p>
<p>遅延シェーディングの長所は、リアルタイムにおいて複数の光源を扱いやすくなることです。その一方で、半透明オブジェクトの扱いが少々難しくなるデメリットがあります。他にも MSAA との相性が悪いという短所もありますが、後述するポストプロセスエフェクトとの相性の良さから FXAA や SMAA といった代替案を用いることがあります。
また G-Buffer に書き込むためにマルチレンダーターゲット (MRT) を利用します。動作には MRT 対応のグラフィックスデバイスが必須になりますが、これについては、ここ最近のゲームプレイ環境がほとんど MRT に対応しているため問題ありません。最新のモバイル端末も MRT に対応し始めています。</p>
<p>遅延シェーディングの副次的な効果として、ポストプロセスエフェクトを追加しやすくなる利点があります。被写界深度ブラーやフォグ、モーションブラーのようなポストプロセスエフェクトをはじめ、SSAO (Screen Space Ambient Occlusion) や SSIL (Screen Space Indirect Lighting), RLR (Real-Time Local Reflections) などのスクリーンスペース系のエフェクトとも相性が良いです。</p>
<p>今回は、平行光源による Half Lambert シェーディングの他に SSAO を追加しました。AO が加わったため、見た目がぐっといい感じになりました(図 1 参照)。その他に、色収差エフェクトとケラレ、被写界深度ブラーをポストエフェクトとして追加しています。アンチエイリアスには FXAA を使用しました。SSAO や色収差、被写界深度ブラーについては日を改めてお話ししたいと思います。</p>
<h2>今年の G-Buffer</h2>
<p>G-Buffer の構成は次のようにしました:</p>
<p><em>(表1) G-Buffer の構成</em></p>
<table>
<thead>
<tr>
<th align="right">Name</th>
<th align="left">Format</th>
<th align="left">Usage</th>
<th align="left"></th>
</tr>
</thead>
<tbody>
<tr>
<td align="right">-</td>
<td align="left">D24S8</td>
<td align="left">Depth, Stencil</td>
<td align="left"></td>
</tr>
<tr>
<td align="right">RT0</td>
<td align="left">R8G8B8A8_UNORM</td>
<td align="left">Albedo (RGBA)</td>
<td align="left"></td>
</tr>
<tr>
<td align="right">RT1</td>
<td align="left">R10G10B10A2_UNORM</td>
<td align="left">World-space normal (XYZ), None (Alpha)</td>
<td align="left"></td>
</tr>
<tr>
<td align="right">RT2</td>
<td align="left">R32_FLOAT</td>
<td align="left">Depth</td>
<td align="left"></td>
</tr>
</tbody>
</table>
<p>G-Buffer の各フォーマットについては、アプリケーションの要件やレンダラの実装に大きく左右されるので、毎回手探りで設定しているような気がします。</p>
<p><img alt="(図 2) 左上から右に向かって、(C) アルベド、(D) ワールド空間の法線、(E) 深度。左下から右に向かって、(F) SSAO、(G) ランバートシェーディングと SSAO, (H) ポストプロセスエフェクトを加えた最終結果。" src="https://enginetrouble.net/images/2014/10/GBuffer_SSAO_PostEffects.png" /><br />
<em>(図 2) 左上から右に向かって、(C) アルベド、(D) ワールド空間の法線、(E) 深度。左下から右に向かって、(F) SSAO、(G) ランバートシェーディングと SSAO, (H) ポストプロセスエフェクトを加えた最終結果。</em></p>
<h2>レンダーターゲットのフォーマット</h2>
<p>サーフェスフォーマットにおける<strong>コンポーネント</strong>とは、R (Red) や G (Green) あるいは D (Depth), S (Stencil) といったカラーチャンネルや成分のことを示します。例えば、R8G8B8A8 や R16G16, D24S8 はそれぞれ 4 成分(4 コンポーネント)、3 成分、2 成分のフォーマットとなります。G-Buffer の各レンダーターゲットのサーフェスフォーマットを決める際、このコンポーネントについて考慮する必要があります:</p>
<ul>
<li>コンポーネントの数 (R, RG, RGB, RGBA)</li>
<li>コンポーネントのサイズ (R8, R16, R32 など)</li>
<li>コンポーネントの型や取りうる値の範囲(NORM, UNORM, UINT, FLOAT など)</li>
</ul>
<p>表 1 に示した各レンダーターゲットは、コンポーネントの数やサイズ、型は異なっていますが、サーフェスフォーマットのサイズはすべて 32-bit に揃えています。これは、NVIDIA が 2004 年に発表した資料に基づいています<sup id="fnref:NVIDIA_2004"><a class="footnote-ref" href="#fn:NVIDIA_2004" rel="footnote">3</a></sup>。</p>
<p>また、Direct3D (DXGI) では 24-bit フォーマット (R8G8B8_UNORM, D24) などの非 2 のべき乗フォーマットは廃止されています。OpenGL を利用する場合でも、移植やマルチプラットフォームを考慮するのであれば 32-bit, 64-bit または 128-bit フォーマットを使うのがよさそうです。</p>
<h2>法線のフォーマット</h2>
<p>正規化された法線情報を G-Buffer に格納するにはいくつか手法が存在しています。
まず手始めに <code>R16G16_FLOAT</code> (いわゆる FP16 フォーマット)を試してみました。本来 3 次元の法線情報を <code>half2</code> フォーマットに変換して G-Buffer に格納し、ライティングパスなどで使用するときはデコードして元の 3 次元法線として扱います。ただ実装方法がよくなかったようで、デコードされた法線の長さが 0 になるケースが見られました。
次に、フォーマットとして <code>R16G16B16A16_FLOAT</code> を試してみました。そのまま xyz の 3 成分を保存でき、面倒なエンコード・デコード処理を必要としません。しかし、64-bit フォーマットとなるため、アルベドや深度といった他の G-Buffer の変更が必要になります。次に、フォーマットとして <code>R10G10B10A2_UNORM</code> を試してみました。こちらは UNORM なので 0.0 から 1.0 の範囲に法線をエンコードする必要があります。目立った問題が出なかったため、こちらを採用しました。</p>
<h2>法線のエンコード・デコード</h2>
<p>法線を G-Buffer に格納する際、エンコード・デコード処理を行います。
例えば、<code>R10G10B10A2_UNORM</code> フォーマットに書き込む際、法線の各成分を 0.0 から 1.0 の範囲におさめる必要があります。正規化されたワールド空間(あるいはビュー空間)の法線は各成分が -1.0 から 1.0 の範囲にあります。そこで次のように、0.0 から 1.0 の範囲にエンコードしてから G-Buffer に出力します。</p>
<div class="highlight"><pre><span></span><span class="k">vec3</span> <span class="n">EncodeNormal</span><span class="p">(</span><span class="k">in</span> <span class="k">vec3</span> <span class="n">normal</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">normalize</span><span class="p">(</span><span class="n">normal</span><span class="p">)</span> <span class="o">*</span> <span class="mf">0.5</span> <span class="o">+</span> <span class="mf">0.5</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">void</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">vec3</span> <span class="n">normal</span> <span class="o">=</span> <span class="n">EncodeNormal</span><span class="p">(</span><span class="n">In</span><span class="p">.</span><span class="n">WolrdNormal</span><span class="p">.</span><span class="n">xyz</span><span class="p">);</span>
<span class="n">NormalOut</span> <span class="o">=</span> <span class="k">vec4</span><span class="p">(</span><span class="n">normal</span><span class="p">.</span><span class="n">xyz</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>G-Buffer から法線を取得するときは、サンプルした法線にデコード処理をかけます。例えば次のようになります。</p>
<div class="highlight"><pre><span></span><span class="k">vec3</span> <span class="n">DecodeNormal</span><span class="p">(</span><span class="k">in</span> <span class="k">vec3</span> <span class="n">normal</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">normal</span> <span class="o">*</span> <span class="mf">2.0</span> <span class="o">-</span> <span class="mf">1.0</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">void</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">vec3</span> <span class="n">normalGBuffer</span> <span class="o">=</span> <span class="n">texture</span><span class="p">(</span><span class="n">NormalSampler</span><span class="p">,</span> <span class="n">In</span><span class="p">.</span><span class="n">TextureCoord</span><span class="p">.</span><span class="n">xy</span><span class="p">).</span><span class="n">xyz</span><span class="p">;</span>
<span class="k">vec3</span> <span class="n">worldNormal</span> <span class="o">=</span> <span class="n">DecodeNormal</span><span class="p">(</span><span class="n">normalGBuffer</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>G-Buffer の内容をクリアするとき、レンダーターゲットの初期色について少し注意が必要です。上記のエンコード方法では、長さ 0 の法線ベクトル <code>vec3(0, 0, 0)</code> はエンコード後 <code>vec(0.5, 0.5, 0.5)</code> となります。これは 256 色の RGB 値で <code>(R, G, B) = (127, 127, 127)</code> となるため、法線の初期値を零ベクトルにするときは、白でも黒でもなくグレーで塗りつぶすことになります。</p>
<p>法線のエンコード・デコード処理はレンダーターゲットのフォーマットに依存し、切っても切れない関係にあります。後述する <code>R32G32B32A32_FLOAT</code> や <code>R16G16B16A16_FLOAT</code> といった浮動小数点フォーマットを使う場合は特にデコード・エンコード処理を考える必要がなくなります。
G-Buffer に法線を格納する場合、エンコード・デコードにかかる処理時間や、G-Buffer を占めるフォーマットサイズ、法線の精度・エラー数などが評価対象になります。</p>
<h2>深度</h2>
<p>深度値を格納するにあたって、それなりにサイズの大きなフォーマットを使用しました。深度情報を扱う際に気をつけることとして、Near および Far クリップを適切に指定<sup id="fnref:Depth_NearFarClip"><a class="footnote-ref" href="#fn:Depth_NearFarClip" rel="footnote">4</a></sup>する必要があります。深度値は、エッジ計算や被写界深度ブラーなどのポストプロセスエフェクトで使用する他、
後述する G-Buffer からワールド座標の位置を求めるときにも使用します。</p>
<p>深度値といっても、ビュー空間の深度 (A) や、射影変換後によって得られる深度 (B) などがあります。</p>
<div class="highlight"><pre><span></span><span class="k">vec4</span> <span class="n">worldPosition</span> <span class="o">=</span> <span class="n">matrices</span><span class="p">.</span><span class="n">Model</span> <span class="o">*</span> <span class="k">vec4</span><span class="p">(</span><span class="n">position</span><span class="p">.</span><span class="n">xyz</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
<span class="n">gl_Position</span> <span class="o">=</span> <span class="p">(</span><span class="n">matrices</span><span class="p">.</span><span class="n">Projection</span> <span class="o">*</span> <span class="p">(</span><span class="n">matrices</span><span class="p">.</span><span class="n">View</span> <span class="o">*</span> <span class="n">worldPosition</span><span class="p">));</span>
<span class="c1">// (A) View-space depth</span>
<span class="k">float</span> <span class="n">ViewSpaceDepth</span> <span class="o">=</span> <span class="p">(</span><span class="n">matrices</span><span class="p">.</span><span class="n">View</span> <span class="o">*</span> <span class="n">worldPosition</span><span class="p">).</span><span class="n">z</span><span class="p">;</span>
<span class="c1">// (B) Perspective depth</span>
<span class="k">float</span> <span class="n">ScreenSpaceDepth</span> <span class="o">=</span> <span class="n">gl_Position</span><span class="p">.</span><span class="n">z</span> <span class="o">/</span> <span class="n">gl_Position</span><span class="p">.</span><span class="n">w</span><span class="p">;</span>
</pre></div>
<p>これらの深度値を G-Buffer に書き込む際は、レンダーターゲットのフォーマットに従って、エンコードする必要があります。例えば (A) の場合、ファークリップ面の距離(以下 FarClip)を利用して 0.0 から 1.0 にエンコードした後、G-Buffer に書き込みます。</p>
<div class="highlight"><pre><span></span><span class="k">void</span> <span class="n">EncodeDepth</span><span class="p">(</span><span class="k">in</span> <span class="k">float</span> <span class="n">depth</span><span class="p">,</span> <span class="k">in</span> <span class="k">float</span> <span class="n">farClip</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">depth</span> <span class="o">/</span> <span class="n">farClip</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// PixelShader</span>
<span class="k">void</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">const</span> <span class="k">float</span> <span class="n">FarClip</span> <span class="o">=</span> <span class="mf">500.0</span><span class="p">;</span>
<span class="k">float</span> <span class="n">depth</span> <span class="o">=</span> <span class="n">EncodeDepth</span><span class="p">(</span><span class="n">In</span><span class="p">.</span><span class="n">ViewSpaceDepth</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">FarClip</span><span class="p">);</span>
<span class="n">Out</span><span class="p">.</span><span class="n">Depth</span> <span class="o">=</span> <span class="k">vec4</span><span class="p">(</span><span class="n">depth</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>サンプリングした深度をビュー空間の深度にデコードするときは FarClip をかけてあげます。</p>
<div class="highlight"><pre><span></span><span class="k">void</span> <span class="n">DecodeDepth</span><span class="p">(</span><span class="k">in</span> <span class="k">float</span> <span class="n">depth</span><span class="p">,</span> <span class="k">in</span> <span class="k">float</span> <span class="n">farClip</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">depth</span> <span class="o">*</span> <span class="n">farClip</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// PixelShader</span>
<span class="k">void</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">const</span> <span class="k">float</span> <span class="n">FarClip</span> <span class="o">=</span> <span class="mf">500.0</span><span class="p">;</span>
<span class="k">float</span> <span class="n">depthGBuffer</span> <span class="o">=</span> <span class="n">texture</span><span class="p">(</span><span class="n">AlbedoTexture</span><span class="p">,</span> <span class="n">In</span><span class="p">.</span><span class="n">TextureCoord</span><span class="p">.</span><span class="n">xy</span><span class="p">).</span><span class="n">x</span><span class="p">;</span>
<span class="k">float</span> <span class="n">viewSpaceDepth</span> <span class="o">=</span> <span class="n">DecodeDepth</span><span class="p">(</span><span class="n">depthGBuffer</span><span class="p">,</span> <span class="n">FarClip</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>(B) の深度値については、0.0 から 1.0 の範囲に収まっているので特にエンコードやデコードは必要ありません。今回は (A) の方法を利用しました。(A) と (B) それぞれの深度バッファを図 3 に示します。 NVIDIA GameWorks による Direct3D の遅延シェーディングサンプル<sup id="fnref:NVIDIAGameWorks_GitHub"><a class="footnote-ref" href="#fn:NVIDIAGameWorks_GitHub" rel="footnote">8</a></sup>では (A) の方法を利用しているようです。</p>
<p><img alt="(図 3) 深度バッファの比較。左が (A) ビュー空間の深度、右が (B) 射影変換後の z/w から求める深度値。" src="https://enginetrouble.net/images/2014/10/DepthBufferComparison.png" /><br />
<em>(図 3) 深度バッファの比較。左が (A) ビュー空間の深度、右が (B) 射影変換後の z/w から求める深度値。</em></p>
<p>(A) の深度値を使って、(B) の深度値を求めることもできます。次のようにします。</p>
<div class="highlight"><pre><span></span><span class="k">float</span> <span class="n">ToPerspectiveDepth</span><span class="p">(</span><span class="k">in</span> <span class="k">float</span> <span class="n">viewDepth</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">vec4</span> <span class="n">projectedPosition</span> <span class="o">=</span> <span class="p">(</span><span class="n">matrices</span><span class="p">.</span><span class="n">ViewToProjection</span> <span class="o">*</span> <span class="k">vec4</span><span class="p">(</span><span class="mo">0</span><span class="p">,</span> <span class="mo">0</span><span class="p">,</span> <span class="n">viewDepth</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">));</span>
<span class="k">return</span> <span class="n">projectedPosition</span><span class="p">.</span><span class="n">z</span> <span class="o">/</span> <span class="n">projectedPosition</span><span class="p">.</span><span class="n">w</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// PixelShader</span>
<span class="k">void</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">const</span> <span class="k">float</span> <span class="n">FarClip</span> <span class="o">=</span> <span class="mf">500.0</span><span class="p">;</span>
<span class="k">float</span> <span class="n">depthGBuffer</span> <span class="o">=</span> <span class="n">texture</span><span class="p">(</span><span class="n">AlbedoTexture</span><span class="p">,</span> <span class="n">In</span><span class="p">.</span><span class="n">TextureCoord</span><span class="p">.</span><span class="n">xy</span><span class="p">).</span><span class="n">x</span><span class="p">;</span>
<span class="k">float</span> <span class="n">viewSpaceDepth</span> <span class="o">=</span> <span class="n">ToLinearDepthDecodeDepth</span><span class="p">(</span><span class="n">depthGBuffer</span><span class="p">,</span> <span class="n">FarClip</span><span class="p">);</span>
<span class="k">float</span> <span class="n">perspectiveDepth</span> <span class="o">=</span> <span class="n">ToPerspectiveDepth</span><span class="p">(</span><span class="n">viewSpaceDepth</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<p>さらに、射影変換行列を展開して、ニアクリップ・ファークリップの距離から求めることも出来ます。次の例は、左手座標系の透視投影変換行列を基に (B) の深度値を計算しています。</p>
<div class="highlight"><pre><span></span><span class="k">float</span> <span class="n">ToPerspectiveDepth</span><span class="p">(</span><span class="k">in</span> <span class="k">float</span> <span class="n">viewDepth</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">const</span> <span class="k">float</span> <span class="n">FarClip</span> <span class="o">=</span> <span class="mf">500.0</span><span class="p">;</span>
<span class="k">const</span> <span class="k">float</span> <span class="n">NearClip</span> <span class="o">=</span> <span class="mf">10.0</span><span class="p">;</span>
<span class="c1">///@note Left-hand coordinate system</span>
<span class="k">float</span> <span class="n">a</span> <span class="o">=</span> <span class="n">FarClip</span> <span class="o">/</span> <span class="p">(</span><span class="n">FarClip</span> <span class="o">-</span> <span class="n">NearClip</span><span class="p">);</span>
<span class="k">float</span> <span class="n">b</span> <span class="o">=</span> <span class="p">(</span><span class="n">NearClip</span> <span class="o">*</span> <span class="n">FarClip</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="n">NearClip</span> <span class="o">-</span> <span class="n">FarClip</span><span class="p">);</span>
<span class="k">float</span> <span class="n">z</span> <span class="o">=</span> <span class="n">viewDepth</span> <span class="o">*</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span><span class="p">;</span>
<span class="k">float</span> <span class="n">w</span> <span class="o">=</span> <span class="n">viewDepth</span><span class="p">;</span>
<span class="k">return</span> <span class="n">z</span> <span class="o">/</span> <span class="n">w</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<h2>深度情報からワールド空間の位置を求める</h2>
<p>ランバートシェーディング (Lambertian Shading) などで、ジオメトリのワールド空間の位置を参照することがあります。そこで、G-Buffer からワールド空間の位置を取得できる必要があります。
表1 を見てわかる通り、G-Buffer にはワールド空間またはビュー空間の位置を格納していません。実装によっては、G-Buffer に位置を書き込むこともありますが、今回は深度情報とビュープロジェクションの逆行列から位置を求めることにしました。</p>
<div class="highlight"><pre><span></span><span class="k">vec3</span> <span class="n">DepthToPosition</span><span class="p">(</span><span class="k">in</span> <span class="k">vec2</span> <span class="n">textureCoord</span><span class="p">,</span> <span class="k">in</span> <span class="k">float</span> <span class="n">depth</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">vec4</span> <span class="n">projectedPosition</span> <span class="o">=</span> <span class="k">vec4</span><span class="p">(</span><span class="n">textureCoord</span><span class="p">.</span><span class="n">xy</span> <span class="o">*</span> <span class="k">vec2</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="o">-</span><span class="mi">2</span><span class="p">)</span> <span class="o">+</span> <span class="k">vec2</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
<span class="k">vec3</span> <span class="n">viewPosition</span> <span class="o">=</span> <span class="p">(</span><span class="n">projectionToView</span> <span class="o">*</span> <span class="n">projectedPosition</span><span class="p">).</span><span class="n">xyz</span><span class="p">;</span>
<span class="k">vec3</span> <span class="n">viewRay</span> <span class="o">=</span> <span class="k">vec3</span><span class="p">(</span><span class="n">viewPosition</span><span class="p">.</span><span class="n">xy</span> <span class="o">/</span> <span class="n">projectedPosition</span><span class="p">.</span><span class="n">z</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
<span class="k">return</span> <span class="n">viewRay</span> <span class="o">*</span> <span class="n">depth</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// PixelShader</span>
<span class="k">void</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">float</span> <span class="n">viewDepth</span> <span class="o">=</span> <span class="n">DecodeDepth</span><span class="p">(</span><span class="n">texture</span><span class="p">(</span><span class="n">tex3_DepthSampler</span><span class="p">,</span> <span class="n">In</span><span class="p">.</span><span class="n">TextureCoord</span><span class="p">.</span><span class="n">xy</span><span class="p">).</span><span class="n">x</span><span class="p">,</span> <span class="n">FarClip</span><span class="p">);</span>
<span class="k">vec3</span> <span class="n">viewPosition</span> <span class="o">=</span> <span class="n">DepthToPosition</span><span class="p">(</span><span class="n">In</span><span class="p">.</span><span class="n">TextureCoord</span><span class="p">.</span><span class="n">xy</span><span class="p">,</span> <span class="n">viewDepth</span><span class="p">);</span>
<span class="k">vec3</span> <span class="n">worldPosition</span> <span class="o">=</span> <span class="p">(</span><span class="n">matrices</span><span class="p">.</span><span class="n">ViewToWorld</span> <span class="o">*</span> <span class="k">vec4</span><span class="p">(</span><span class="n">viewPosition</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">)).</span><span class="n">xyz</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<blockquote>
<p><strong>EDIT (Nov 3, 2016)</strong><br />
この <code>DepthToPosition</code> の実装は誤りがあり、正しく深度からワールド空間の位置を復元できません。これについて次の記事を書きました。
<a href="/2016/10/reconstructing-world-position-from-depth-2016.html">"G-Buffer の深度値からワールド空間の位置を復元した秋 2016"</a> をお読みください。</p>
</blockquote>
<h2>お手軽な実装としての浮動小数点バッファ</h2>
<p>遅延シェーディングの実装をより単純にするには G-Buffer の各レンダーターゲットのフォーマットを R32G32B32A32_FLOAT のような 128-bit フォーマットにするとよさそうです。GL で言うところの <code>GL_RGBA32UI</code>, <code>GL_RGBA32F</code> や <code>GL_DEPTH_COMPONENT32F</code> が該当します。特に <code>GL_RGBA32F</code> のように、1 コンポーネントあたり 32 ビット、4 コンポーネント合わせて 128 ビットとなる浮動小数点バッファを 128-bit (FP32) と表現することもあります。
0.0 から 1.0 の範囲内でしか表現できなかった 8-bit の UNORM とは異なり、FP32<sup id="fnref:FP32"><a class="footnote-ref" href="#fn:FP32" rel="footnote">5</a></sup> を直接書き込めるため次のメリットがあります。</p>
<ul>
<li>1 コンポーネントあたり 32-bit もあるため精度が十分ある</li>
<li>法線や深度値を 0.0 から 1.0 の範囲にエンコードせずに格納できる</li>
<li>ワールド空間またはビュー空間の位置をそのまま G-Buffer に格納できる</li>
</ul>
<p>G-Buffer への書き込み・読み込み処理がとても簡素になり、GLSL や HLSL などのシェーダプログラムがよりシンプルになります。</p>
<p>短所として、帯域幅が 128-bit となり、R8G8B8A8 のような 32-bit フォーマットに比べて、フレームバッファのサイズが大きくなります。とてつもなく解像度の高いモバイル端末が普及している時代なので、GPU の性能(特にフィルレート)やディスプレイサイズによって使用するフォーマットを選択したほうが良さそうです。</p>
<p>とはいえ、最近のデスクトップで動かす分には問題ありません。例えば、<a href="http://www.openglsuperbible.com/">OpenGL SuperBible</a> では、この 128-bit (FP32) フォーマットを使用した遅延シェーディングのサンプルを公開<sup id="fnref:SuperBible_GitHub"><a class="footnote-ref" href="#fn:SuperBible_GitHub" rel="footnote">6</a></sup>しています。わずらわしい法線のエンコード処理や、深度と逆行列からワールド空間の位置を求めるといった計算が必要ないため、チュートリアルやサンプルに適しています。</p>
<p>32-bit と 128-bit フォーマットの折衷案として、64-bit の浮動小数点フォーマット (FP16) を利用する方法があります。書籍『リアルタイムシャドウ』では現在のハードウェアの性能を考慮して著者の経験からこの FP16 フォーマットを推奨<sup id="fnref:RealtimeShadow"><a class="footnote-ref" href="#fn:RealtimeShadow" rel="footnote">7</a></sup>しています。
また、NVIDIA GameWorks が公開している Direct3D 向けのサンプルアプリケーションでは、<code>DXGI_FORMAT_R16G16B16A16_FLOAT</code> のように 16-bit 4 成分の 64-bit フォーマットを G-Buffer に使用しています。FP32 と同様、FP16 ではわずらわしい法線のエンコード処理を特別必要としません。
今回は 32-bit フォーマットを使用しましたが、新たに実装する機会があれば 64-bit (FP16) フォーマットを使ってみたいです。</p>
<h2>参考文献</h2>
<ul>
<li><a href="http://aras-p.info/texts/CompactNormalStorage.html">Compact Normal Storage for Small G-Buffers</a> - G-Buffer に法線を格納する手法を比較したページ</li>
</ul>
<div class="footnote">
<hr />
<ol>
<li id="fn:EpicGames_2012">
<p>Epic Games が公開している SIGGRAPH 2012 の発表資料 <em><a href="https://www.unrealengine.com/resources">The Technology Behind the "Unreal Engine 4 Elemental demo"</a></em> では <em>Classic deferred shading</em> という語が出てきます。 <a class="footnote-backref" href="#fnref:EpicGames_2012" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:Crytek_2009">
<p>Crytek が公開している Triangle Game Conference 2009 の発表資料 <em><a href="http://www.crytek.com/cryengine/cryengine3/presentations/a-bit-more-deferred---cryengine3">A bit more deferred - CryEngine 3</a></em> では <em>Classic Deferred Rendering</em> について言及しています。 <a class="footnote-backref" href="#fnref:Crytek_2009" rev="footnote" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:NVIDIA_2004">
<p>次の資料には MRT のフォーマットについて <em>"All must have the same number of bits"</em> そして <em>"You can mix RTs with different number of channels"</em> とあります。 <a href="http://http.download.nvidia.com/developer/presentations/2004/6800_Leagues/6800_Leagues_Deferred_Shading.pdf">http://http.download.nvidia.com/developer/presentations/2004/6800_Leagues/6800_Leagues_Deferred_Shading.pdf</a> (PDF) <a class="footnote-backref" href="#fnref:NVIDIA_2004" rev="footnote" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
<li id="fn:Depth_NearFarClip">
<p>Near, Far クリップを適切に指定しないと、ポリゴンがちらつくことがあります。このピクセルのちらつきを <strong>artifact</strong> と言います。 <a class="footnote-backref" href="#fnref:Depth_NearFarClip" rev="footnote" title="Jump back to footnote 4 in the text">↩</a></p>
</li>
<li id="fn:FP32">
<p>32-bit の浮動小数点数のこと。 <a class="footnote-backref" href="#fnref:FP32" rev="footnote" title="Jump back to footnote 5 in the text">↩</a></p>
</li>
<li id="fn:SuperBible_GitHub">
<p>GitHub の <a href="https://github.com/openglsuperbible/sb6code">openglsuperbible/sb6code</a> から Deferred Shading のサンプルコードを閲覧することが出来ます。 <a class="footnote-backref" href="#fnref:SuperBible_GitHub" rev="footnote" title="Jump back to footnote 6 in the text">↩</a></p>
</li>
<li id="fn:RealtimeShadow">
<p>『リアルタイムシャドウ』Elmar Eisemann, Michael Schwarz, Ulf Assarsson, Michael Wimmer 著、中本浩訳。日本語版 305 ページ参照。G-Buffer について「4 つの 16 ビットバッファを用いるとよい。」とあります。 <a class="footnote-backref" href="#fnref:RealtimeShadow" rev="footnote" title="Jump back to footnote 7 in the text">↩</a></p>
</li>
<li id="fn:NVIDIAGameWorks_GitHub">
<p>GitHub で公開されている NVIDIA GameWorks のサンプルコードリポジトリ <a href="https://github.com/NVIDIAGameWorks/D3DSamples/">NVIDIAGameWorks/D3DSamples</a> のこと。 <a class="footnote-backref" href="#fnref:NVIDIAGameWorks_GitHub" rev="footnote" title="Jump back to footnote 8 in the text">↩</a></p>
</li>
</ol>
</div>ビルドツール GYP で Visual Studio プロジェクトのあれこれを設定しよう2014-12-11T00:00:00+09:00mogemimitag:enginetrouble.net,2014-10-01:2014/10/gyp-build-settings-in-msvs2014.html<p>これまで趣味のビデオゲーム開発に、ビルドツールの <a href="https://code.google.com/p/gyp/">GYP (Generate Your Projects)</a> を利用してきました。
Visual Studio と Xcode のプロジェクト生成に GYP を使用している中で、まとまった具体例やドキュメントが管見の及ぶ限りなかなか見当たりません。
そこで今回は備忘録もかねて、Visual Studio 向けの具体的な GYP の記述例について、一挙にドドーンとまるっとまるごと紹介していきます。</p>
<p>今回は最新(2014 年 10 月現在)の <strong>Visual Studio 14 CTP</strong> を利用します。また Visual Studio で <strong>Clang for Windows (LLVM Clang 3.5)</strong> を
GYP とともに利用する方法についても紹介します。</p>
<h5>一般的な設定</h5>
<ul>
<li><a href="#msvs_settings">GYP で Visual Studio のビルド設定を行う (msvs_settings)</a></li>
<li><a href="#configurations">リリースビルドとデバッグビルドの設定をする (configurations)</a></li>
<li><a href="#GenerateDebugInformation">デバッグ情報を生成する (/DEBUG)</a><ul>
<li><a href="#DebugInformationFormat">デバッグ情報の形式 (/Z7, /Zi, /ZI)</a></li>
<li><a href="#Profile">パフォーマンスツールプロファイラを利用する (/PROFILE)</a></li>
</ul>
</li>
</ul>
<h5>コンパイラツールの設定</h5>
<ul>
<li><a href="#WarningLevel">警告レベルの設定 (/W0, /W1, /W2, /W3, /W4)</a><ul>
<li><a href="#WarnAsError">警告をエラーとして扱う (/WX)</a></li>
<li><a href="#DisableSpecificWarnings">特定のコンパイラの警告を無視する (/wd)</a></li>
</ul>
</li>
<li><a href="#PreprocessorDefinitions">プリプロセッサ定義</a></li>
<li><a href="#RuntimeLibrary">ランタイムライブラリの指定 (/MT, /MTd, /MD, /MDd)</a></li>
<li><a href="#Optimization">コード最適化のオプションを指定する (/Od, /O1, /O2, /Ox)</a></li>
<li><a href="#OmitFramePointers">フレームポインタの省略を有効・無効にする (/Oy, /Oy-)</a></li>
<li><a href="#EnableIntrinsicFunctions">組み込み関数を使用する (/Oi)</a></li>
<li><a href="#EnableFunctionLevelLinking">関数レベルでリンクする (/Gy)</a></li>
<li><a href="#InlineFunctionExpansion">インライン関数の展開 (/Ob1, /Ob2)</a></li>
<li><a href="#FavorSizeOrSpeed">コードの速度とサイズのどちらを優先するか選択 (/Ot, /Os)</a></li>
<li><a href="#BufferSecurityCheck">スタックバッファのセキュリティチェックを行う (/GS, /GS-)</a></li>
<li><a href="#ExceptionHandling">C++ の例外を有効にする (/EHsc, /EHa)</a></li>
<li><a href="#TreatWChar_tAsBuiltInType">wchar_t をビルトイン型として扱う (/Zc:wchar_t, /Zc:wchar_t-)</a></li>
<li><a href="#SuppressStartupBanner">コンパイル時に著作権情報をメッセージとして表示しない(/nologo)</a></li>
<li><a href="#WholeProgramOptimization">プログラム全体の最適化 (/GL)</a></li>
<li><a href="#StringPooling">読み取り専用の文字列プールを有効にする (/GF, /GF-)</a></li>
<li><a href="#EnableFiberSafeOptimizations">ファイバー保護の最適化 (/GT)</a></li>
<li><a href="#BasicRuntimeChecks">基本的なランタイムエラーチェックを有効にする (/RTCs, /RTCu, /RTC1)</a></li>
<li><a href="#EnablePREfast">ネイティブコード分析を有効にする (/analyze)</a></li>
<li><a href="#MinimalRebuild">変更されたソースコードとそれに影響するソースコードだけを再コンパイルする簡易リビルド (/Gm)</a></li>
<li><a href="#FloatingPointExceptions">浮動小数点数の例外処理を有効にする (/fp:except)</a></li>
<li><a href="#CompileAs">ファイルのコンパイル言語オプションを C または C++ に変更する (/TC, /TP)</a></li>
</ul>
<h5>リンカツール/ライブラリアンツールの設定</h5>
<ul>
<li><a href="#AdditionalDependencies">依存するライブラリを追加する (AdditionalDependencies)</a><ul>
<li><a href="#directx11">DirectX 11 を依存するライブラリとして追加する</a></li>
</ul>
</li>
<li><a href="#LinkIncremental">インクリメンタルを有効・無効にする (/INCREMENTAL)</a></li>
<li><a href="#OptimizeReferences">参照されない関数とデータを削除する (/OPT:REF, /OPT:NOREF)</a></li>
<li><a href="#AdditionalOptions">リンカの追加オプションを指定する (AdditionalOptions)</a></li>
<li><a href="#TargetMachine">ターゲットマシンを指定する (/MACHINE)</a></li>
<li><a href="#SubSystem">サブシステムを指定する (/SUBSYSTEM)</a></li>
<li><a href="#GenerateManifest">side-by-side マニフェストを生成する (/MANIFEST)</a></li>
<li><a href="#EnableCOMDATFolding">COMDAT の圧縮 (/OPT:NOICF, /OPT:ICF)</a></li>
<li><a href="#LinkTimeCodeGeneration">リンク時のコード生成を指定する (/LTCG)</a></li>
<li><a href="#ProgramDatabaseFile">プログラムデータベースファイル (PDB, .pdb) の名前を変更する</a></li>
</ul>
<h5>その他</h5>
<ul>
<li><a href="#CharacterSet">文字セットを指定する (CharacterSet)</a></li>
<li><a href="#OutputDirectory">出力ディレクトリを指定する</a></li>
<li><a href="#IntermediateDirectory">中間ファイルの出力先(中間ディレクトリ)を指定する</a></li>
<li><a href="#msvs_guid">プロジェクト固有の GUID を指定する</a></li>
<li><a href="#msbuild_toolset">プラットフォームツールセットを指定する(任意のバージョンのコンパイラに設定する, msbuild_toolset)</a></li>
<li><a href="#clang">Visual Studio + Clang 3.5 でビルドする</a></li>
</ul>
<h2><a name="msvs_settings"></a>GYP で Visual Studio のビルド設定を行う (msvs_settings)</h2>
<p>GYP で Visual Studio 固有のビルド設定を行うには <code>msvs_settings</code> を利用します。
<code>msvs_settings</code> には <code>VCCLCompilerTool</code>, <code>VCLinkerTool</code>, そして <code>VCLibrarianTool</code> などの項目があります。
これらの項目は Visual Studio における <a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vcclcompilertool.aspx">VCCLCompilerTool インターフェイス</a> や <a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vclinkertool.aspx">VCLinkerTool インターフェイス</a> および <a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vclibrariantool.aspx">VCLibrarianTool インターフェイス</a> を提供する
<a href="http://msdn.microsoft.com/ja-jp/library/Microsoft.VisualStudio.VCProjectEngine.aspx">Microsoft.VisualStudio.VCProjectEngine 名前空間</a> にほとんど沿っています。</p>
<p><code>msvs_settings</code> の他に、GYP では任意のプラットフォームツールセットを指定する <code>msbuild_toolset</code> が用意されています。
<code>msbuild_toolset</code> については、このページの「<a href="#msbuild_toolset">プラットフォームツールセットを指定する(任意のバージョンのコンパイラに設定する, msbuild_toolset)</a>」をご覧ください。</p>
<p>また、今回はお話しできませんが、Xcode についても <code>msvs_settings</code> と同様に Xcode 固有のビルド設定を行う <code>xcode_settings</code> が用意されています。
次の例は、Visual Studio と Xcode のビルド設定を含んだ GYP の設定例を示しています。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'WarningLevel'</span><span class="p">:</span> <span class="s1">'4'</span><span class="p">,</span> <span class="c1"># /W4 (Level 4)</span>
<span class="s1">'WarnAsError'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /WX</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'MyApp'</span><span class="p">,</span>
<span class="s1">'product_name'</span><span class="p">:</span> <span class="s1">'MyApp'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'executable'</span><span class="p">,</span>
<span class="s1">'include_dirs'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'../include'</span><span class="p">,</span>
<span class="p">],</span>
<span class="s1">'sources'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'../src/main.cpp'</span><span class="p">,</span>
<span class="p">],</span>
<span class="s1">'msbuild_toolset'</span><span class="p">:</span> <span class="s1">'v120'</span><span class="p">,</span> <span class="c1"># Visual Studio 2013 (v120)</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'DebugInformationFormat'</span><span class="p">:</span> <span class="s1">'3'</span> <span class="c1"># /Zi</span>
<span class="p">},</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GenerateDebugInformation'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /DEBUG</span>
<span class="s1">'Profile'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /PROFILE</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_INLINES_ARE_PRIVATE_EXTERN'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span> <span class="c1"># '-fvisibility-inlines-hidden'</span>
<span class="s1">'GCC_SYMBOLS_PRIVATE_EXTERN'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span> <span class="c1"># '-fvisibility=hidden'</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">]</span> <span class="c1"># targets</span>
<span class="p">}</span>
</pre></div>
<h2><a name="configurations"></a>リリースビルドとデバッグビルドの設定をする (configurations)</h2>
<p>Visual Studio のプロジェクトファイルの多くは、デバッグビルドとリリースビルドの 2 つのコンフィグレーションを持っています。
GYP では <code>configurations</code> を使って、これらのコンフィグレーションを設定することができます。</p>
<p>既定のコンフィグレーションは <code>default_configuration</code> で指定できます。
次の例では、デバッグビルドとリリースビルドを <code>configurations</code> に追加し、
それぞれに沿ったプリプロセッサを定義しています。
また、デフォルトのビルドがデバッグビルドとなるよう <code>default_configuration</code> を設定しています。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'configurations'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'Debug'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'defines'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'DEBUG=1'</span><span class="p">],</span>
<span class="p">},</span>
<span class="s1">'Release'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'defines'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'NDEBUG=1'</span><span class="p">],</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="s1">'default_configuration'</span><span class="p">:</span> <span class="s1">'Debug'</span><span class="p">,</span>
<span class="c1">#'default_configuration': 'Release',</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>この <code>configurations</code> の設定は Visual Studio に限らず Xcode などの環境でも有効です。</p>
<h2><a name="GenerateDebugInformation"></a>デバッグ情報を生成する (/DEBUG)</h2>
<p>デバッグ情報を生成するには、<code>GenerateDebugInformation</code> に <code>true</code> を設定します。
次の例では、デバッグビルド時のみデバッグ情報を生成するようにしています。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'configurations'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'Debug'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'DebugInformationFormat'</span><span class="p">:</span> <span class="s1">'3'</span> <span class="c1"># /Zi</span>
<span class="p">},</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GenerateDebugInformation'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /DEBUG</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="s1">'Release'</span><span class="p">:</span> <span class="p">{</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="s1">'default_configuration'</span><span class="p">:</span> <span class="s1">'Release'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<p>これから 'msvs_settings' の各項目について、具体的な例を挙げながら説明していきます。</p>
<h3><a name="DebugInformationFormat"></a>デバッグ情報の形式 (/Z7, /Zi, /ZI)</h3>
<p>デバッグ情報の形式については <code>DebugInformationFormat</code> で指定できます。これは
<a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vcclcompilertool.debuginformationformat.aspx">VCCLCompilerTool.DebugInformationFormat プロパティ</a>に対応します。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'DebugInformationFormat': '0', # None</span>
<span class="c1">#'DebugInformationFormat': '1', # /Z7 (C7 compatible)</span>
<span class="c1">#'DebugInformationFormat': '3', # /Zi (Program Database)</span>
<span class="s1">'DebugInformationFormat'</span><span class="p">:</span> <span class="s1">'4'</span><span class="p">,</span> <span class="c1"># /ZI (Program Database for Edit And Continue)</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h3><a name="Profile"></a>パフォーマンスツールプロファイラを利用する (/PROFILE)</h3>
<p>パフォーマンスツールプロファイラで使用できる出力ファイルを作成する場合は <code>Profile</code> に <code>true</code> を設定します。
これは
<a href="http://msdn.microsoft.com/ja-jp/library/ays5x7b0.aspx">/PROFILE (パフォーマンス ツール プロファイラー)</a>
に値します。また <code>/PROFILE</code> を使用するには <code>/DEBUG</code> を指定する必要があります。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'DebugInformationFormat'</span><span class="p">:</span> <span class="s1">'3'</span> <span class="c1"># /Zi</span>
<span class="p">},</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GenerateDebugInformation'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /DEBUG</span>
<span class="s1">'Profile'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /PROFILE</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<h2><a name="WarningLevel"></a>警告レベルの設定 (/W0, /W1, /W2, /W3, /W4)</h2>
<p>コンパイラの警告レベルを指定するには <code>WarningLevel</code> を設定します。
0 から 4 までの値を指定することができ、それぞれ Visual C++ コンパイラの警告レベル
TurnOffAllWarnings (警告なし) と Level 1 から Level 4 に対応します。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'WarningLevel': '0', # /W0 (TurnOffAllWarnings)</span>
<span class="c1">#'WarningLevel': '1', # /W1 (Level 1)</span>
<span class="c1">#'WarningLevel': '2', # /W2 (Level 2)</span>
<span class="c1">#'WarningLevel': '3', # /W3 (Level 3)</span>
<span class="s1">'WarningLevel'</span><span class="p">:</span> <span class="s1">'4'</span><span class="p">,</span> <span class="c1"># /W4 (Level 4)</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<h3><a name="WarnAsError"></a>警告をエラーとして扱う (/WX)</h3>
<p>警告をエラーとして扱う場合、<code>WarnAsError</code> を <code>true</code> に設定します。
次の例では警告レベルを <code>/W4</code> とし、警告をエラーとして扱うように <code>/WX</code> を設定しています。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'WarningLevel'</span><span class="p">:</span> <span class="s1">'4'</span><span class="p">,</span> <span class="c1"># /W4 (Level 4)</span>
<span class="s1">'WarnAsError'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /WX</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<h3><a name="DisableSpecificWarnings"></a>特定のコンパイラの警告を無視する (/wd)</h3>
<p>特定のコンパイラの警告を無視するには <code>DisableSpecificWarnings</code> を設定します。
これは
<a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vcclcompilertool.disablespecificwarnings.aspx">VCCLCompilerTool.DisableSpecificWarnings プロパティ</a>に対応します。</p>
<p>次の例では、コンパイラの警告 <a href="http://msdn.microsoft.com/ja-jp/library/axhfhh6x.aspx">C4700</a> と <a href="http://msdn.microsoft.com/ja-jp/library/1wea5zwe.aspx">C4701</a> を無視しています。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'DisableSpecificWarnings'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'4700'</span><span class="p">,</span> <span class="s1">'4701'</span><span class="p">],</span> <span class="c1"># /wd"4700" /wd"4701"</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<h2><a name="PreprocessorDefinitions"></a>プリプロセッサ定義</h2>
<p><code>WIN32_LEAN_AND_MEAN</code> や <code>NOMINMAX</code> などのプリプロセッサの定義は
<code>PreprocessorDefinitions</code> に追加します。
<a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vcclcompilertool.preprocessordefinitions.aspx">VCCLCompilerTool.PreprocessorDefinitions プロパティ</a>
に相当します。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'PreprocessorDefinitions'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'_WIN32_WINNT=0x0601'</span><span class="p">,</span> <span class="c1"># Windows 7 or later</span>
<span class="s1">'WIN32_LEAN_AND_MEAN'</span><span class="p">,</span>
<span class="s1">'NOMINMAX'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<p>これは次のように GYP の <code>defines</code> に書き換えることも可能です。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'defines'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'_WIN32_WINNT=0x0601'</span><span class="p">,</span> <span class="c1"># Windows 7 or later</span>
<span class="s1">'WIN32_LEAN_AND_MEAN'</span><span class="p">,</span>
<span class="s1">'NOMINMAX'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<p><code>defines</code> と使い分ける一つの指針として Visual Studio でのみ有効にしたいプリプロセッサについては、
<code>PreprocessorDefinitions</code> に定義を追加していくと良いでしょう。</p>
<h2><a name="RuntimeLibrary"></a>ランタイムライブラリの指定 (/MT, /MTd, /MD, /MDd)</h2>
<p>"マルチスレッドデバッグ (/MTd)" や "マルチスレッド DLL (/MD)" といったランタイムライブラリは <code>RuntimeLibrary</code> で設定できます。
<a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vcclcompilertool.runtimelibrary.aspx">VCCLCompilerTool::RuntimeLibrary プロパティ</a>
に相当します。
次の例では "マルチスレッド (/MT)" をランタイムライブラリとしてリンクします。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'RuntimeLibrary'</span><span class="p">:</span> <span class="s1">'0'</span><span class="p">,</span> <span class="c1"># /MT</span>
<span class="c1">#'RuntimeLibrary': '1', # /MTd</span>
<span class="c1">#'RuntimeLibrary': '2', # /MD</span>
<span class="c1">#'RuntimeLibrary': '3', # /MDd</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="Optimization"></a>コード最適化のオプションを指定する (/Od, /O1, /O2, /Ox)</h2>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'Optimization'</span><span class="p">:</span> <span class="s1">'0'</span><span class="p">,</span> <span class="c1"># /Od</span>
<span class="c1">#'Optimization': '1', # /O1</span>
<span class="c1">#'Optimization': '2', # /O2</span>
<span class="c1">#'Optimization': '3', # /Ox</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>開発するアプリケーションによって異なりますが、筆者はリリースビルドの場合 <code>/O2</code>, デバッグビルドの場合 <code>/Od</code>, を指定することが多いです。</p>
<h2><a name="OmitFramePointers"></a>フレームポインタの省略を有効・無効にする (/Oy, /Oy-)</h2>
<p>フレームポインタを省略する場合は <code>/Oy</code>, 省略しない場合は <code>/Oy-</code> を指定する必要があります。
<code>/Oy</code> については、以下の MSDN のページをご覧ください。<br />
<a href="http://msdn.microsoft.com/ja-jp/library/2kxx5t2c.aspx">/Oy (フレーム ポインターの省略)</a></p>
<p>次の例ではフレームポインタを省略しないよう設定しています。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'OmitFramePointers': 'true', # /Oy</span>
<span class="s1">'OmitFramePointers'</span><span class="p">:</span> <span class="s1">'false'</span><span class="p">,</span> <span class="c1"># /Oy-</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="EnableIntrinsicFunctions"></a>組み込み関数を使用する (/Oi)</h2>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'EnableIntrinsicFunctions'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># Yes (/Oi)</span>
<span class="c1">#'EnableIntrinsicFunctions': 'false', # No</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p><code>/Oi</code> については、以下の MSDN のページをご覧ください。<br />
<a href="http://msdn.microsoft.com/ja-jp/library/f99tchzc.aspx">/Oi (組み込み関数の生成)</a></p>
<h2><a name="EnableFunctionLevelLinking"></a>関数レベルでリンクする (/Gy)</h2>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'EnableFunctionLevelLinking'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /Gy</span>
<span class="c1">#'EnableFunctionLevelLinking': 'false',</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p><code>/Gy</code> については、以下の MSDN のページをご覧ください。<br />
<a href="http://msdn.microsoft.com/ja-jp/library/xsa71f43.aspx">/Gy (関数レベルのリンクの有効化)</a></p>
<h2><a name="InlineFunctionExpansion"></a>インライン関数の展開 (/Ob1, /Ob2)</h2>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'InlineFunctionExpansion': '0', # Default</span>
<span class="c1">#'InlineFunctionExpansion': '1', # /Ob1</span>
<span class="s1">'InlineFunctionExpansion'</span><span class="p">:</span> <span class="s1">'2'</span><span class="p">,</span> <span class="c1"># /Ob2</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="FavorSizeOrSpeed"></a>コードの速度とサイズのどちらを優先するか選択 (/Ot, /Os)</h2>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'FavorSizeOrSpeed': '0', # Neither</span>
<span class="c1">#'FavorSizeOrSpeed': '1', # /Ot</span>
<span class="s1">'FavorSizeOrSpeed'</span><span class="p">:</span> <span class="s1">'2'</span><span class="p">,</span> <span class="c1"># /Os</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="BufferSecurityCheck"></a>スタックバッファのセキュリティチェックを行う (/GS, /GS-)</h2>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'BufferSecurityCheck': 'true', # /GS</span>
<span class="s1">'BufferSecurityCheck'</span><span class="p">:</span> <span class="s1">'false'</span><span class="p">,</span> <span class="c1"># /GS-</span>
<span class="s1">'DebugInformationFormat'</span><span class="p">:</span> <span class="s1">'3'</span><span class="p">,</span>
<span class="p">},</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GenerateDebugInformation'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="ExceptionHandling"></a> C++ の例外を有効にする (/EHsc, /EHa)</h2>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'ExceptionHandling': '0', # No</span>
<span class="c1">#'ExceptionHandling': '1', # /EHsc</span>
<span class="s1">'ExceptionHandling'</span><span class="p">:</span> <span class="s1">'2'</span><span class="p">,</span> <span class="c1"># /EHa</span>
<span class="s1">'WarnAsError'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span>
<span class="s1">'Optimization'</span><span class="p">:</span> <span class="s1">'0'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="TreatWChar_tAsBuiltInType"></a>wchar_t をビルトイン型として扱う (/Zc:wchar_t, /Zc:wchar_t-)</h2>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'TreatWChar_tAsBuiltInType': 'false', # /Zc:wchar_t-</span>
<span class="s1">'TreatWChar_tAsBuiltInType'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /Zc:wchar_t</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p><code>TreatWChar_tAsBuiltInType</code> または <code>/Zc:wchar_t</code> については、以下の MSDN のページをご覧ください。<br />
<a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vcclcompilertool.treatwchar_tasbuiltintype.aspx">VCCLCompilerTool.TreatWChar_tAsBuiltInType プロパティ</a></p>
<h2><a name="SuppressStartupBanner"></a>コンパイル時に著作権情報をメッセージとして表示しない(/nologo)</h2>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'SuppressStartupBanner': 'false', # No</span>
<span class="s1">'SuppressStartupBanner'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># Yes (/nologo)</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="WholeProgramOptimization"></a>プログラム全体の最適化 (/GL)</h2>
<p>特に指定しなければ <code>false</code> となり、Visual Studio の既定値が指定されます。
<a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vcclcompilertool.wholeprogramoptimization.aspx">VCCLCompilerTool::WholeProgramOptimization プロパティ</a>に対応します。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'WholeProgramOptimization'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /GL</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="StringPooling"></a>読み取り専用の文字列プールを有効にする (/GF, /GF-)</h2>
<p>読み取り専用の文字列プールを有効にする場合は <code>StringPooling</code> を <code>true</code> に設定します。これは
<a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vcclcompilertool.stringpooling.aspx">VCCLCompilerTool::StringPooling プロパティ</a>に対応します。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'StringPooling'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /GF</span>
<span class="c1">#'StringPooling': 'false', # /GF-</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="EnableFiberSafeOptimizations"></a>ファイバー保護の最適化 (/GT)</h2>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'EnableFiberSafeOptimizations': 'false', # No</span>
<span class="s1">'EnableFiberSafeOptimizations'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /GT</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="BasicRuntimeChecks"></a>基本的なランタイムエラーチェックを有効にする (/RTCs, /RTCu, /RTC1)</h2>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'Optimization'</span><span class="p">:</span> <span class="s1">'0'</span><span class="p">,</span>
<span class="c1">#'BasicRuntimeChecks': '0', # Default</span>
<span class="c1">#'BasicRuntimeChecks': '1', # /RTCs</span>
<span class="c1">#'BasicRuntimeChecks': '2', # /RTCu</span>
<span class="s1">'BasicRuntimeChecks'</span><span class="p">:</span> <span class="s1">'3'</span><span class="p">,</span> <span class="c1"># /RTC1</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="EnablePREfast"></a>ネイティブコード分析を有効にする (/analyze)</h2>
<p>ネイティブコード分析を有効にするには <code>EnablePREfast</code> を <code>true</code> に設定します。これは
<a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vcclcompilertool.enableprefast.aspx">VCCLCompilerTool::EnablePREfast プロパティ</a>に対応します。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'EnablePREfast'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /analyze</span>
<span class="s1">'WarnAsError'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /WX</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<h2><a name="MinimalRebuild"></a>変更されたソースコードとそれに影響するソースコードだけを再コンパイルする簡易リビルド (/Gm)</h2>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'MinimalRebuild': 'false', # /Gm-</span>
<span class="s1">'MinimalRebuild'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /Gm</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<h2><a name="FloatingPointExceptions"></a>浮動小数点数の例外処理を有効にする (/fp:except)</h2>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'FloatingPointExceptions': 'false', # /fp:except-</span>
<span class="s1">'FloatingPointExceptions'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /fp:except</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<h2><a name="CompileAs"></a>ファイルのコンパイル言語オプションを C または C++ に変更する (/TC, /TP)</h2>
<p>次の例では、ソースコードを C 言語としてコンパイルするように <code>CompileAs</code> オプションを指定しています。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'CompileAs': '0', # Default</span>
<span class="s1">'CompileAs'</span><span class="p">:</span> <span class="s1">'1'</span><span class="p">,</span> <span class="c1"># /TC (CompileAsC)</span>
<span class="c1">#'CompileAs': '2', # /TP (CompileAsCpp)</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<h2><a name="AdditionalDependencies"></a>依存するライブラリを追加する (AdditionalDependencies)</h2>
<p><code>kernel32.lib</code> や <code>winmm.lib</code> といったライブラリファイルにプロジェクトが依存している場合、<code>AdditionalDependencies</code> に<strong>追加の依存ファイル</strong>として追加することができます。
これは <a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vclinkertool.additionaldependencies.aspx">VCLinkerTool::AdditionalDependencies プロパティ</a>に対応します。</p>
<p>次の例では、Win32 のライブラリを追加しています。適宜アプリケーションに必要なライブラリはコメントアウトを外してリンクするようにしてください。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'AdditionalDependencies'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'winmm.lib'</span><span class="p">,</span>
<span class="s1">'ws2_32.lib'</span><span class="p">,</span>
<span class="s1">'kernel32.lib'</span><span class="p">,</span>
<span class="c1">#'user32.lib',</span>
<span class="c1">#'gdi32.lib',</span>
<span class="c1">#'winspool.lib',</span>
<span class="c1">#'comdlg32.lib',</span>
<span class="c1">#'advapi32.lib',</span>
<span class="c1">#'shell32.lib',</span>
<span class="c1">#'ole32.lib',</span>
<span class="c1">#'oleaut32.lib',</span>
<span class="c1">#'uuid.lib',</span>
<span class="c1">#'odbc32.lib',</span>
<span class="c1">#'odbccp32.lib',</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>これは次のように GYP の <code>libraries</code> で置き換えることも可能です:</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'link_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'libraries'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'-lwinmm.lib'</span><span class="p">,</span>
<span class="s1">'-lws2_32.lib'</span><span class="p">,</span>
<span class="s1">'-lkernel32.lib'</span><span class="p">,</span>
<span class="c1">#'-luser32.lib',</span>
<span class="c1">#'-lgdi32.lib',</span>
<span class="c1">#'-lwinspool.lib',</span>
<span class="c1">#'-lcomdlg32.lib',</span>
<span class="c1">#'-ladvapi32.lib',</span>
<span class="c1">#'-lshell32.lib',</span>
<span class="c1">#'-lole32.lib',</span>
<span class="c1">#'-loleaut32.lib',</span>
<span class="c1">#'-luuid.lib',</span>
<span class="c1">#'-lodbc32.lib',</span>
<span class="c1">#'-lodbccp32.lib',</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<h3><a name="directx11"></a>DirectX 11 を依存するライブラリとして追加する</h3>
<p>次のようにして、<code>AdditionalDependencies</code> として、 DXGI, Direct3D 11, Direct3D Compiler のライブラリを追加します。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'AdditionalDependencies'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'dxgi.lib'</span><span class="p">,</span>
<span class="s1">'d3d11.lib'</span><span class="p">,</span>
<span class="s1">'d3dcompiler.lib'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="LinkIncremental"></a>インクリメンタルを有効・無効にする (/INCREMENTAL)</h2>
<p>インクリメンタルリンクを無効にする場合 (<code>/INCREMENTAL:NO</code>) は '1' を、有効にする場合 (<code>/INCREMENTAL:YES</code>) は '2' を <code>LinkIncremental</code> に設定します。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'LinkIncremental'</span><span class="p">:</span> <span class="s1">'1'</span><span class="p">,</span> <span class="c1"># /INCREMENTAL:NO</span>
<span class="c1">#'LinkIncremental': '2', # /INCREMENTAL:YES</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>開発するアプリケーションによって異なりますが、筆者の場合、リリースビルド時はインクリメンタルリンクを無効にし、デバッグビルド時は有効にすることが多いです。</p>
<h2><a name="OptimizeReferences"></a>参照されない関数とデータを削除する (/OPT:REF, /OPT:NOREF)</h2>
<p>参照されない関数とデータを削除、または保持する場合は <code>OptimizeReferences</code> を設定します。これは
<a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vclinkertool.optimizereferences.aspx">VCLinkerTool.OptimizeReferences プロパティ</a>に対応します。</p>
<p>次の例では、参照されることのない関数を削除する最適化オプション <code>/OPT:REF</code> を指定しています。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'OptimizeReferences': '1', # /OPT:NOREF</span>
<span class="s1">'OptimizeReferences'</span><span class="p">:</span> <span class="s1">'2'</span><span class="p">,</span> <span class="c1"># /OPT:REF</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="AdditionalOptions"></a>リンカの追加オプションを指定する (AdditionalOptions)</h2>
<p><a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vclinkertool.additionaloptions.aspx">VCLinkerTool::AdditionalOptions プロパティ</a>に相当するリンカの追加オプション (<code>AdditionalOptions</code>) を指定することができます。</p>
<p>次の例では <code>/OPT:REF</code> オプションをリンカに追加しています。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'AdditionalOptions'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'/OPT:REF'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="TargetMachine"></a>ターゲットマシンを指定する (/MACHINE)</h2>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'TargetMachine': '0', # Not Set</span>
<span class="s1">'TargetMachine'</span><span class="p">:</span> <span class="s1">'1'</span><span class="p">,</span> <span class="c1"># /MACHINE:X86</span>
<span class="c1">#'TargetMachine': '3', # /MACHINE:ARM</span>
<span class="c1">#'TargetMachine': '4', # /MACHINE:EBC</span>
<span class="c1">#'TargetMachine': '5', # /MACHINE:IA64</span>
<span class="c1">#'TargetMachine': '7', # /MACHINE:MIPS</span>
<span class="c1">#'TargetMachine': '8', # /MACHINE:MIPS16</span>
<span class="c1">#'TargetMachine': '9', # /MACHINE:MIPSFPU</span>
<span class="c1">#'TargetMachine': '10', # /MACHINE:MIPSFPU16</span>
<span class="c1">#'TargetMachine': '14', # /MACHINE:SH4</span>
<span class="c1">#'TargetMachine': '16', # /MACHINE:THUMB</span>
<span class="c1">#'TargetMachine': '17', # /MACHINE:X64</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="SubSystem"></a>サブシステムを指定する (/SUBSYSTEM)</h2>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'SubSystem': '0', # Not Set</span>
<span class="s1">'SubSystem'</span><span class="p">:</span> <span class="s1">'1'</span><span class="p">,</span> <span class="c1"># /SUBSYSTEM:CONSOLE</span>
<span class="c1">#'SubSystem': '2', # /SUBSYSTEM:WINDOWS</span>
<span class="c1">#'SubSystem': '3', # /SUBSYSTEM:NATIVE</span>
<span class="c1">#'SubSystem': '4', # /SUBSYSTEM:EFI_APPLICATION</span>
<span class="c1">#'SubSystem': '5', # /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER</span>
<span class="c1">#'SubSystem': '6', # /SUBSYSTEM:EFI_ROM</span>
<span class="c1">#'SubSystem': '7', # /SUBSYSTEM:EFI_RUNTIME_DRIVER</span>
<span class="c1">#'SubSystem': '8', # WindowsCE</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="GenerateManifest"></a>side-by-side マニフェストを生成する (/MANIFEST)</h2>
<p>side-by-side アセンブリマニフェストを生成する場合は <code>GenerateManifest</code> を 'true' に設定します。
マニフェストファイルについては以下の MSDN ページをご確認ください:<br />
<a href="http://msdn.microsoft.com/ja-jp/library/f2c0w594.aspx">/MANIFEST (side-by-side アセンブリ マニフェストを作成する)</a></p>
<p>次の例ではマニフェストを生成しないように指定しています。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'GenerateManifest': 'true', # /MANIFEST</span>
<span class="s1">'GenerateManifest'</span><span class="p">:</span> <span class="s1">'false'</span><span class="p">,</span> <span class="c1"># /MANIFEST:NO</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<h2><a name="EnableCOMDATFolding"></a>COMDAT の圧縮 (/OPT:NOICF, /OPT:ICF)</h2>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'DebugInformationFormat'</span><span class="p">:</span> <span class="s1">'3'</span><span class="p">,</span>
<span class="s1">'EnableFunctionLevelLinking'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span>
<span class="s1">'Optimization'</span><span class="p">:</span> <span class="s1">'0'</span><span class="p">,</span>
<span class="p">},</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'EnableCOMDATFolding': '1', # /OPT:NOICF</span>
<span class="s1">'EnableCOMDATFolding'</span><span class="p">:</span> <span class="s1">'2'</span><span class="p">,</span> <span class="c1"># /OPT:ICF</span>
<span class="s1">'GenerateDebugInformation'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span>
<span class="s1">'LinkIncremental'</span><span class="p">:</span> <span class="s1">'1'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p><code>EnableCOMDATFolding</code> または <code>/OPT:NOICF</code>, <code>/OPT:ICF</code> オプションについては、以下の MSDN のページをご覧ください。<br />
<a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vclinkertool.enablecomdatfolding.aspx">VCLinkerTool.EnableCOMDATFolding プロパティ</a></p>
<h2><a name="LinkTimeCodeGeneration"></a>リンク時のコード生成を指定する (/LTCG)</h2>
<p>Visual Studio の <code>/LTCG</code> オプションを指定するには、<code>msvs_settings</code> の
<code>VCLibrarianTool.LinkTimeCodeGeneration</code> または <code>VCLinkerTool.LinkTimeCodeGeneration</code> の項目を設定します。</p>
<p>ここで注意することがあります。
静的ライブラリ (<code>static_library</code>) をビルドする場合、ライブラリアンツール (<code>VCLibrarianTool</code>) の <code>LinkTimeCodeGeneration</code> を設定します。
動的ライブラリ (<code>shared_library</code>) または実行ファイル (<code>executable</code>) をビルドする場合は、
リンカツール (<code>VCLinkerTool</code>) の <code>LinkTimeCodeGeneration</code> を指定します。
また、<code>VCLibrarianTool</code> で <code>/LTCG</code> を設定する場合は、<code>LinkTimeCodeGeneration</code> を <code>true</code> にする必要がありますが、
<code>VCLinkerTool.LinkTimeCodeGeneration</code> には <code>1</code> から <code>4</code> 整数値を指定する必要があります。
<code>msvs_settings</code> の設定で特にはまりやすいところだと思われるので注意が必要です。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'gameengine'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'static_library'</span><span class="p">,</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'WholeProgramOptimization'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /GL</span>
<span class="p">},</span>
<span class="s1">'VCLibrarianTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'LinkTimeCodeGeneration'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span> <span class="c1"># /LTCG</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'game-app'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'executable'</span><span class="p">,</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'LinkTimeCodeGeneration': '0', # Default</span>
<span class="s1">'LinkTimeCodeGeneration'</span><span class="p">:</span> <span class="s1">'1'</span><span class="p">,</span> <span class="c1"># /LTCG</span>
<span class="c1">#'LinkTimeCodeGeneration': '2', # /LTCG:PGInstrument</span>
<span class="c1">#'LinkTimeCodeGeneration': '3', # /LTCG:PGOptimize</span>
<span class="c1">#'LinkTimeCodeGeneration': '4', # /LTCG:PGUpdate</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">],</span>
<span class="p">}</span>
</pre></div>
<p><code>LinkTimeCodeGeneration</code> または <code>/LTCG</code> オプションについては、以下の MSDN のページをご覧ください。<br />
<a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vclinkertool.linktimecodegeneration.aspx">VCLinkerTool::LinkTimeCodeGeneration プロパティ</a></p>
<h2><a name="ProgramDatabaseFile"></a>プログラムデータベースファイル (PDB, .pdb) の名前を変更する</h2>
<p><code>/DEBUG</code> オプションを指定すると生成されるプログラムデータベースファイル (PDB, 拡張子は <code>.pdb</code>) の名前を変更します。<code>ProgramDatabaseFile</code> は<a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vclinkertool.programdatabasefile.aspx">VCLinkerTool.ProgramDatabaseFile プロパティ</a>に相当します。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'DebugInformationFormat'</span><span class="p">:</span> <span class="s1">'3'</span><span class="p">,</span>
<span class="p">},</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'ProgramDatabaseFile'</span><span class="p">:</span> <span class="s1">'$(OutDir)$(TargetName).pdb'</span><span class="p">,</span>
<span class="s1">'GenerateDebugInformation'</span><span class="p">:</span> <span class="s1">'true'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>次の例のように、GYP の変数である <code><(PRODUCT_DIR)</code> を指定することも可能です。</p>
<div class="highlight"><pre><span></span><span class="s1">'ProgramDatabaseFile'</span><span class="p">:</span> <span class="s1">'<(PRODUCT_DIR)</span><span class="se">\\</span><span class="s1">$(TargetName).pdb'</span><span class="p">,</span>
</pre></div>
<h2><a name="CharacterSet"></a> 文字セットを指定する (CharacterSet)</h2>
<p>次の例では、文字セットとして Unicode 文字セットを設定しています。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_configuration_attributes'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'CharacterSet'</span><span class="p">:</span> <span class="s1">'1'</span><span class="p">,</span> <span class="c1"># Use Unicode Character Set</span>
<span class="c1">#'CharacterSet': '2', # Use Multi-Byte Character Set</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<h2><a name="OutputDirectory"></a> 出力ディレクトリを指定する</h2>
<p>実行ファイルやライブラリファイルなどの出力先を示す<strong>出力ディレクトリ</strong>を指定するには <code>IntermediateDirectory</code> にファイルパスを設定します。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_configuration_attributes'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'OutputDirectory'</span><span class="p">:</span> <span class="s1">'$(SolutionDir)$(ConfigurationName)'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<h2><a name="IntermediateDirectory"></a> 中間ファイルの出力先(中間ディレクトリ)を指定する</h2>
<p>obj ファイルなどの中間ファイルの出力先を示す<strong>中間ディレクトリ</strong>を指定するには <code>IntermediateDirectory</code> にファイルパスを設定します。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_configuration_attributes'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'IntermediateDirectory'</span><span class="p">:</span> <span class="s1">'$(SolutionDir)..</span><span class="se">\\</span><span class="s1">out</span><span class="se">\\</span><span class="s1">obj</span><span class="se">\\</span><span class="s1">$(ConfigurationName)</span><span class="se">\\</span><span class="s1">$(ProjectName)'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<h2><a name="msvs_guid"></a>プロジェクト固有の GUID を指定する</h2>
<p>Visual Studio のプロジェクトファイル (<code>.vcxproj</code>) はそれぞれ GUID (<code>ProjectGuid</code>) が割り振られています。
GYP では <code>msvs_guid</code> を使って設定することが可能です。
以下の例では、 共有ライブラリをビルドターゲットにしているプロジェクトファイルに特定の GUID を指定しています。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'gameengine-static'</span><span class="p">,</span>
<span class="s1">'product_name'</span><span class="p">:</span> <span class="s1">'gameengine'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'static_library'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'gameengine-shared'</span><span class="p">,</span>
<span class="s1">'product_name'</span><span class="p">:</span> <span class="s1">'GameEngine'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'shared_library'</span><span class="p">,</span>
<span class="s1">'msvs_guid'</span><span class="p">:</span> <span class="s1">'XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">],</span>
<span class="p">}</span>
</pre></div>
<p>GUID の作成については以下の MSDN のページをご覧ください:<br />
<a href="http://msdn.microsoft.com/ja-jp/library/ms241442.aspx">GUID の作成 (guidgen.exe)</a></p>
<h2><a name="msbuild_toolset"></a>プラットフォームツールセットを指定する(任意のバージョンのコンパイラに設定する, msbuild_toolset)</h2>
<p>GYP で任意のバージョンのプラットフォームツールセットを指定するには、<code>msbuild_toolset</code> を利用します。</p>
<p>例えば、プラットフォームツールセットとして <strong>Visual Studio 2013 (v120)</strong> を利用する場合は、
次のように <code>msbuild_toolset</code> に <code>v120</code> を設定します。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="c1">#'msbuild_toolset': 'v110', # Visual Studio 2012 (v110)</span>
<span class="s1">'msbuild_toolset'</span><span class="p">:</span> <span class="s1">'v120'</span><span class="p">,</span> <span class="c1"># Visual Studio 2013 (v120)</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<p>C++11, C++14 の機能を使うためには、Visual Studio 14 (CTP) に含まれるプラットフォームツールセットである <strong>Visual Studio 2014 (v140) </strong> を利用する必要があります。
次のように <code>msbuild_toolset</code> に <code>v140</code> を指定することで対応できます。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msbuild_toolset'</span><span class="p">:</span> <span class="s1">'v140'</span><span class="p">,</span> <span class="c1"># Visual Studio 2014 (v140)</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<h2><a name="clang"></a>Visual Studio + Clang 3.5 でビルドする</h2>
<p>LLVM Clang を利用して Visual Studio 上でビルドするには、<strong>Clang for Windows</strong> を事前にインストールしておく必要があります。
Visual Studio で利用可能な Clang for Windows は次のページからダウンロード可能です。<br />
<a href="http://llvm.org/releases/download.html">LLVM Download Page</a></p>
<p>また、最新の開発バージョン(スナップショットビルド)は以下のページからダウンロード可能です。<br />
<a href="http://llvm.org/builds/">LLVM Snapshot Builds</a></p>
<p>ここでは、2014 年 10 月現在最新のバージョンである Clang 3.5 を例に GYP の設定例を紹介します。</p>
<p>Visual Studio で Clang を利用する場合、プラットフォームツールセットとして <code>msbuild_toolset</code> に <code>LLVM-vs2013</code> を指定する必要があります。
もし必要であれば、プリプロセッサの定義に <code>_HAS_EXCEPTIONS=0</code> を追加後、ビルドしてください。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msbuild_toolset'</span><span class="p">:</span> <span class="s1">'LLVM-vs2013'</span><span class="p">,</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'PreprocessorDefinitions'</span><span class="p">:</span> <span class="p">[</span>
<span class="c1">#'_HAS_EXCEPTIONS=0',</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<h2>最後に</h2>
<p>少々長くなりました。開発のお役に立つことができれば幸いです。
次回は GYP における Xcode の設定について見ていきたいと思います。</p>
<h5>追記 (Dec 11, 2014)</h5>
<p>GYP には <code>msvs_settings</code> の他に <code>msbuild_settings</code> が用意されています。
最新の Visual Studio をターゲットにしている場合、<code>msvs_settings</code> は <code>msbuild_settings</code> でほとんどそのまま置き換えることができます。
このことについて、<a href="/2014/12/msbuild-settings-with-gyp.html">"GYP における MSBuild Settings (msbuild_settings) の使い方"</a> という記事を書きました。
ご参考になれば幸いです。</p>
<h2>参考文献</h2>
<ul>
<li><a href="http://msdn.microsoft.com/ja-jp/library/Microsoft.VisualStudio.VCProjectEngine.aspx">Microsoft.VisualStudio.VCProjectEngine 名前空間 - MSDN</a></li>
<li><a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vcclcompilertool.aspx">VCCLCompilerTool インターフェイス (Microsoft.VisualStudio.VCProjectEngine)</a></li>
<li><a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vclinkertool.aspx">VCLinkerTool インターフェイス (Microsoft.VisualStudio.VCProjectEngine)</a></li>
<li><a href="http://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.vcprojectengine.vclibrariantool.aspx">VCLibrarianTool インターフェイス (Microsoft.VisualStudio.VCProjectEngine)</a></li>
<li><a href="https://code.google.com/p/gyp/wiki/InputFormatReference">InputFormatReference - gyp</a></li>
</ul>今週の進み具合 #18 - ポストプロセスエフェクト2014-09-23T23:58:00+09:00mogemimitag:enginetrouble.net,2014-09-23:2014/09/gamedev-weekly-2014-0923.html<p>季節の変わり目なのか風邪を引き始めて、鼻水やくしゃみが止まりません。
3 週間もブログの更新が空いてしまいました…(汗)
8 月が終わってからこれまでの開発状況を振り返ってみます。</p>
<h2>進み具合 (2014年8月31日 - 9月22日)</h2>
<ul>
<li>ポストプロセスエフェクトの追加</li>
<li>アクターとアクションの追加</li>
<li>パーティクルシステムのリファクタリング </li>
<li>アニメーションのクロスフェード</li>
</ul>
<p>先週する予定だった「爆発エフェクトの追加」「背景の作成」はまったく手をつけていません(汗)</p>
<h2>ポストプロセスエフェクト</h2>
<p>ポストプロセスエフェクトを追加してみました。
以前実装した FXAA の他に、新たに実装したのは、次の通りです。</p>
<ul>
<li>ケラレ 1 (Vignette 1)</li>
<li>ケラレ 2 (Vignette 2)</li>
<li>魚眼レンズ 1 (Fisheye 1)</li>
<li>魚眼レンズ 2 (Fisheye 2)</li>
<li>アンチ魚眼レンズ (Fisheye)</li>
<li>グレースケール (Grayscale)</li>
<li>セピアトーン (Sepia tone)</li>
</ul>
<p>またポストプロセスエフェクトを自由に組み合わせることができるようにしました。順番も任意で変更できます。次の図 1 から図 12 がそのスクリーンショットです。</p>
<p><img alt="(図 1) ポストプロセスエフェクトなし" src="https://enginetrouble.net/images/2014/09/PostProcess_Plain.png" /></p>
<p><em>(図 1) ポストプロセスエフェクトなし</em></p>
<p><img alt="(図 2) FXAA 適用後" src="https://enginetrouble.net/images/2014/09/PostProcess_FXAA.png" /></p>
<p><em>(図 2) FXAA 適用後</em></p>
<p><img alt="(図 3) 魚眼レンズ 1" src="https://enginetrouble.net/images/2014/09/PostProcess_FishEye1.png" /></p>
<p><em>(図 3) 魚眼レンズ 1</em></p>
<p><img alt="(図 4) 魚眼レンズ 2" src="https://enginetrouble.net/images/2014/09/PostProcess_FishEye2.png" /></p>
<p><em>(図 4) 魚眼レンズ 2</em></p>
<p><img alt="(図 5) 魚眼レンズ 2 を適用後、FXAA を適用した場合" src="https://enginetrouble.net/images/2014/09/PostProcess_FishEye2_FXAA.png" /></p>
<p><em>(図 5) 魚眼レンズ 2 を適用後、FXAA を適用した場合</em></p>
<p><img alt="(図 6) アンチ魚眼レンズ" src="https://enginetrouble.net/images/2014/09/PostProcess_AntiFishEye.png" /></p>
<p><em>(図 6) アンチ魚眼レンズ</em></p>
<p><img alt="(図 7) ケラレ 1" src="https://enginetrouble.net/images/2014/09/PostProcess_Vignette.png" /></p>
<p><em>(図 7) ケラレ 1</em></p>
<p><img alt="(図 8) ケラレ 2" src="https://enginetrouble.net/images/2014/09/PostProcess_Vignette_TV.png" /></p>
<p><em>(図 8) ケラレ 2</em></p>
<p><img alt="(図 9) グレースケール" src="https://enginetrouble.net/images/2014/09/PostProcess_Grayscale.png" /></p>
<p><em>(図 9) グレースケール</em></p>
<p><img alt="(図 10) セピアトーン" src="https://enginetrouble.net/images/2014/09/PostProcess_SepiaTone.png" /></p>
<p><em>(図 10) セピアトーン</em></p>
<p><img alt="(図 11) セピアトーンを適用後、FXAA を適用した場合" src="https://enginetrouble.net/images/2014/09/PostProcess_SepiaTone_FXAA.png" /></p>
<p><em>(図 11) セピアトーンを適用後、FXAA を適用した場合</em></p>
<p><img alt="(図 12) FXAA を適用後、セピアトーンを適用した場合" src="https://enginetrouble.net/images/2014/09/PostProcess_FXAA_SepiaTone.png" /></p>
<p><em>(図 12) FXAA を適用後、セピアトーンを適用した場合</em></p>
<p>魚眼レンズ 1 (図 3 参照)と魚眼レンズ 2 (図 4 参照)は、スクリーンの膨らみ方が異なります。ケラレ 1 (図 7 参照)はピクセルシェーダで中心から楕円を描くように暗くしています。ケラレ 1 を踏まえてケラレ 2 (図 8 参照)では単純な楕円ではなく、スクリーンの矩形に沿って暗くなるようにしています。
セピアトーンを適用後に FXAA を適用した場合(図 11 参照) と、FXAA を適用後にセピアトーンを適用した場合(図 12 参照)でレンダリング結果が異なっているところは興味深いです。ポストプロセスエフェクトをかける場合は、エフェクトをかける順番にも心がけたほうがよさそうです。</p>
<h2>今週の予定</h2>
<ul>
<li>パーティクルエフェクトの読み込み</li>
<li>爆発エフェクトの追加</li>
<li>背景の作成</li>
</ul>
<p>いざパーティクルシステムをゲームで利用するとき困ったのが、再生するパーティクルエフェクトの作成です。見た目に関する部分をコードで表現するのは大変なので、エフェクトデータを作成するパーティクルエディタが必要です。エディタを1から作るのはこれまた大変なので、既存のパーティクルエディタを利用することにしました。そこで、パーティクルエディタで作成したパーティクルをインポートできるようにこれから実装します。</p>
<p>(次週は無事に "今週の進み具合 #19" を更新できるよう努めます…。お楽しみに。)</p>今週の進み具合 #17 - ゲーム開発ことはじめ2014-08-31T03:00:00+09:00mogemimitag:enginetrouble.net,2014-08-31:2014/08/gamedev-weekly-2014-0831.html<p>1ヶ月も更新が途絶えていました(汗)
もうめっきり涼しくなって今夏も終わりに近づいています。8月中にやったことを振り返ってみます。</p>
<iframe class="vine-embed" src="https://vine.co/v/OBMUhr29xtn/embed/simple" width="320" height="320" frameborder="0"></iframe>
<script async src="//platform.vine.co/static/scripts/embed.js" charset="utf-8"></script>
<p><em>(動画) 新しく作り始めたゲームのプロトタイプ</em></p>
<h2>進み具合 (2014年7月28日 - 8月30日)</h2>
<ul>
<li>定数バッファ API の追加</li>
<li>アセットのキャッシュ</li>
<li>プロジェクトテンプレートを生成するスクリプトの修正</li>
<li>Experimental ディレクトリの追加</li>
<li>GLSL シェーダの組み込み</li>
<li>ビルトインシェーダのキャッシュ</li>
<li>新しくゲームのリポジトリを作った</li>
</ul>
<h2>アセットのキャッシュ</h2>
<p>1度ロードしたアセットをキャッシュするようにしました。テクスチャやシェーダなどのアセットを読み込むときにファイル I/O の回数が極端に減って速くなりました。加えて、ゲームエンジンが提供するシェーダをすべてソースコードに埋め込んでビルトインシェーダ(Built-in shader)にしました。ソースコードのサイズは大きくなりますが、ファイル I/O を利用しないためランタイムの読み込みがとても速くなります。ビルトインシェーダについても、1度コンパイルしてリンクしたエフェクトはキャッシュするようにしました。</p>
<h2>GLSL シェーダをビルトインシェーダとして組み込む</h2>
<p>GLSL シェーダファイルを C++ のソースコードに埋め込みやすいヘッダファイルとして出力する Python の<a href="https://github.com/mogemimi/pomdog/blob/master/tools/glsl2embedded.py">スクリプト</a>を作りました。例えば、次のような GLSL シェーダがあります。</p>
<div class="highlight"><pre><span></span><span class="cp">#version 330</span>
<span class="c1">// {xy__} = Position.xy</span>
<span class="c1">// {__zw} = TextureCoord.xy</span>
<span class="n">layout</span><span class="p">(</span><span class="n">location</span> <span class="o">=</span> <span class="mo">0</span><span class="p">)</span> <span class="k">in</span> <span class="k">vec4</span> <span class="n">PositionTextureCoord</span><span class="p">;</span>
<span class="k">out</span> <span class="n">VertexData</span> <span class="p">{</span>
<span class="k">vec2</span> <span class="n">TextureCoord</span><span class="p">;</span>
<span class="p">}</span> <span class="n">Out</span><span class="p">;</span>
<span class="k">void</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">gl_Position</span> <span class="o">=</span> <span class="k">vec4</span><span class="p">(</span><span class="n">PositionTextureCoord</span><span class="p">.</span><span class="n">xy</span><span class="p">,</span> <span class="mf">1.0</span> <span class="mf">1.0</span><span class="p">);</span>
<span class="n">Out</span><span class="p">.</span><span class="n">TextureCoord</span> <span class="o">=</span> <span class="n">PositionTextureCoord</span><span class="p">.</span><span class="n">zw</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>今回作ったスクリプトを使って、このシェーダから次のような C++ のソースコードをヘッダファイルとして出力します。</p>
<div class="highlight"><pre><span></span>char const* Builtin_GLSL_VertexShader =
"#version 330\n"
"layout(location=0)in vec4 PositionTextureCoord;\n"
"out VertexData{\n"
"vec2 TextureCoord;\n"
"}Out;\n"
"void main()\n"
"{\n"
"gl_Position=vec4(PositionTextureCoord.xy,1.0 1.0);\n"
"Out.TextureCoord=PositionTextureCoord.zw;\n"
"}\n";
</pre></div>
<p>以前までスプライトバッチや FXAA などのエンジンが提供するシェーダはランタイム時にファイルから読み込んでいましたが、すべて C++ のソースコードに埋め込んでファイル IO を使わないようにしました。</p>
<h2>新しいゲームのリポジトリ</h2>
<p>新しくゲームを作り始めました。ゲームコードはすべて <a href="https://github.com/mogemimi/Majokko">GitHub で公開</a>しています。今後はゲームを作りながら、その都度ゲームエンジンのほうも更新していきます。</p>
<h2>今週の予定</h2>
<ul>
<li>爆発エフェクトの追加</li>
<li>背景の作成</li>
<li>レンダラのリファクタリング</li>
</ul>
<p>(来週は間を空けず "今週の進み具合 #18" を更新できるでしょうか。お楽しみに。)</p>今週の進み具合 #16 - 夏バテと Lua 5.3 ことはじめ2014-07-28T05:56:00+09:00mogemimitag:enginetrouble.net,2014-07-28:2014/07/gamedev-weekly-2014-0728.html<p>夏バテに困ってます。日記の更新が滞っておりました。まとめて 2 週間の進み具合を振り返っていきます。</p>
<h2>進み具合 (2014年7月11日 - 7月27日)</h2>
<p>実装したことは次の通りです。</p>
<ul>
<li>JSON で記述したアニメーションステートの読み込み</li>
<li>Lua 5.3 (work 3) の組み込み</li>
<li>tolua の導入</li>
<li>Selene の導入</li>
<li>RapidJSON のアップデート</li>
<li>レンダリングキューとレンダリングコマンドの実装</li>
<li>エンティティコンポーネントシステムのリファクタリング</li>
</ul>
<p>ゲーム作りについてはあまり進んでいません…(汗)<br />
それぞれについて見ていきます。</p>
<h3>JSON で記述したアニメーションステートの読み込み</h3>
<p>アニメーションステートを外部ファイルから読み込もうと思い、JSON で記述することにしました。前回の日記で Lua を使ってみようと目論んでいましたが、JSON で事足りました。</p>
<p>ステートを指定してアニメーションツリーを再生することができるようになりました。アニメーションステートの遷移と、ステート間でのクロスフェードはまだ実装していません。ここ数ヶ月やってきたスケルタルアニメーションについて、ひとまず一段落したと思います。</p>
<h3>Lua 5.3 (work 3) の組み込み</h3>
<p>開発が進むにつれ、ネイティブの C++ コードだとコンパイルとリンクにかかる時間がプロジェクトの足かせになってきます。そこでスクリプティングの登場です。ランタイムの実行速度とメモリリソースを少しだけ犠牲にする代わりに、ゲームコードを書き換えても再コンパイルをする必要がなくなります。</p>
<p>今回は Lua 5.3 を利用することにしました。理由としては、組み込みやすく言語に癖がないことと、Lua 自体がミニマルであることが挙げられます。他の候補としては、Mono, V8, mruby が挙がりました。特にこだわりがなかったので、バイナリが小さい Lua を使うことにしました。</p>
<h3>tolua の導入</h3>
<p>Lua を使うとなると、ネイティブの C++ と Lua をつなぐバインディング部分を記述することになります。このバインディングを書くのがとても大変で、手作業でやるのは後々大変になるため、出来る限り自動化するのが重要になってきます。バインディング部分を示すコードを<strong>グルーコード (glue code)</strong> と言います。グルーコードを生成してくれるツールはいくつかあります。今回は <a href="http://www.tecgraf.puc-rio.br/~celes/tolua/">tolua</a> を使ってみました。もう 1 つ <a href="http://www.codenix.com/~tolua/">tolua++</a> という tolua を拡張したツールがありますが、こちらは Lua 5.1 にしか対応してなかったため、使用を見送りました。tolua のほうは Lua 5.2 対応でビルドも上手く通ったので利用することにしました。</p>
<h3>Selene の導入</h3>
<p>Lua は C で記述されたライブラリのため、そのまま C++ で扱うには少々面倒です。特に Lua ステートやスタックの管理が大変です。そこでそういった Lua のバインディングを楽にしてくれるライブラリがあります。
バインディングライブラリはグルーコード生成ツールに比べて種類が豊富で、多機能なものから最小限のものまで様々です。アプリケーションに適したものを使うことになるでしょう。
今回は C++11 に友好的でヘッダーオンリーな Lua バインディング <a href="https://github.com/jeremyong/Selene">Selene</a> を使ってみました。他にも C++11 でヘッダーオンリーなライブラリとして <a href="https://github.com/Rapptz/sol/">Sol</a> や <a href="https://github.com/Tomasu/LuaGlue">LuaGlue</a> などがあります。</p>
<h3>RapidJSON のアップデート</h3>
<p>更新の途絶えていた RapidJSON が、最近になって頻繁にコミットされるようになりました。それに伴い以前の <a href="https://code.google.com/p/rapidjson/">googlecode</a> から <a href="https://github.com/miloyip/rapidjson">GitHub</a> にリポジトリを移行しました。これまで溜まっていた pull request をどんどんマージしているようです。
JSON のパーサに RapidJSON を利用しているので嬉しい限りです。早速プロジェクトで使っている RapidJSON をアップデートしました。</p>
<p>サードパーティのライブラリについて現在はコピーして使っていますが、今後 Git サブツリーマージで代用していくべきか少し悩んでいます。</p>
<h3>レンダリングキューとレンダリングコマンドの実装</h3>
<p>レンダリングキューとレンダリングコマンドを実装しました。実装やフレームワークによって、このキューやコマンドが具体的に何を意味するのか変わってきますが、作成したのは 2D ゲーム用のレンダリングシステムです。ゲームワールド内のレンダリング可能なオブジェクトをすべて列挙して、それぞれに対してコマンドを発行し、キューに挿入します。コマンドを描画順(drawing-order または z-order)に並び替えて、キューに挿入されたすべてのコマンドを実行します。こういった描画の流れを行うようにしました。2D ゲームでは描画順が重要になります。こういったレンダリングキューを挟むことで、ゲームワールド内のシーングラフやオブジェクトのデータ構造(ヒエラルキーツリーなど)と描画順が分離できるようになりました。</p>
<h3>エンティティコンポーネントシステムのリファクタリング</h3>
<p>スクリプティングを利用するにあたって、ゲームワールドやレベル、ゲームオブジェクトの記述を考え直す必要がありました。そこで実装しているエンティティコンポーネントシステムを大幅にリファクタリングしました。エンティティコンポーネントシステムについてはいずれまとまったお話をしたいところです。</p>
<h2>今週の予定</h2>
<ul>
<li>1 度読み込んだアセットのキャッシュ</li>
</ul>
<p>新しくゲームオブジェクトをロードするたびに、ファイルを開いて、png フォーマットで読み込んで、OpenGL でテクスチャを作成して、ということをやっています。そのためオブジェクトを作る瞬間にフレームレートががくんと落ちてしまいます。 </p>
<p><img alt="(図 1) おばけをスポーンしているときにフレームレートが落ちる" src="https://enginetrouble.net/images/2014/07/framerate_slowdown_with_spawning.gif" /><br />
<em>(図 1) おばけをスポーンしているときにフレームレートが落ちる</em></p>
<p>そこで、1 度読み込んだアセットはキャッシュしておき、2 回目以降はキャッシュからアセットを取得し、共有するような形にしたいと思います。特にテクスチャのコピーを作るのは、メモリを無駄に食いますし、バッチレンダリングもできません。迅速なコンテントキャッシュの実装が今求められています。</p>
<p>(はたして次週、"今週の進み具合 #17" があるのでしょうか…。お楽しみに。)</p>
<h2>参考文献</h2>
<ul>
<li><a href="http://lua-users.org/wiki/BindingCodeToLua">lua-users wiki: Binding Code To Lua - Lua-users.org</a></li>
</ul>今週の進み具合 #15 - アニメーションブレンディング2014-07-10T23:54:00+09:00mogemimitag:enginetrouble.net,2014-07-10:2014/07/gamedev-weekly-2014-0710.html<p>暑い日が続いてめっきり夏です。2 週間ぶりにゲーム開発の具合を振り返ってみます。 </p>
<h2>進み具合 (2014年6月24日 - 7月10日)</h2>
<p>アニメーションブレンディングを実装しました。</p>
<iframe class="vine-embed" src="https://vine.co/v/MU0aQvhx9TX/embed/simple" width="320" height="320" frameborder="0"></iframe>
<script async src="//platform.vine.co/static/scripts/embed.js" charset="utf-8"></script>
<p><em>(動画) 2つのアニメーションを入力とした線形補間 (Lerp) を使ったアニメーションブレンディング。</em></p>
<p>その他に実装したことは次の通りです:</p>
<ul>
<li>2D ゲーム向けのレンダリングパイプラインの作成</li>
<li>エンティティコンポーネントシステムの更新</li>
<li>ゲーム内エディタのリファクタリング</li>
</ul>
<p>今回は、<a href="/2014/05/gamedev-weekly-2014-0512.html">少し前の日記</a>でも触れたアニメーションブレンディングについて、いつもの通りざっくり見ていきたいと思います。ソースコードはすべて <a href="https://github.com/mogemimi/pomdog">GitHub</a> で公開しているので、詳しい実装について知りたい方はそちらをご参照ください。</p>
<h2>アニメーションブレンディング</h2>
<p>複数のアニメーションを組み合わせて、新たなアニメーションを作成することを <strong>アニメーションブレンディング (Animation blending)</strong> と言います。今回実装したアニメーションブレンディングは、とてもシンプルなもので、2 つのアニメーション間を、重みをつけて線形補間して新たなアニメーションを実行時に計算しています。図 1 のアニメーションクリップ A と図 2 のアニメーションクリップ B から、最終的なポーズを生成しています。</p>
<p><img alt="(図 1) ビームライフルを構えながら走るアニメーションクリップ A" src="https://enginetrouble.net/images/2014/07/Anim_Sequance_Run1.png" /><br />
<em>(図 1) ビームライフルを構えながら走るアニメーションクリップ A</em></p>
<p><img alt="(図 2) ビームライフルを下げつつ走るアニメーションクリップ B" src="https://enginetrouble.net/images/2014/07/Anim_Sequance_Run2.png" /><br />
<em>(図 2) ビームライフルを下げつつ走るアニメーションクリップ B</em></p>
<p>この線形補間を用いるブレンディングをそのまま<strong>LERP ブレンディング</strong>または<strong>重み付き平均を用いたブレンディング</strong>と言います。2 つ(またはそれ以上)のアニメーションを入力として、重みに沿って線形補間を行い、最終的なポーズを出力するという流れです。この計算を図に示したものが図 3 です。</p>
<p><img alt="(図 3) 2入力の 1 次元 LERP ブレンディングツリー" src="https://enginetrouble.net/images/2014/07/Anim_BlendTree_Lerp.png" /><br />
<em>(図 3) 2入力の 1 次元 LERP ブレンディングツリー</em></p>
<p>図を見てお気づきの方もいると思いますが、LERP ブレンディングは木構造になっています。LERP のノードを演算子として見てみると、この木はある種の<strong>式木</strong>と言えます。このようなアニメーションブレンディングの式木を<strong>ブレンディングツリー</strong>といい、昨今の 3D ゲーム開発では非常によく利用されています。ブレンディングツリーはコーディングを必要としないビジュアルなグラフとして扱いやすいため、GUI でアニメーションブレンディングを行えるノードエディタを提供しているミドルウェアも少なくありません。</p>
<p>線形補間(LERP)の他に、アニメーション結果を加算する<strong>加算ブレンディング</strong>や、スケルトンの任意の間接のみについて部分的にブレンディングを行う<strong>部分的なスケルトンブレンディング</strong>などがアニメーションブレンディングの手法としてあります。これらも LERP のように、式木として表現することが可能です。
これらの演算子やツリーを組み合わせて、より複雑で、ダイナミックなアニメーションをゲーム内で作成し再生することができます。</p>
<h3>ブレンディング係数(重み)</h3>
<p>LERP ノードの重み <em>w</em> の値を変更することで、最終的な出力ポーズを変化させることが出来ます。図 4 は重み <em>w</em> をそれぞれ 0 から 100% まで 5 段階で変化させたときの最終的なポーズの例を示しています。</p>
<p><img alt="(図 4) 1 次元 LERP ブレンディングの重み w をそれぞれ 0, 20, 50, 80, 100% と変えたときの出力例" src="https://enginetrouble.net/images/2014/07/Anim_Lerp_Weights.png" /><br />
<em>(図 4) 1 次元 LERP ブレンディングの重み w をそれぞれ 0, 20, 50, 80, 100% と変えたときの出力例</em></p>
<p>重みを変えることで、複雑なアニメーションを少ないアセットで動的に生成することができます。例えば、さわやかに走っているアニメーションと、へとへとになりながら走っているアニメーションの 2 つを作成すれば、その中間の表現を簡単に作ることが出来ます。「どちらかと言えば疲れているけど、まだまだ体力が残っている様子の走っているアニメーション」というような中途半端な表現も可能です。</p>
<h3>エイミング</h3>
<p>さらにこれを応用することで、様々なことができます。その 1 つが<strong>エイミング (Aiming)</strong>, または単に <strong>エイム (Aim)</strong>と呼ばれるものです。これは、銃を使った FPS や TPS でよく使用されます。銃を構えたアニメーションを作成する際に、「正面方向を向いたもの」「右方向を向いたもの」「左方向を向いたもの」の 3 種類を作成します。これらを、実行時のキャラクターの注視方向に合わせて、アニメーションブレンディングします。これで 180 度どの方向にも対応したアニメーションを再生することが出来ます。
今回実装したアニメーションもなんちゃってエイミングをしています。LERP ノードの重みの値を変更することで、銃口の向き、銃の構え方が変化します。</p>
<h3>アニメーションステートの遷移</h3>
<p>キャラクターの状態(ステート)に合わせて、現在のアニメーションから次のアニメーションへとスムーズに遷移させることもゲームではよくあることでしょう。ブレンディングツリーに加えて、状態と遷移関数を対にしたステートマシンをアニメーションシステムとして提供しているゲームエンジンは多いです。ステートマシンも先ほどのブレンディングツリーのように図として表現しやすく、ビジュアルなノードエディタで編集できるものもあります。Unity の <a href="http://japan.unity3d.com/unity/mecanim/">Mecanim</a> がその代表的な例でしょう。ブレンディングツリー単体、またはステートマシンの遷移図も含めて、これらを<strong>アニメーショングラフ (Animation graph)</strong> と言うこともあります。</p>
<h2>今週の予定</h2>
<ul>
<li>アニメーションステートの遷移</li>
<li>Lua スクリプティング</li>
</ul>
<p>アニメーションブレンディングやアニメーションのステートマシンの記述を C++ で行うのは大変です。ソースコードに埋め込んだパラメータを変更するたびにコンパイルするのは、修行僧が険しい山奥で行う苦行そのものでしょう…(汗)<br />
ノードベースのグラフィカルなエディタを作る余力もないため、Lua を使ってスクリプトという形でアニメーショングラフを記述しようと思います。Lua でスクリプティングが可能になれば、そのままゲームコーディングもできるようになります。コンパイル待ちはできるだけ回避したいところです。</p>
<p>(次回、"今週の進み具合 #16" の更新が遅れないようにします…。お楽しみに。)</p>
<h2>参考文献</h2>
<ul>
<li>『ゲームエンジンアーキテクチャ』 ジェイソン・グレゴリー著、大貫宏美、田中幸訳、今給黎隆、桐山忍、鴨島潤、湊和久監修、ソフトバンククリエイティブ。第11章「アニメーションシステム」参照。</li>
</ul>今週の進み具合 #14 - 2D ハードウェアスキニング2014-06-23T23:50:00+09:00mogemimitag:enginetrouble.net,2014-06-23:2014/06/gamedev-weekly-2014-0623.html<h2>進み具合 (2014年6月12日 - 6月23日)</h2>
<p>ハードウェアスキニングを実装しました。</p>
<iframe class="vine-embed" src="https://vine.co/v/MT9Y79KDhZz/embed/simple" width="320" height="320" frameborder="0"></iframe>
<script async src="//platform.vine.co/static/scripts/embed.js" charset="utf-8"></script>
<p><em>(動画) スキンメッシュに対して頂点シェーダでのハードウェアスキニング</em></p>
<ul>
<li>スプライトフォント</li>
<li>ベクトルテクスチャによるフォントレンダリング</li>
<li>ゲーム内エディタの作成</li>
<li>キーボード入力の取得</li>
<li>ハードウェアスキニング</li>
</ul>
<h2>距離フィールドによるフォントレンダリング</h2>
<p>フォントレンダリングやってみました。事前に libGDX Hiero を使ってフォントテクスチャとグリフのテクスチャアトラスを作成してから、実行時にロードして描画しています。内部的には 1 文字を UTF-32 エンコーディングの文字として扱っています。そのため UTF-8, UTF-32 エンコーディングの文字列をレンダリングすることが可能です。
これでフレームレートなどのデバッグ表示がとても楽になります。</p>
<p>また距離フィールドによるベクトルテクスチャを使っているので、テクスチャの解像度はそこそこに、文字サイズを大きくしても綺麗に描画することができるようになりました。ピクセルシェーダに渡すパラメータによって、フォントの太さも自由自在に変えられるので重宝します。図 1 では、<strong>ゲーム内エディタ(in-game editor)</strong> のラベル表示にフォントレンダリングを利用しています。</p>
<p><img alt="ゲーム内エディタのフォントレンダリングにベクトルテクスチャを使用している例" src="https://enginetrouble.net/images/2014/06/editor-gui-for-debug.png" /><br />
<em>(図 1) ゲーム内エディタのフォントレンダリングにベクトルテクスチャを使用している例</em></p>
<h2>ハードウェアスキニング</h2>
<p><img alt="左から「スキンドメッシュ」「スキンドメッシュ + ワイアーフレーム表示」「ワイアーフレーム表示」「ワイアーフレーム表示 + ジョイント(間接)の表示」を表している" src="https://enginetrouble.net/images/2014/06/skinned-meshes.png" /><br />
<em>(図 2) 左から「スキンドメッシュ」「スキンドメッシュ + ワイアーフレーム表示」「ワイアーフレーム表示」「ワイアーフレーム表示 + ジョイント(間接)の表示」を表している</em></p>
<p>やっとハードウェアスキニングを実装しました。頂点シェーダを使って、各頂点につき最大 4 つのボーン行列から重みつき平均を求めて、最終的な頂点の位置を計算しています。図 2 では実際に 64 個の間接から作成した行列パレットを定数バッファにコピーして、GPU 上でスキニングを行っています。
GLSL での頂点シェーダのコード例を以下に示します。</p>
<div class="highlight"><pre><span></span><span class="cp">#version 330</span>
<span class="n">layout</span><span class="p">(</span><span class="n">location</span> <span class="o">=</span> <span class="mo">0</span><span class="p">)</span> <span class="k">in</span> <span class="k">vec4</span> <span class="n">PositionTextureCoord</span><span class="p">;</span>
<span class="n">layout</span><span class="p">(</span><span class="n">location</span> <span class="o">=</span> <span class="mi">1</span><span class="p">)</span> <span class="k">in</span> <span class="k">vec4</span> <span class="n">Weights</span><span class="p">;</span>
<span class="n">layout</span><span class="p">(</span><span class="n">location</span> <span class="o">=</span> <span class="mi">2</span><span class="p">)</span> <span class="k">in</span> <span class="k">ivec4</span> <span class="n">JointIndices</span><span class="p">;</span>
<span class="k">uniform</span> <span class="n">SkinningConstants</span> <span class="p">{</span>
<span class="k">vec4</span> <span class="n">SkinMatrices1</span><span class="p">[</span><span class="mi">64</span><span class="p">];</span>
<span class="k">vec4</span> <span class="n">SkinMatrices2</span><span class="p">[</span><span class="mi">64</span><span class="p">];</span>
<span class="p">};</span>
<span class="c1">// ...</span>
<span class="k">void</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">mat3x2</span> <span class="n">skinning</span> <span class="o">=</span> <span class="k">mat3x2</span><span class="p">(</span><span class="mf">0.0</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="k">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mo">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">4</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">int</span> <span class="n">jointIndex</span> <span class="o">=</span> <span class="n">JointIndices</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="n">jointIndex</span> <span class="o"><</span> <span class="mo">0</span><span class="p">)</span> <span class="p">{</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">mat3x2</span> <span class="n">boneMatrix</span> <span class="o">=</span> <span class="k">mat3x2</span><span class="p">(</span>
<span class="k">vec2</span><span class="p">(</span><span class="n">SkinMatrices1</span><span class="p">[</span><span class="n">jointIndex</span><span class="p">].</span><span class="n">xy</span><span class="p">),</span>
<span class="k">vec2</span><span class="p">(</span><span class="n">SkinMatrices1</span><span class="p">[</span><span class="n">jointIndex</span><span class="p">].</span><span class="n">zw</span><span class="p">),</span>
<span class="k">vec2</span><span class="p">(</span><span class="n">SkinMatrices2</span><span class="p">[</span><span class="n">jointIndex</span><span class="p">].</span><span class="n">xy</span><span class="p">));</span>
<span class="n">skinning</span> <span class="o">+=</span> <span class="n">boneMatrix</span> <span class="o">*</span> <span class="n">Weights</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">vec2</span> <span class="n">position</span> <span class="o">=</span> <span class="p">(</span><span class="n">skinning</span> <span class="o">*</span> <span class="k">vec3</span><span class="p">(</span><span class="n">PositionTextureCoord</span><span class="p">.</span><span class="n">xy</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">)).</span><span class="n">xy</span><span class="p">;</span>
<span class="n">gl_Position</span> <span class="o">=</span> <span class="k">vec4</span><span class="p">(</span><span class="n">position</span><span class="p">.</span><span class="n">xy</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">)</span> <span class="o">*</span> <span class="n">ModelViewProjection</span><span class="p">;</span>
<span class="n">Out</span><span class="p">.</span><span class="n">TextureCoord</span> <span class="o">=</span> <span class="n">PositionTextureCoord</span><span class="p">.</span><span class="n">zw</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p>3D のスキニングとほとんど変わりませんが、大きく違うところは、2 次元なので 3x2 行列で変換できるということです。3D の場合だと 4x3 行列、もしくはボーンの動きを回転とスケールだけに制限して 3x3 行列で表すことがほとんどです。3x2 行列で足りるので、実質 float の 6 個分(= 3 x 2)つまり vec4 と vec2 で 1 つのボーン行列を表現できます。</p>
<h3>苦戦したこと</h3>
<p>ハードウェアスキニング自体は半日ちょっととさくっと作ることができました。時間がかかったのは、アセットをロードしてスキンドメッシュを作成する部分でした。スケルトンデータを作るために Spine を利用していますが、<a href="http://esotericsoftware.com/spine-json-format">ドキュメンテーション</a>を見る限り、スキニングに関するフォーマットが見当たらず、コーディングが難航しました。なんとかエクスポートした Spine の JSON ファイルからフォーマットを推測することができ、スキンドメッシュの読み込みを実装し終えました。</p>
<p>最も時間がかかったのは言うまでもなく、テクスチャとアニメーションデータの作成です…(汗)</p>
<h3>Spine について</h3>
<p>2D スケルタルアニメーションを作る上で <a href="http://esotericsoftware.com/">Spine</a> というソフトウェアを利用しています。Spine は libGDX で作られており、マルチプラットフォームです。また多くのゲームエンジン向けにランタイムが用意されており、C のランタイムもオープンソースとして公開されています。見たところ、C で書かれたランタイムについては、ソフトウェアでのスキニングを実装しているようです。<br />
作っているエンジンでは、これらのランタイムを利用せず、スクラッチでスケルタルアニメーションを実装しました。スキニングについても同様に、公開されているランタイムを使用していないため、頂点シェーダによるハードウェアスキニングを実装することができました。どうしてもインスタンシングなどで頂点数が増えると CPU で処理するには限界があるので、今回は GPU によるスキニングを採用してみました。</p>
<h3>今後のアニメーションシステムについて</h3>
<p>これまで 2D スケルタルアニメーションや剛体アニメーション、スプライトアニメーション、そしてハードウェアスキニングを実装してきました。アニメーションについてはまだまだ課題が残っています。例えばモーフィングや IK (逆運動学)などです。今後実装していきたいのはずばりアニメーションブレンディングです。アニメーションを手作業で作成するのはとても時間がかかり苦労するので、組み合わせ爆発を起こさないためにもアニメーションブレンディングを実装していきます。</p>
<h2>今週の予定</h2>
<ul>
<li>アニメーションステート</li>
<li>スキニング周りのリファクタリング</li>
</ul>
<p>(来週もこのペースで "今週の進み具合 #15" が更新できますように…。お楽しみに。)</p>libGDX Hiero を使ったビットマップフォントおよび距離フィールドテクスチャの作成2014-06-15T00:36:00+09:00mogemimitag:enginetrouble.net,2014-06-15:2014/06/how-to-generate-distance-field-fonts-using-hiero.html<p><strong>libGDX</strong> は Java で開発されているマルチプラットフォームのゲームフレームワークです。特筆すべき点は、ランタイムライブラリだけでなく周辺ツールも豊富に揃えていることでしょう。libGDX はランタイムアプリケーション用のライブラリの他に、コンテントパイプライン(アセットパイプライン)のサポートを行うべく各種ツールを提供しています。例えばテクスチャアトラスを生成する TexturePacker やパーティクルの編集が行える ParticleEditor など、どれもゲーム製作に欠かせないツールです。こういったツールも Java で開発されているため、Windows, OS X, Linux など多くの開発環境で利用することが可能です。</p>
<p>今回紹介する <strong>Hiero</strong> は libGDX が提供しているビットマップフォント作成ツールです。Windows の場合、<a href="http://www.angelcode.com/products/bmfont/">Bitmap Font Generator</a> が有名ですが、残念ながら OS X や Linux で利用することはできません。そこで Hiero の登場です。Hiero は Windows, OS X そして Linux で動作します。これは開発者にとって嬉しいことです。
もう1つ大きな特徴があります。それはビットマップフォントだけでなく距離フィールドテクスチャも作成できるということです。今回は、距離フィールドテクスチャを作成していきつつ、Hiero を紹介したいと思います。</p>
<p>距離フィールドによるベクトル化テクスチャについては Valve による 2007 年に発表された次のペーパーをご参照ください。</p>
<ul>
<li><a href="http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf">Improved Alpha-Tested Magnification for Vector Textures and Special Effects (PDF)</a></li>
</ul>
<h2>最新版 (Nightly build) を手に入れよう</h2>
<p>まずは Hiero を手に入れる必要があります。
libGDX の <a href="http://libgdx.badlogicgames.com/nightlies/">最新のビルド (Nightly build)</a> をウェブからダウンロードして、解凍します。いくつかナイトリービルドが並んでいますので、お好きな形式のファイルを保存してください。ここでは <code>libgdx-nightly-latest.zip</code> をひっぱってきました。</p>
<p>ちなみに、ナイトリービルドを使わずにソースコードを自前でビルドして動かすこともできます。ここでは省略しますが、最新の libGDX を利用する場合はこの方法がよいでしょう。
ソースコードは libGDX の <a href="https://github.com/libgdx/libgdx">GitHub リポジトリ</a> から取得することができます。</p>
<div class="highlight"><pre><span></span>git clone https://github.com/libgdx/libgdx.git
</pre></div>
<h2>Hiero を起動する</h2>
<p>コンソール(コマンドプロンプトまたはターミナル)を開いてください。お使いの環境に Java はインストールされていますか。Java のバージョンを確認しておきましょう。</p>
<div class="highlight"><pre><span></span>java -version
</pre></div>
<p>コンソール上で、さきほど解凍したディレクトリに移動したら、次のコマンドを実行します。コマンド実行後、Hiero が起動します。</p>
<p>Windows の場合:</p>
<div class="highlight"><pre><span></span>java -cp gdx.jar;gdx-natives.jar;gdx-backend-lwjgl.jar;gdx-backend-lwjgl-natives.jar;extensions\gdx-tools\gdx-tools.jar com.badlogic.gdx.tools.hiero.Hiero
</pre></div>
<p>OS X と Linux の場合:</p>
<div class="highlight"><pre><span></span>java -cp gdx.jar:gdx-natives.jar:gdx-backend-lwjgl.jar:gdx-backend-lwjgl-natives.jar:extensions/gdx-tools/gdx-tools.jar com.badlogic.gdx.tools.hiero.Hiero
</pre></div>
<p><img alt="(図 1) Hiero (version 3.0) を起動した例" src="https://enginetrouble.net/images/2014/06/hiero-overview.png" /><br />
<em>(図 1) Hiero (version 3.0) を起動した例</em></p>
<h2>Hiero を設定する</h2>
<p>Hiero は起動できましたか? Hiero を開くといくつか設定項目が並んでいるのが確認できます。それぞれについて見ていきましょう。</p>
<p><strong>Font</strong><br />
まずはフォントを選択しましょう。システムフォントと、任意のフォントファイルの 2 つから選択できます。</p>
<p><strong>Rendering</strong><br />
Glyph cache が実際に出力されるフォントテクスチャです。"Reset Cache" でキャッシュの内容を更新することができます。</p>
<p><strong>Padding</strong><br />
グリフ間の余白を示します。今回生成する距離フィールドテクスチャは余白を必要とするのでぜひ設定しておきましょう。</p>
<h2>Distance Field フォントの設定</h2>
<h3>Distance Field エフェクトの追加</h3>
<p>Hiero で Distance Field によるベクトル化フォントテクスチャを利用するにはエフェクトを追加する必要があります。図 2 のように、Effects の項目で Distance Field エフェクトを追加しましょう。</p>
<p><img alt="(図 2) Effects の項目に新たに Distance Field エフェクトを追加した様子" src="https://enginetrouble.net/images/2014/06/hiero-effects.png" /><br />
<em>(図 2) Effects の項目に新たに Distance Field エフェクトを追加した様子</em></p>
<h3>Distance Field の各パラメータについて</h3>
<p>Hiero は、フォントデータからグリフを一度レンダリングし、そのレンダリング結果からエッジを計算して距離フィールドテクスチャを生成します。元となるグリフのレンダリングサイズが大きければ大きいほど、出力される距離フィールドテクスチャの品質もよくなります。<strong>Scale</strong> は、この元となるグリフのレンダリングサイズを表します。デフォルトは <code>1</code> です。つまり、等倍のグリフからエッジを抽出し、距離フィールドテクスチャを生成します。距離フィールドテクスチャの良さをいかすにも、等倍よりも大きな値を設定します。おおよそ Scale は 3 ~ 6 の値に設定しておけば距離フィールドテクスチャとしては問題ないでしょう。</p>
<blockquote>
<p><strong>Note:</strong>
通常この Scale の値が大きいほど、詳細なエッジの抽出が行われ距離フィールドテクスチャの品質がよくなりますが、その分 Hiero を使ってフォントテクスチャを出力する時間が少しかかります。半角英数字 100 文字程度だと問題ありませんが、日本語のようにひらがな, カタカナ, そして漢字を含めていくと、文字数が 2000 以上を超えることがあります。そうすると、Hiero でテクスチャを保存するときに少し重たくなるかもしれません。</p>
</blockquote>
<p><strong>Spread</strong> はエッジからの最大距離です。デフォルトは <code>1.1</code> です。グリフ間のパディングを考慮しながら設定したほうがいいでしょう。</p>
<h2>フォントテクスチャを出力/保存する</h2>
<p><code>Hiero > File > Save BMFont files (text)</code> から生成したフォントを保存することができます。ファイル名を指定し保存すると、PNG 形式のテクスチャと、<code>.fnt</code> 拡張子のテキストファイルが出力されます。<code>.fnt</code> ファイルには、フォント用のテクスチャアトラスが記述されています。このフォーマットは <a href="http://www.angelcode.com/products/bmfont/">Bitmap Font Generator</a> に由来しています。</p>
<h2>実際に使ってみたときの設定例</h2>
<p>半角英数といくつかの記号(ASCII 文字)からフォントテクスチャを作成してみます。
フォントは <strong>Ubuntu Mono Regular</strong> (<code>ubuntu-font-family-0.80/UbuntuMono-R.ttf</code>) を使用しました。</p>
<table>
<thead>
<tr>
<th align="left">項目</th>
<th align="left">設定した値</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Font Size</td>
<td align="left">32</td>
</tr>
<tr>
<td align="left">Font Rendering</td>
<td align="left">Java</td>
</tr>
<tr>
<td align="left">Rendering - Page width</td>
<td align="left">256</td>
</tr>
<tr>
<td align="left">Rendering - Page height</td>
<td align="left">256</td>
</tr>
<tr>
<td align="left">Padding</td>
<td align="left">(up, right, down, left) = (4, 4, 4, 4)</td>
</tr>
<tr>
<td align="left">Padding</td>
<td align="left">(X, Y) = (0, 0)</td>
</tr>
<tr>
<td align="left">Effects - Color</td>
<td align="left">White</td>
</tr>
<tr>
<td align="left">Effects - Distance field - Color</td>
<td align="left">White</td>
</tr>
<tr>
<td align="left">Effects - Distance field - Scale</td>
<td align="left">6</td>
</tr>
<tr>
<td align="left">Effects - Distance field - Spread</td>
<td align="left">3.8</td>
</tr>
</tbody>
</table>
<p>図 3 は実際に出力したフォントテクスチャの例です。</p>
<p><img alt="(図 3) ベクトル化されたフォントテクスチャの出力例" src="https://enginetrouble.net/images/2014/06/hiero-fonts-output.png" /><br />
<em>(図 3) ベクトル化されたフォントテクスチャの出力例</em></p>
<h2>最後に</h2>
<p>以上が libGDX Hiero で距離フィールドによるベクトル化テクスチャを作成する手順です。Hiero はベクトル化テクスチャだけでなく、通常のビットマップフォントを作成することも可能です。またフォントの縁取りを行うこともできます。これらは Effects から任意のエフェクトを追加することで可能です。
libGDX が提供している各種ツールは多くの環境で動作し、エクスポート形式もゲーム開発の中でよく使われているフォーマットに沿っているため、非常に重宝します。個人でゲームを開発する方は、libGDX のこういったツールを利用してみてはどうでしょうか。</p>
<h2>参考文献</h2>
<ul>
<li><a href="https://github.com/libgdx/libgdx/wiki/Hiero">Hiero - libgdx/libgdx GitHub wiki</a></li>
<li><a href="http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf">Improved Alpha-Tested Magnification for Vector Textures and Special Effects (PDF)</a></li>
<li><a href="http://www.angelcode.com/products/bmfont/doc/file_format.html">Bitmap Font Generator - Documentation</a></li>
<li><a href="http://font.ubuntu.com/">Ubuntu Font Family</a> - Ubuntu Mono Regular フォントを利用しました</li>
</ul>今週の進み具合 #13 - ライトニングボルトエフェクト2014-06-12T21:38:00+09:00mogemimitag:enginetrouble.net,2014-06-12:2014/06/gamedev-weekly-2014-0612.html<h2>進み具合 (2014年5月28日 - 6月11日)</h2>
<p>雷エフェクトを作っていました。</p>
<iframe class="vine-embed" src="https://vine.co/v/MjruZZFBqbA/embed/simple" width="320" height="320" frameborder="0"></iframe>
<script async src="//platform.vine.co/static/scripts/embed.js" charset="utf-8"></script>
<p><em>(動画) 作っていたライトニングボルトエフェクト</em></p>
<p>どうでしょうか、雷っぽく見えるでしょうか。今回作ったライトニングボルトは、雷っぽくするために枝を任意の数だけ分岐しています。上記の動画では枝の分かれた凸凹した雷を表示していますが、パラメータの与え方によっては枝分かれのないまっすぐな光線に変化したりします。<a href="https://docs.unrealengine.com/latest/INT/Engine/Rendering/ParticleSystems/Reference/index.html">Unreal Engine</a> ではこういったシステムを<strong>ビーム</strong> あるいは <strong>ビームパーティクル (Beam particle)</strong> と呼び、パーティクルシステムの亜種として扱っているそうです。</p>
<p>実際に今回作ったビームシステムは、前回話したパーティクルシステムに非常に似ています。光線を生成して、毎フレーム少しずつ状態を更新して、一定時間が経つと消滅させる流れは同じです。光線に与える初期状態や、光線がどう変化するのかといった各状態をパラメータとして変更できるようにしています。例えば、枝を何本分岐させるか、何段階まで再帰的に枝を分岐させるか、など<strong>ブランチング (Branching)</strong> に関するパラメータを用意しています。このパラメータはパーティクルシステムにはない、ビームシステム特有のものです。</p>
<p>気になる描画部分については、バッチレンダリングを利用して、スプライトを使った簡単なラインレンダリングを行っています。いずれはポリラインで実装してみたいところです。</p>
<p>その他に、UI としてスライダーを実装しました。上記の動画で作ってみたスライダーの動きが確認できます。スライダーのおかげでありとあらゆるパラメータ操作が大変簡単になりました。これからはパーティクルの個数や初速度を変更するたびにビルドをしなくてすみます。こんなに重宝するとわかっていたら、もう少し早く作っていました。</p>
<h2>今週の予定</h2>
<ul>
<li>スプライトフォント</li>
<li>キーボードのイベントを取得する</li>
</ul>
<p>文字を表示する仕組みがなくてデバッグが大変でした。</p>
<p>(来週は無事に "今週の進み具合 #14" が更新されるのでしょうか。お楽しみに。)</p>今週の進み具合 #12 - パーティクルシステムことはじめ2014-05-28T21:38:00+09:00mogemimitag:enginetrouble.net,2014-05-28:2014/05/gamedev-weekly-2014-0528.html<h2>進み具合 (2014年5月12日 - 5月27日)</h2>
<p>前回までスケルタルアニメーションをやってきました。スキニングの実装途中でしたが(実装するのが少々面倒くさくなり)スキニングはひとまず隅に置いて、ブランチをきって新しくパーティクルシステムを実装しました。</p>
<iframe class="vine-embed" src="https://vine.co/v/MdZ3gbPFWnI/embed/simple" width="320" height="320" frameborder="0"></iframe>
<script async src="//platform.vine.co/static/scripts/embed.js" charset="utf-8"></script>
<p><em>(動画) 実装したパーティクルシステムで炎のエフェクトを再生している例</em></p>
<p>実装したパーティクルシステムは CPU でシミュレーションを行っています。描画はスプライトバッチ(ハードウェアインスタンシング)を利用しています。</p>
<p>描画に使っているパーティクル素材は図1の通りです。上記の動画では 1 秒間に 256 個生成し、最大 2048 個のパーティクルを一回のドローコールでバッチレンダリングをしています。わかりやすいようにパーティクルスプライトをワイアーフレーム表示したものが図2です。</p>
<p><img alt="(図 1) パーティクル素材" src="https://enginetrouble.net/images/2014/05/particle-smoke.png" /><br />
<em>(図 1) パーティクル素材</em></p>
<p><img alt="(図 2) 左は1秒間に96個のパーティクルを放出した様子。右はそれをワイアーフレーム表示したもの" src="https://enginetrouble.net/images/2014/05/particles-fire-and-wireframe.png" /><br />
<em>(図 2) 左は1秒間に96個のパーティクルを放出した様子。右はそれをワイアーフレーム表示したもの</em></p>
<h2>パーティクル効果</h2>
<p>グラフィカルなビデオゲームには、視覚効果(VFX, または<strong>視覚エフェクト</strong>と呼ばれています)が山のように登場します。その1つが<strong>パーティクル効果</strong> (パーティクルエフェクト, particle effect) です。</p>
<p>パーティクル効果を利用する例として、爆発や炎、煙、雪、雨、花火などを表現するときが挙げられます。お好きなファンタジー作品を何か1つ思い浮かべてみてください。魔法使いが呪文を唱えると、杖の先から大量の星屑や光の粒が放出され、空気中を舞っているでしょう(図 3 を参照)。古来より魔法使いは演出としてパーティクル効果を利用していたのです。</p>
<p><img alt="(図 3) 魔法使いが呪文を唱えると、どこからともなくパーティクルが発生する" src="https://enginetrouble.net/images/2014/05/particles-with-witch.png" /><br />
<em>(図 3) 魔法使いが杖を振ると、どこからともなくパーティクルが発生する</em></p>
<p>現代の魔法使いも、C++ や HLSL, GLSL といった闇の呪文を使ってパーティクル効果を実現しています。最近では <a href="https://github.com/soulwire/WebGL-GPU-Particles">WebGL で実装</a>されたものもあります。わけもわからず "おまじない" を唱えると後で痛い目を見るのは魔法の世界ではお約束ですので、今回はパーティクル効果についてざっくり見ていきたいと思います。</p>
<h2>パーティクルシステム</h2>
<p>パーティクル効果を実現するためにビデオゲームでは<strong>パーティクルシステム</strong>を利用します。このパーティクルシステムは大きく分けて次の 2 つの要素から構成されます。</p>
<ul>
<li>パーティクル(particle, 粒子)</li>
<li>エミッタ(emitter, 放出器)</li>
</ul>
<p><strong>パーティクル</strong>は小さな粒のことで、砂粒のようなものから、空気中の微細なほこり、粉塵、あるいは光子(可視光の粒)を表現します。このパーティクルを大量にスクリーンに表示することで砂埃や煙、炎やレーザー光線などを表現します。
<strong>エミッタ</strong>はこれら大量のパーティクルの発生源となる放出器のことを指します。</p>
<h3>パーティクル</h3>
<p>冬の寒い日に外で息をすると白い煙のような吐息が出ます。
ここだけの話、あれもパーティクル効果です。白く見えるものの正体は空気中の微細な水滴です。やがて数秒も経たないうちに、水滴は水蒸気になり見えなくなります。
これを再現するため、パーティクルには寿命または<strong>生存期間</strong> (Lifetime または Time to live) が設定されています。寿命をむかえたパーティクルはゲームワールドから姿を消し、見えなくなってしまいます。<br />
また、パーティクルは時間の経過とともに徐々に位置が変化します。実装によっては、色、サイズ、回転量または速度ベクトルなども時間経過に応じて変化します。これにより個々のパーティクルが異なる動きを見せ、ダイナミックで複雑な動きをシミュレートすることができます。例えば、2次元空間でのパーティクルは次のような構造体で表現できます。</p>
<div class="highlight"><pre><span></span><span class="k">struct</span> <span class="n">Particle</span> <span class="p">{</span>
<span class="n">Vector2</span> <span class="n">Position</span><span class="p">;</span>
<span class="n">Color</span> <span class="n">Color</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">Rotation</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">Size</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">TimeToLive</span><span class="p">;</span>
<span class="p">};</span>
</pre></div>
<h3>エミッタ</h3>
<p>パーティクルの初期位置や初速はどのようにして決めるのか気になったかと思います。パーティクルの初期状態を決めるのがエミッタの役割です。シンプルなパーティクルシステムを考えてみましょう。パーティクルの初期位置を決めるときに、エミッタの現在位置を利用します。そうすれば、エミッタの位置からパーティクルが放出される様子が想像できるでしょう。エミッタのコード例を示します。</p>
<div class="highlight"><pre><span></span><span class="k">struct</span> <span class="n">ParticleEmitter</span> <span class="p">{</span>
<span class="n">Vector2</span> <span class="n">StartPosition</span><span class="p">;</span>
<span class="n">Color</span> <span class="n">StartColor</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">StartRotation</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">StartSize</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">StartLifetime</span><span class="p">;</span>
<span class="p">};</span>
</pre></div>
<p>パーティクルの初期状態を決定する役目の他に、1秒間に何個のパーティクルを放出するのか、つまりパーティクルの放出率 (発生頻度)を指定するのもエミッタの役割です。また最大でどれくらいの量のパーティクルが発生するのかを指定するのもエミッタの仕事です。このことをふまえたエミッタのコードは次のようになります。</p>
<div class="highlight"><pre><span></span><span class="k">struct</span> <span class="n">ParticleEmitter</span> <span class="p">{</span>
<span class="n">Vector2</span> <span class="n">StartPosition</span><span class="p">;</span>
<span class="n">Color</span> <span class="n">StartColor</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">StartRotation</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">StartSize</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">StartLifetime</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="kt">uint32_t</span> <span class="n">EmissionRate</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="kt">uint32_t</span> <span class="n">MaxParticles</span><span class="p">;</span>
<span class="p">};</span>
</pre></div>
<h2>パーティクルの生成とシミュレーション</h2>
<p>パーティクルシステムには大きく分けて 2 つのフェーズがあります。</p>
<ol>
<li>パーティクルの生成・発生</li>
<li>パーティクルのシミュレーション</li>
</ol>
<p>まず<strong>パーティクルの生成フェーズ</strong>では、エミッタの発生頻度に合わせてパーティクルを生成します。</p>
<div class="highlight"><pre><span></span><span class="k">static</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Particle</span><span class="o">></span> <span class="n">particles</span><span class="p">;</span>
<span class="k">static</span> <span class="kt">float</span> <span class="n">emissionTimer</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">Emit</span><span class="p">(</span><span class="kt">float</span> <span class="n">frameDuration</span><span class="p">,</span> <span class="n">ParticleEmitter</span> <span class="k">const</span><span class="o">&</span> <span class="n">emitter</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">emissionTimer</span> <span class="o">+=</span> <span class="n">frameDuration</span><span class="p">;</span>
<span class="c1">// 1 個発生させるのにかかる時間(秒/個)</span>
<span class="k">auto</span> <span class="n">emissionInterval</span> <span class="o">=</span> <span class="mf">1.0f</span> <span class="o">/</span> <span class="n">emitter</span><span class="p">.</span><span class="n">EmissionRate</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">emissionTimer</span> <span class="o">>=</span> <span class="n">emissionInterval</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">particles</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o"><</span> <span class="n">emitter</span><span class="p">.</span><span class="n">MaxParticles</span><span class="p">)</span> <span class="p">{</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">emissionTimer</span> <span class="o">-=</span> <span class="n">emissionInterval</span><span class="p">;</span>
<span class="n">Particle</span> <span class="n">particle</span><span class="p">;</span>
<span class="n">particle</span><span class="p">.</span><span class="n">TimeToLive</span> <span class="o">=</span> <span class="n">emitter</span><span class="p">.</span><span class="n">StartLifetime</span><span class="p">;</span>
<span class="n">particle</span><span class="p">.</span><span class="n">Position</span> <span class="o">=</span> <span class="n">emitter</span><span class="p">.</span><span class="n">StartPosition</span><span class="p">;</span>
<span class="n">particles</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">particle</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p><strong>パーティクルのシミュレーションフェーズ</strong>では、現在発生しているパーティクルの状態(位置や速度など)を生存期間に応じて変化させます。また、生存期間が過ぎたパーティクルオブジェクトをゲームワールドから削除するのもこのフェーズです。</p>
<div class="highlight"><pre><span></span><span class="k">static</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">Particle</span><span class="o">></span> <span class="n">particles</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">Simulate</span><span class="p">(</span><span class="kt">float</span> <span class="n">frameDuration</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="o">&</span> <span class="nl">particle</span><span class="p">:</span> <span class="n">particles</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Vector2</span> <span class="n">velocity</span> <span class="p">{</span><span class="mf">3.0f</span><span class="p">,</span> <span class="mf">4.0f</span><span class="p">};</span>
<span class="n">particle</span><span class="p">.</span><span class="n">TimeToLive</span> <span class="o">-=</span> <span class="n">frameDuration</span><span class="p">;</span>
<span class="n">particle</span><span class="p">.</span><span class="n">Position</span> <span class="o">+=</span> <span class="p">(</span><span class="n">velocity</span> <span class="o">*</span> <span class="n">frameDuration</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">particles</span><span class="p">.</span><span class="n">erase</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">remove_if</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">begin</span><span class="p">(</span><span class="n">particles</span><span class="p">),</span> <span class="n">std</span><span class="o">::</span><span class="n">end</span><span class="p">(</span><span class="n">particles</span><span class="p">),</span>
<span class="p">[](</span><span class="n">Particle</span> <span class="k">const</span><span class="o">&</span> <span class="n">p</span><span class="p">){</span> <span class="k">return</span> <span class="n">p</span><span class="p">.</span><span class="n">TimeToLive</span> <span class="o"><=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}),</span> <span class="n">std</span><span class="o">::</span><span class="n">end</span><span class="p">(</span><span class="n">particles</span><span class="p">));</span>
<span class="p">}</span>
</pre></div>
<p>この 2 つのフェーズを毎フレーム行うことで大量のパーティクルをシミュレートします。</p>
<div class="highlight"><pre><span></span><span class="kt">void</span> <span class="nf">Update</span><span class="p">(</span><span class="kt">float</span> <span class="n">frameDuration</span><span class="p">,</span> <span class="n">ParticleEmitter</span> <span class="k">const</span><span class="o">&</span> <span class="n">emitter</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Emit</span><span class="p">(</span><span class="n">frameDuration</span><span class="p">,</span> <span class="n">emitter</span><span class="p">)</span><span class="o">:</span>
<span class="n">Simulate</span><span class="p">(</span><span class="n">frameDuration</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<h2>パーティクルシステムの課題</h2>
<p>パーティクルシステムをビデオゲームで実装する上で、以下の問題がつきまといます。</p>
<ul>
<li>大量の粒子を描画する必要があること (A)</li>
<li>大量の粒子の情報を毎フレーム更新する必要があること (B)</li>
</ul>
<p>(A) はスプライトバッチのようなバッチレンダリングを利用することで解決できます。もう少し言えば、ハードウェアインスタンシング(OpenGL だとジオメトリインスタンシング)を用います。1回のドローコールで大量のパーティクルを描画することができればボトルネックになることはありません。</p>
<p>(B) は発生させるパーティクルの最大数にもよります。現在の計算機であれば 2000 個までだったら、ソフトウェア上で計算して問題ないでしょう。大量の粒子をシミュレートしようとするときは厄介です。例えば 10 万個以上のパーティクルの動きを毎フレーム CPU で計算しようとすれば、おそらくフレームレートが急激に落ちます。そこで大量の粒子をシミュレートするときは GPU を用いて計算するのが昨今の有力なアプローチです。頂点シェーダ・ピクセルシェーダを用いて実装することはもちろん、最近ではコンピュートシェーダを利用するケースが多いようです。
Unreal Engine 4 では <a href="https://docs.unrealengine.com/latest/FRA/Engine/Rendering/ParticleSystems/Reference/TypeData/GPUSprites/index.html">GPUSprite</a> として GPU で計算を行うパーティクルシステムを提供しています。GPU を用いると 10 万個以上の大量のパーティクルを表現することができます。また、CPU で実装するとボトルネックになりがちだった複雑なシミュレーションも得意としています。例えば、外部の環境に影響するパーティクルを実装することが出来ます。風に影響を受けたり、重力に影響を受けたり、あるいは物体に衝突したり、とゲームワールドの影響を受けることでインタラクティブな動きを表現することができます。</p>
<blockquote>
<p><strong>Note: 現実のパーティクル</strong><br />
砂粒の大きさは 2 mm から 60 μm、花粉の大きさは 10 μm、タバコの煙に含まれる粒子の大きさは 0.01 μm から 1 μm ほどだそうです。本来の粒子(パーティクル)はあまりにも小さく、煙の塊として肉眼で認識するためには莫大な数のパーティクルを発生させる必要があります。
リアルタイムのビデオゲームで、莫大な数のパーティクルをシミュレーションするのはハードウェアの制約上とても難しいです。そこで現在目にするパーティクルシステムの多くは、パーティクル 1 個の大きさが大きかったり、いくつもの微細な粒子の集合を 1 個のパーティクルとしてシミュレーションしています。</p>
</blockquote>
<h2>柔軟なパーティクルシステム</h2>
<p>放出されるパーティクルがすべて、同じ位置から、同じ速度で、同じ向きで放出されたらどうでしょうか。きっと煙には見えないはずです。初期の状態(位置、速度ベクトル、回転角、サイズあるいは色など)を乱数によって決めたり、あるいは時間経過に応じて初期状態を変化させると見た目の面白いパーティクルシステムになります。そこで多くのゲームエンジンでは次のようなエミッタの設定を行えるようになっています。</p>
<ul>
<li>乱数とその範囲(distribution, variance)を使ったパラメータ制御</li>
<li>カーブやコントロールポイントによるパラメータ制御</li>
</ul>
<p>特に Unity 4 に搭載されているパーティクルシステム Shuriken では <a href="http://docs-jp.unity3d.com/Documentation/Manual/Particle%20System%20Curve%20Editor.html">Curve Editor</a> と呼ばれるエディタを使って、パーティクルの各状態の変化量を、生存期間や速度に応じて細かく制御できるようになっています。</p>
<p>また、パーティクルシステムで、煙や花火、炎、雪、雨など多くの表現を行うためには、多くの設定をカスタマイズでき、それをランタイムで再生することが重要になってきます。
そのため次のような機能を提供しているゲームエンジンもあります。</p>
<ul>
<li>エミッタの形状(パーティクルの放出角、速度、位置に影響する)</li>
<li>重力または加速度</li>
<li>コリジョン</li>
<li>サブエミッタ</li>
</ul>
<p>この中で面白いのがサブエミッタです。パーティクルが寿命を迎えゲームワールドから消えるとともに、そのパーティクルがエミッタに変化します。それが<strong>サブエミッタ</strong>です。打ち上げ花火を表現するときに適した機能です。</p>
<h2>今週の予定</h2>
<ul>
<li>雷エフェクト</li>
<li>ラインレンダリング</li>
</ul>
<p>スキニングはすっかり忘れて、雷や電撃を表現するエフェクトを実装します。できればラインレンダリングもやりたいところ。</p>
<p>(はたして来月 6 月に "今週の進み具合 #13" が更新されるのでしょうか。7, 8 月になったらごめんなさい。)</p>今週の進み具合 #11 - スプライトアニメーション2014-05-11T03:47:00+09:00mogemimitag:enginetrouble.net,2014-05-11:2014/05/gamedev-weekly-2014-0512.html<h2>進み具合 (2014年4月30日 - 5月11日)</h2>
<p>目のまばたきを UV アニメーション(スプライトアニメーション)で実装してみました。</p>
<iframe class="vine-embed" src="https://vine.co/v/M6PJ9zBMABt/embed/simple" width="320" height="320" frameborder="0"></iframe>
<script async src="//platform.vine.co/static/scripts/embed.js" charset="utf-8"></script>
<p><em>(動画) スプライトアニメーションを用いた目のまばたきアニメーション</em></p>
<p>スキニングを実装する予定でしたが、手つかずのまま過ごしてしまいました(汗)<br />
スキニングの実装を行う代わりに、アニメーションシステムのリファクタリングをしていました。
アニメーションシステムのコードがだいぶ増えてきて、複雑になってきたのでなんとかしたいです。
リファクタリングしながら、スキニングの次はアニメーションブレンディングあたりをやってみたいと考えてました。アニメーションブレンディングについて簡単に説明します。</p>
<h2>アニメーションブレンディングについて</h2>
<p>普段歩くときに(寸分たがわぬ正確さで)全く同じ歩き方をする人がこの世にいるでしょうか。おそらくほとんどの人はそんな歩き方をしないはずです。眠たそうに歩いたり、急ぎ足で歩いたり、Wii リモコン片手に歩いたり、いろんな歩き方をしています。「歩く」動き1つをとってみても、人の状態によっていろんな歩き方をします。最近の写実的なインタラクティブゲームではこれを再現しようと試みているタイトルがたくさんあります。ではどうやって再現しましょうか。<br />
もっとも簡単な方法は、それぞれの状態に対する動きのアニメーションクリップを(Maya や Blender などの)DCC ツールを使って気合いと根性で作成し、それをゲーム内で直接再生することです。
もちろん、大量のアニメーションを手作業で作成するのはとても気が滅入る作業です。仮に気を失いそうになりながらもなんとか作成することが出来たとして、次はランタイムで再生する際に何かと困難を極めるはずです。アニメーションデータの量が膨大になってパッケージを配布するのが難しくなるでしょう。そこで、少ないアニメーションを組み合わせて複雑なアニメーションを大量に作成しようという試みがされました。それがアニメーションブレンディングです。</p>
<p>アニメーションブレンディングについてざっくり説明すると:</p>
<ol>
<li>複数のアニメーションクリップから複数のローカルポーズを生成する。</li>
<li>複数のローカルポーズからそれぞれ重みを付けて補間したポーズを生成する(複数のローカルポーズの重み付き平均からポーズを生成する)</li>
<li>生成したローカルポーズからグローバルポーズ(行列パレット, Matrix Palette)を出力する</li>
</ol>
<p>上記のことを行うのがもっとも簡単なアニメーションブレンディング(<strong>重み付き平均を用いたブレンディング</strong>)です。アニメーションブレンディングには上記以外のブレンディング手法も存在します。特殊な<strong>差分ポーズ</strong>を用いて最終的なポーズを作成する<strong>加算ブレンディング</strong>や、スケルトンの部位ごとに別のアニメーションを適用して部分ごとのポーズ(<strong>部分ポーズ</strong>)から全体ポーズを生成する<strong>部分的なスケルトンブレンディング</strong>がそれに該当します。<br />
最初に挙げた重み付き平均を用いたブレンディングと加算ブレンディングを組み合わせて使うことも少なくありません。キャラクターの状態あるいはキャラクターのインスタンスごとに、どのアニメーションブレンディングをどれくらいの量(ブレンディング係数)で適用するかを柔軟に指定できるととても便利でしょう。そこで昨今のゲームエンジンでは、ノードベースのビジュアルなエディタとともに<strong>ブレンディングツリー</strong>が頻繁に利用されています。例えば Unity3D のアニメーションシステム <a href="http://japan.unity3d.com/unity/mecanim/">Mecanim</a> や Unreal Engine 4 の<a href="https://docs.unrealengine.com/latest/INT/Programming/Animation/AnimNodes/index.html">アニメーションシステム</a>ではエディタ上から直接ブレンディングツリーを編集することができ、より複雑なアニメーションブレンディングを簡単に追加することが出来ます。</p>
<p>ここでのアニメーションブレンディングはスケルトンのアニメーションを想定して書いていますが、モーフィングでもこのブレンディングの考えは使われています。表情のパターンを大量に作成するのは難しいでしょう。そこで「普通の表情」と「笑った表情」のみ手作業で作り、ゲーム内ではこの 2 つの表情をブレンディングして様々な表情を作ります。「普通の表情: 10%、笑った表情: 90%」や「普通の表情: 60%、笑った表情: 40%」といったような組み合わせから膨大な表情パターンを作成することが出来ます。重み(ブレンディング係数)を変更することでゲーム内のキャラクターの状態に適した表情を作成することができるので魅力的なアプローチです。</p>
<p>アニメーションブレンディングについては実装するときに(機会があれば…)もう少し掘り下げて説明したいと思います。</p>
<h2>今週の予定</h2>
<ul>
<li>2D スキンドメッシュの作成</li>
<li>2D スキンアニメーション(スキニング)</li>
</ul>
<p>先週に引き続きスキニングを実装します。今週中に終わらせたいところ!</p>
<p>(来週は "今週の進み具合 #12" があるのでしょうか。乞うご期待。)</p>
<h2>参考文献</h2>
<ul>
<li>『ゲームエンジンアーキテクチャ』 ジェイソン・グレゴリー著、大貫宏美、田中幸訳、今給黎隆、桐山忍、鴨島潤、湊和久監修、ソフトバンククリエイティブ。第11章「アニメーションシステム」参照。</li>
</ul>今週の進み具合 #10 - 2D 階層的剛体アニメーション2014-04-29T15:39:00+09:00mogemimitag:enginetrouble.net,2014-04-29:2014/04/gamedev-weekly-2014-0429.html<h2>進み具合 (2014年4月21日 - 4月29日)</h2>
<p>ここ1週間ずっとスケルタルアニメーションを実装していました。
スケルタルアニメーションの再生ができたので、階層的剛体アニメーションをやってみました。</p>
<iframe class="vine-embed" src="https://vine.co/v/Mv6WJnE99AW/embed/simple" width="320" height="320" frameborder="0"></iframe>
<script async src="//platform.vine.co/static/scripts/embed.js" charset="utf-8"></script>
<p><em>(動画) 左が階層的剛体アニメーション, 右がボーンのみのスケルタルアニメーション</em></p>
<p>剛体アニメーションを表示できたので次はスキンアニメーションとスキンドメッシュを実装しようと思います。</p>
<h2>顔のアニメーションはどうする?</h2>
<p>間接やボーンデータを用いたアニメーションである<strong>スケルタルアニメーション</strong>を使ったものとして、<strong>階層的剛体アニメーション</strong>と<strong>スキンアニメーション</strong>があることを前回の日記で少し紹介しました。
ゲームにおけるアニメーションを考える上で重要なものとして <strong>フェイシャルアニメーション</strong> (facial animation) があります。フェイシャルアニメーションはキャラクターの顔に関するアニメーションです。キャラクターの表情を表現したものや、音声データと唇の動きが同期して喋っているかのように見えるアニメーション(<strong>リップシンク</strong>)あるいは目のまばたきといった顔に関するアニメーションを指します。</p>
<p>ボーン(またはジョイント)を使ったスケルタルアニメーションでは、細かい表情の動きをコントロールすることが難しいので、他のアニメーション手法が使われることが多いです。その1つが、<strong>UV アニメーション</strong>です。テクスチャアトラスと UV 座標を用いて、古き良きスプライトアニメーションを行います。図1 は目のまばたきを UV アニメーションで表現する際に使用するテクスチャの例です。</p>
<p><img alt="目のまばたきアニメーションで用いるテクスチャ" src="https://enginetrouble.net/images/2014/04/EyeBlinkingTextureAtlas.png" /><br />
<em>(図 1) 目のまばたきアニメーションで用いるテクスチャ</em></p>
<p>少し前の 3D ゲームで、目のまばたきアニメーションなどに UV アニメーションを利用しているのを見かけました。また今でもノンフォトリアリスティック(Non-Photorealistic, 非写実的)なアートを用いる 3D ゲームでは UV アニメーションを使ってキャラクターの表情を表現しています。セルアニメ調のキャラクターの表情には向いています。2D ゲームのキャラクターとの相性もいいでしょう。<br />
最近のインタラクティブゲーム、特に 3D の写実的な表現を行うゲームでは、<strong>頂点単位のアニメーション</strong>を用いてキャラクターの表情を表現することが多いです。文字通り、頂点単位で動かすことによって、表情を自由自在に表現します。ハードウェアの制約があるため、リアルタイムのビデオゲームでは<strong>モーフターゲットアニメーション</strong>(単にモーフィングや頂点ブレンディングとも言います)という手法がよく利用されます。顔の表情ごとに頂点群をあらかじめいくつか作成しておき、表情と表情の頂点間を補間することで滑らかな頂点単位のアニメーションを実現します。</p>
<p>フェイシャルアニメーションにはいくつか手法がありますが、まずは UV アニメーションを使って目のまばたきアニメーションを実装してみます。その後で 2D の頂点ブレンディングについても考えてみます。</p>
<h2>今週の予定</h2>
<ul>
<li>2D スキンアニメーション(スキニング)</li>
<li>2D スキンドメッシュ</li>
<li>UV アニメーション</li>
</ul>
<p>今週はスキニングに注力します。
(はたして来週は "今週の進み具合 #11" があるのでしょうか…)</p>
<h2>参考文献</h2>
<ul>
<li>『ゲームエンジンアーキテクチャ』 ジェイソン・グレゴリー著、大貫宏美、田中幸訳、今給黎隆、桐山忍、鴨島潤、湊和久監修、ソフトバンククリエイティブ。第11章「アニメーションシステム」参照。</li>
</ul>今週の進み具合 #9 - 2D 階層的剛体スケルトン2014-04-21T06:57:00+09:00mogemimitag:enginetrouble.net,2014-04-21:2014/04/gamedev-weekly-2014-0421.html<p>今週は忘れずに日記を書くことが出来ました(汗)</p>
<p>そういえば、Photoshop Creative Cloud をついに手に入れました。これで絵を描いたり編集したりする作業がはかどりそうです。実際にはかどっているのかどうか先週やったことを振り返ってみます…。</p>
<h2>進み具合 (2014年4月13日 - 4月20日)</h2>
<p>先週やったことは次の通りです:</p>
<ul>
<li>TexturePacker によるテクスチャアトラスデータの読み込み</li>
<li>スケルトンデータ、アニメーションデータの読み込み</li>
<li>階層的スケルトン</li>
<li>ボーンのデバッグ表示</li>
<li>剛体(スキン)の表示</li>
</ul>
<p>ほとんど JSON やテクスチャアトラスデータのパーシング処理を書いていました。その甲斐あって 2D スケルトンデータを読み込んで階層的スケルトンを再生することができました。テクスチャアトラスも扱えるようになったため、スケルトンに剛体として 2D テクスチャを貼ってみました。</p>
<p><img alt="左がボーンをデバッグ表示したもので, 右がボーンに基づいてスキンテクスチャを貼った様子" src="https://enginetrouble.net/images/2014/04/SkeletalBones1.png" /><br />
<em>(図 1) 左がボーンをデバッグ表示したもので, 右がボーンに基づいてスキンテクスチャを貼った様子</em></p>
<h2>スケルタルアニメーション</h2>
<p>しばらくの間スケルタルアニメーションを実装していくので、簡単にスケルタルアニメーションと階層的スケルトンについて説明しておきます。(ここでは本当にざっくりと。)</p>
<h3>なぜ、2D スケルタルアニメーション?</h3>
<p>2D ゲームのアニメーション手法でよく用いられているのが、<strong>スプライトアニメーション</strong> と呼ばれている手法です。ビデオゲームに限らず言えば、セルアニメーションもこれに該当します。これはあらかじめ何枚ものキャラクターのイメージを描き起こしておき(図 2 参照)、ゲーム内でイメージを短い時間の中で順に表示する手法です。図 3 に示すように、プレイヤーの目にはあたかもキャラクターが連続的に動いているかのように見えます。単位時間あたりのイメージの枚数が多くなればなるほど、キャラクターの動きはより連続的に見えます。</p>
<p><img alt="スプライトアニメーションで用いるスプライトの例" src="https://enginetrouble.net/images/2014/04/WalkAnimation2.png" /><br />
<em>(図 2) スプライトアニメーションで用いるスプライトの例</em></p>
<p><img alt="連続して動いているように見えるスプライトアニメーションの例" src="https://enginetrouble.net/images/2014/04/WalkAnimation1.gif" /><br />
<em>(図 3) 連続して動いているように見えるスプライトアニメーションの例</em></p>
<p>このスプライトアニメーションの短所として次の事柄が挙げられます:</p>
<ul>
<li>キャラクターのアクションが増えれば、それだけ描く必要がある</li>
<li>アニメーションに使用するイメージの枚数が多くなるほど、テクスチャデータが大きくなる</li>
</ul>
<p>アニメーションを描き起こすのはとても苦労します。2D の横方向スクロールアクションゲームを考えてみます。1 つのキャラクターのアクションに使用するイメージは 4 枚だとします。キャラクターのアクションが「歩く」「飛ぶ」の 2 アクションだけであれば、2×4 = 8 枚書くだけで済みます。横方向スクロールなので左向きのアニメーションだけ描いて、右向きのアクションはイメージを水平方向反転すればいいでしょう。もしも右方向と左方向のグラフィックを区別したい場合は、16 枚描く必要があるでしょう。新たに「蹴る」「つかむ」「たしなめる」「やさぐれる」の 4 つのアクションが追加されました。また、キャラクターのビジュアルをダウンロードコンテンツでドワーフの姿からエルフの姿に変更できるようになりました。さらに斜め見下ろし視点の上下左右移動可能なゲームに仕様変更となり、新たに後ろ向き、正面向きのアニメーションも必要になりました。さらに斜め移動も可能となり、斜め右後ろ向き、斜め左後ろ向き、斜め右正面向き、斜め左正面向きのアニメーションを描き起こすことに…。</p>
<p>スプライトアニメーションでは、アニメーションのパターンを増やすことがとても難しい問題になっています。特にビデオゲームの場合、ユーザの入力によってゲームの状態が変わるため、同じ「歩く」アクションでも「大きな樽を抱えたまま歩く」アクションと「うさぎの耳をつけたまま歩く」アクション、あるいは両方を満たすアクションが考えられます。もちろん見た目はどれも異なるはずなので、事前にすべてのパターンを描き起こす必要があります。組み合わせの数が増えるほど、アセットを作る負担が増えます。
ここに視点方向(右向き・左向き・正面向き)による組み合わせが追加されたときには組み合わせ爆発を起こす前に、アートセットを作る人が爆発してしまうでしょう。</p>
<p>さらに、仮に描き揃えたとしてもハードウェアの制約があります。イメージの枚数が増えるほど、ゲームデータ全体のテクスチャが占めるサイズは大きくなります。ランタイムのテクスチャバッファも大きくとっておく必要があるでしょう。もちろん、それらを避けるための手法として、<strong>テクスチャアトラス</strong>(図 4)、<strong>UV アニメーション</strong>などの手法も編み出されました。</p>
<p><img alt="1 つの画像ファイルに複数の画像ファイルを敷き詰めたテクスチャアトラスの例" src="https://enginetrouble.net/images/2014/04/TextureAtlas1.png" /><br />
<em>(図 4) 1 つの画像ファイルに複数の画像ファイルを敷き詰めたテクスチャアトラスの例(後述する階層的剛体スケルトンで使用しているテクスチャアトラス)</em></p>
<p>視点方向を自由自在に変更できる 3D のビデオゲームでスプライトアニメーションを行えば、組み合わせの数は膨大な量になるでしょう。ここはおとなしくあきらめて、スプライトアニメーションを行わないテクスチャを貼付けただけのビルボードを描画するのもいいでしょう。もちろん、ユーザがそんなビジュアルを許してくれるとは思いません。(いや待った、それは逆にレトロな雰囲気でアリかも!)</p>
<p>今日の 3D ゲームではスプライトアニメーションに替わって<strong>スケルタルアニメーション</strong>というアニメーション手法が利用されています。もちろん 3D かどうかは関係なく 2D のビデオゲームでも利用することができます。実際に利用しているゲームとしてよく知られているタイトルに「<a href="http://os.atlusnet.jp/">オーディンスフィア</a>」があります。このゲームタイトルを開発した<a href="http://vanillaware.co.jp/">ヴァニラウェア</a>さんは 2D スケルタルアニメーションを利用したゲームを開発していることで有名です。</p>
<p>スケルタルアニメーションの概念を 2D ゲームに持ち込めば、作成するアセットの量を押さえることができます。それどころか、ユーザの入力やシーンに対して、(この言葉が適切かどうか心配ですが)よりインタラクティブな表現(?)が実現できるでしょう。</p>
<h3>階層的剛体スケルトン</h3>
<p>今週実装したことは、スケルトンアニメーションに必要なスケルトン情報の読み込みと、スケルトンのデータ構造を作成したことです。具体的な実装についてはまた次の機会に紹介します。次元の数が減るだけで、よく見かける 3D のスケルトンデータと同じ作りにしてみました。</p>
<p><strong>スケルトン</strong>は次のような階層的な構造になっています:</p>
<div class="highlight"><pre><span></span>Root
|-- Hip
|-- Left Upper Leg
| |-- Left Lower Leg
| |-- Left Foot
|-- Right Upper Leg
| |-- Right Lower Leg
| |-- Rightt Foot
|-- Torso
| |-- Neck
| |-- Head
| |-- Hair
| |-- Eye
...
</pre></div>
<p><code>Root</code> や <code>Hip</code> が<strong>ボーン(骨)</strong>です。ここではボーンと呼んでいますが、厳密に言えば<strong>ジョイント(間接)</strong>であり、ジョイントの親と子の 2 点間を結んだものをボーンと呼びます。<sup id="fnref:1"><a class="footnote-ref" href="#fn:1" rel="footnote">1</a></sup>とはいえ、ジョイントとボーンを同じものとみなしても、ここでは構いません。このボーンはわかりやすいようデバッグ表示していますが(図 1 左を参照)、実際にはプレイヤーの目に映ることはありません。実際に目に映るものとして、図 5 のように、ボーンにくっついて回る四角形(プレーン)にテクスチャを貼付けて表示しています。</p>
<p><img alt="剛体をワイヤーフレーム表示した図" src="https://enginetrouble.net/images/2014/04/SkeletalBones2.png" /><br />
<em>(図 5) 剛体をワイヤーフレーム表示した図</em></p>
<p>このワイヤーフレームで表示している四角形が<strong>剛体</strong>です。もう少し言えば、この四角形を構成する頂点群が剛体です。(剛体は必ずしも四角形でなくてかまいません。)剛体、もっと言えば剛体を構成する頂点は 1 つのボーンにしか影響を受けません<sup id="fnref:2"><a class="footnote-ref" href="#fn:2" rel="footnote">2</a></sup>。この剛体を使ったアニメーションを<strong>階層的剛体アニメーション</strong>と呼びます。この階層的剛体アニメーションには問題点があり、キャラクターが肘や膝を曲げたときに図 6 に示すような肘や膝がぱっくり割れたような見た目になってしまうという弱点があります。</p>
<p><img alt="階層的剛体アニメーションの弱点" src="https://enginetrouble.net/images/2014/04/SkeletalBones3.png" /><br />
<em>(図 6) 階層的剛体アニメーションの弱点</em></p>
<p>そこでこの弱点を克服するために発展させたのが、1 つは<strong>頂点単位のアニメーション</strong>です。ボーンという概念をひとまず置いといて、頂点を動かそうという試みです。今回は頂点単位のアニメーションについてはお話ししませんが、この頂点単位のアニメーションと、階層的剛体アニメーションのいいとこ取りをしたものが<strong>スキニングアニメーション</strong>です。スキニングアニメーションでは肘や膝がぱっくり割れることがありません。特徴として、これまでは 1 つのボーンにのみくっついて動いていた頂点が、複数のボーンの影響を受けることで、剛体でなくなるところにあります。この頂点群を<strong>スキン</strong>や<strong>スキンドメッシュ</strong>と呼びます。
もちろんこれらの概念は 3D アニメーションで頻繁に出てくる物ですが、2D アニメーションでも応用できます。</p>
<p>階層的剛体スケルトンのデータ構造まで実装しました。これからアニメーションデータを実際に再生し、剛体アニメーションをしてみます。アニメーションを再生できたら次はスキニングアニメーションを実装してみます。</p>
<h2>今週の予定</h2>
<ul>
<li>アニメーションデータの再生</li>
<li>スキンドメッシュ</li>
</ul>
<p>引き続きスケルタルアニメーションについて実装していきます。ボーンのアニメーション再生までできたら、スキニングアニメーションについても考えてみます。</p>
<p>(来週も無事 "今週の進み具合 #10" があるのでしょうか。乞うご期待…。)</p>
<h2>参考文献</h2>
<ul>
<li>『ゲームエンジンアーキテクチャ』 ジェイソン・グレゴリー著、大貫宏美、田中幸訳、今給黎隆、桐山忍、鴨島潤、湊和久監修、ソフトバンククリエイティブ。第11章「アニメーションシステム」参照。</li>
</ul>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>この理由はまた日を改めてお話ししたいです。 <a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:2">
<p>ごめんなさい、この部分かなりおおざっぱに書いています。 <a class="footnote-backref" href="#fnref:2" rev="footnote" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
</ol>
</div>今週の進み具合 #8 - FXAA を実装しました2014-04-13T04:25:00+09:00mogemimitag:enginetrouble.net,2014-04-13:2014/04/gamedev-weekly-2014-0413.html<p>日記もコミットもさぼりがちになっていました。ごめんなさい。
コミットログを見ながら実装したことを振り返ってみます。</p>
<h2>進み具合 (2014年3月23日 - 4月12日)</h2>
<ul>
<li>FXAA の実装</li>
<li>高精度タイマー</li>
<li>スプライトのバッチレンダリング</li>
</ul>
<p>ポストプロセスのアンチエイリアシング(post-processing antialiasing)として FXAA を組み込んでみました。また、OS X と Windows で高精度タイマー (High-Resolution Timer) から時間を取得できるようにしました。その他に、ジオメトリインスタンシングと同じ仕組みを使ってスプライトのバッチレンダリングをやってみました。</p>
<h2>FXAA を実装しました</h2>
<p>レンダリング結果のエイリアシング(いわゆるジャギー)を軽減させるため、FXAA を組み込んでみました。
以下に「アンチエイリアシング Off/On」の比較画像を載せます。</p>
<h5>(1)</h5>
<p><img alt="(image 1-A) FXAA: Off" src="https://enginetrouble.net/images/2014/03/Fxaa1A.png" /><br />
<em>(図 1-A) FXAA: Off</em></p>
<p><img alt="(image 1-B) FXAA: On" src="https://enginetrouble.net/images/2014/03/Fxaa1B.png" /><br />
<em>(図 1-B) FXAA: On</em></p>
<h5>(2)</h5>
<p><img alt="(image 2-A) FXAA: Off" src="https://enginetrouble.net/images/2014/03/Fxaa2A.png" /><br />
<em>(図 2-A) FXAA: Off</em></p>
<p><img alt="(image 2-B) FXAA: On" src="https://enginetrouble.net/images/2014/03/Fxaa2B.png" /><br />
<em>(図 2-B) FXAA: On</em></p>
<h5>(3)</h5>
<p><img alt="(image 3-A) FXAA: Off" src="https://enginetrouble.net/images/2014/03/Fxaa3A.png" /><br />
<em>(図 3-A) FXAA: Off</em></p>
<p><img alt="(image 3-B) FXAA: On" src="https://enginetrouble.net/images/2014/03/Fxaa3B.png" /><br />
<em>(図 3-B) FXAA: On</em></p>
<h5>(4)</h5>
<p><img alt="(image 4-A) FXAA: Off" src="https://enginetrouble.net/images/2014/03/Fxaa4A.png" /><br />
<em>(図 4-A) FXAA: Off</em></p>
<p><img alt="(image 4-B) FXAA: On" src="https://enginetrouble.net/images/2014/03/Fxaa4B.png" /><br />
<em>(図 4-B) FXAA: On</em></p>
<p>どうでしょう?(効果があるような、ないような、ちょっとぼけすぎのような…。)</p>
<h3>FXAA についての簡単な説明</h3>
<p>Fast Approximate Anti-Aliasing (FXAA) は NVIDIA の <a href="http://timothylottes.blogspot.jp/">Timothy Lottes</a> 氏が考案したアンチエイリアシング技法です。
2 次元テクスチャを入力とし、ピクセルシェーダを使って実装することができるポストプロセスのアンチエイリアシングです。ポストプロセスとして追加することができ、ピクセルシェーダもいたってシンプルなので、組み込みやすいのが特徴です。</p>
<p>FXAA については次を参照ください:</p>
<ul>
<li><a href="http://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf">FXAA - Nvidia</a> (PDF)</li>
<li><a href="http://iryoku.com/aacourse/">Filtering Approaches for Real-Time Anti-Aliasing</a></li>
</ul>
<p>類似したアンチエイリアシング技法に <a href="http://sites.amd.com/jp/game/technology/Pages/morphological-aa.aspx">Morphological Anti-Aliasing (MLAA)</a> や <a href="http://www.iryoku.com/smaa/">Subpixel Morphological Anti-Aliasing (SMAA)</a> があります。FXAA と同様に MLAA も SMAA もポストプロセスのアンチエイリアシング手法です。
SMAA を以前使用したことがあるので、今回は SMAA と比較しながら大まかに FXAA について紹介します。</p>
<p>FXAA は(SMAA と比べて):</p>
<ul>
<li>シェーダコードが比較的短い</li>
<li>SMAA のように計算用のテクスチャ (AreaTex, SearchTex) を必要としない</li>
<li>エッジ検出から最終的な色を計算するまで(入力テクスチャの描画は含めずに)シングルパスで済む</li>
<li>全体的に「ぼかし」がかかり、シャープな表現が難しい</li>
</ul>
<p>ピクセルシェーダを使ったポストプロセスのアンチエイリアシングでは、大きく次の計算が行われます:</p>
<ol>
<li>エッジ検出(輪郭抽出, Edge detection)</li>
<li>エッジからブレンディングの重みを計算</li>
<li>隣接したピクセルを参照したカラーブレンディング</li>
<li>最終的な色の出力</li>
</ol>
<p>(ざっくり説明すると)<br />
FXAA はアンチエイリアシングを適用するカラーテクスチャを入力とし、輝度情報 (luma) を元にエッジを検出します。
このエッジを元にブレンディングの重みづけを行います。FXAA ではエッジの方向を検出し、重みづけしているようです。MLAA や SMAA ではエッジのエイリアシングの形 (shape) を認識して重みを計算していました(記憶があやふやなため曖昧な記述で申し訳ありません。)
隣接したピクセルのテクスチャカラーをサンプリングし、重みを元にカラーブレンディングします。
ブレンディングした色がアンチエイリアシングされた最終的な出力となります。</p>
<p>FXAA は (1) ~ (4) のプロセスをシングルパスで行います。
それに対して SMAA では、エッジ検出後、エッジテクスチャを出力 (1) します。
このエッジテクスチャを入力とし、重み計算を行い、ブレンドテクスチャを出力 (2) します。
最後にソースとなるカラーテクスチャとブレンドテクスチャを入力とし、最終的なカラーを出力 (3), (4) します。
そのため SMAA は 3 パスのシェーダになります。</p>
<h3>FXAA の良さと特徴</h3>
<p>FXAA を使う利点は、実装が容易で組み込みやすいことです。シェーダはシングルパスですし、入力もカラーテクスチャのみといたって簡単です。マルチテクスチャやマルチレンダーターゲットを利用することもなく、事前計算したテクスチャを用意する必要もありません。
ちょっとしたデモンストレーションに組み込むにはちょうどよいアンチエイリアシング技法です。</p>
<p>FXAA より SMAA のほうがどちらかと言えば、綺麗な結果を出力してくれます(ごめんなさい、今回は SMAA を試していないので比較できる画像がありません)。それはエッジのエイリアシングの検出に違いがあります。FXAA は SMAA に比べ、検出されるエイリアシングの範囲が大きく、エイリアシングではない箇所を含むことがあります。エイリアシングではない箇所も含めて処理するため、ぼかしの適用範囲が大きくなり、全体的にシャープな表現が失われます。それに対して SMAA は FXAA よりシャープな表現を得意としています。(…と言われています。)</p>
<h2>今週の予定</h2>
<ul>
<li>スケルタルアニメーション</li>
</ul>
<p>今週はスケルタルアニメーションを実装したいと思います。</p>
<p>(はたして次回 "今週の進み具合 #9" があるのでしょうか、お楽しみに。)</p>今週の進み具合 #72014-03-23T06:53:00+09:00mogemimitag:enginetrouble.net,2014-03-23:2014/03/gamedev-weekly-2014-0323.html<p>前回の更新からだいぶ日が開いてしまいました。
Git のコミットを見ながら実装したことについて振り返ります。</p>
<h2>進み具合 (2014年2月2日 - 3月22日)</h2>
<p>実装したこと:</p>
<ul>
<li>2D テクスチャの実装 (OpenGL)</li>
<li>2D レンダーターゲットの実装 (OpenGL)</li>
<li>アセットの同期読み込み</li>
<li>PNG テクスチャの読み込み</li>
<li>DDS テクスチャの読み込み</li>
<li>2D カメラの実装/2D スプライトの描画</li>
</ul>
<p>イメージファイルから 2D テクスチャを読み込んでみました。PNG と DDS フォーマットに対応しています。
3D テクスチャとテクスチャキューブについては未実装です。
2D テクスチャとレンダーターゲットが使用できるようになったので、2D カメラとスプライトを実装して描画してみました。</p>
<p>その他にやったこと:</p>
<ul>
<li>Xcode 5.1 (Clang 3.4) でのコンパイルエラーを修正しました</li>
<li>Cocoa でのマウス入力周りを修正しました</li>
<li>Cocoa アプリのアーカイブが行えるよう修正しました</li>
<li>Cocoa アプリのリソースバンドル周りを少し変更しました</li>
</ul>
<h2>これからすること</h2>
<p>これまでは、Windows + Direct3D 11/OpenGL 3.3 で開発してきたソースコードを OS X + Cocoa で動かせるようにポーティングを中心に行ってきました。
ランタイムエンジンの機能が出揃ってきたので、これからは実際にゲームを作りながら必要な機能のみ追加していきます。</p>
<p>実装すること:</p>
<ul>
<li>ポストプロセスアンチエイリアシング (OpenGL)</li>
<li>高精度タイマー</li>
<li>OS X でゲームパッド入力の取得</li>
<li>スプライトフォントのバッチレンダリング</li>
</ul>
<h2>今週の予定</h2>
<p>今週は OpenGL を使ってアンチエイリアシングのポストエフェクトを実装します。
日記の文章を書くのが大変なので、ペタッと画像を貼るだけで済んじゃうような内容を優先していきます(汗)</p>
<p>(はたして来週 "今週の進み具合 #8" があるのでしょうか…。)</p>今週の進み具合 #62014-02-02T06:28:00+09:00mogemimitag:enginetrouble.net,2014-02-02:2014/02/gamedev-weekly-2014-0202.html<p>なんと 1 月が終わってしまいました!!<br />
先月からの進み具合を振り返っていきます。</p>
<h2>進み具合 (2014年1月20日 - 2月1日)</h2>
<p>コミット状況を見てわかる通り、開発が停滞しています。(何とかしないと…!)</p>
<p>開発が停滞していた間に大きく 2 つのことをしていました。
1 つは、先月公開した <a href="/2014/01/gyp-basic-functions.html">GYP の使い方</a>を書いていました。
内容が内容であることと、書くのが遅いことが相まって時間がかかりました。
去年の 7 月から少しずつ書いていたお話なので、やっと公開できて嬉しいです。</p>
<p>もう 1 つ、使用している Pelican のテーマを編集しました。
テーマ作成をしている間はどうも夢中になってしまって、気づけば時間が過ぎていることが多いので危険です(汗)</p>
<h2>OS X Mavericks で絵文字 (emoji) を使う</h2>
<p>OS X Mavericks から絵文字入力のショートカットキー <code>Control + Command + Space</code> が追加されたそうです。
せっかくなので絵文字入力ショートカットキーを試したところ、ショートカットキーが有効になっていませんでした。</p>
<p>そこで <code>~/Library/Preferences/.GlobalPreferences.plist</code> に <code>Control + Command + Space</code> ショートカットキーを新たに追加しました。
手順は次の通りです。下記のコマンドをターミナルで実行すると、Mavericks で絵文字ショートカットキーが有効になります。(もちろん、自己責任で!!)</p>
<div class="highlight"><pre><span></span>defaults write -g NSUserKeyEquivalents -dict-add <span class="s1">'Special Characters...'</span> <span class="s1">'@^ '</span>
</pre></div>
<p>無事に、設定したショートカットキーから絵文字の入力を素早く行うことが出来ました。</p>
<p>さて、Unicode で表現可能な emoji であれば C++ のソースコード内で使うことが出来ます。<br />
もともと、日本語(を含めた多言語)のコメントを利用するため、C++ のソースファイルのエンコーディングを UTF-8 (BOM あり<sup id="fnref:1"><a class="footnote-ref" href="#fn:1" rel="footnote">1</a></sup>) にしていました。
ということで試しに、ソースコード中の識別子を全部 emoji に置き換えて Xcode でビルドしてみたところ、ビルドが通ることを確認しました。これだったら誰でも楽しく C++ を書けそうです。</p>
<h2>今週の予定</h2>
<p>emoji でプロジェクトを書き直します。(ごめんなさい、冗談です)<br />
時間がだいぶ開いてしまったので書いてきたコードを読み直しつつ、テクスチャの実装にとりかかります。</p>
<p>(来週は "今週の進み具合 #7" があるのでしょうか…。)</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>Visual C++ Compiler で UTF-8 エンコーディングされたソースコードを扱うために BOM をつけています。
Xcode で編集中にいつの間にか BOM を消していることがあるのでちょっと大変です。 <a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
</ol>
</div>GYP (Generate Your Projects) の入力フォーマットと基本的な使い方2015-09-27T00:00:00+09:00mogemimitag:enginetrouble.net,2014-01-23:2014/01/gyp-basic-functions.html<p><a href="https://code.google.com/p/gyp/">GYP (Generate Your Projects)</a> の基本的な使い方について紹介します。
GYP の導入からプロジェクトを生成してビルドするまでの大まかな流れについて知りたい方は<a href="/2013/07/getting-started-with-gyp.html">前回の記事(ビルドオートメーションツールGYPを使おう)</a>をご参照ください。</p>
<ul>
<li><a href="#input-format">基本的な入力フォーマット</a></li>
<li><a href="#dictionary-keywords">ディクショナリのキーワード一覧</a></li>
<li><a href="#conditions">条件分岐 (conditions)</a></li>
<li><a href="#variables">変数 (variables)</a></li>
<li><a href="#includes">共通の設定ファイルを読み込む (includes)</a></li>
<li><a href="#targets">ビルドターゲット・ソースファイルの指定 (targets)</a></li>
<li><a href="#target-defaults">ビルドターゲット間で共通の設定を記述する (target_defaults)</a></li>
<li><a href="#depth">DEPTH</a></li>
</ul>
<h2><a name="input-format"></a>基本的な入力フォーマット</h2>
<p>プロジェクトの生成に必要な入力ファイルは、編集しやすいプレーンテキストです。拡張子は <code>.gyp</code> を使用します。
<code>.gyp</code> ファイルの構文規則は Python のデータ構造 (Data Structures) を採用しています<sup id="fnref:1"><a class="footnote-ref" href="#fn:1" rel="footnote">1</a></sup>。Python を使ったことがなくても、JSON に似ているので馴染みのある方ならきっとすぐに慣れるでしょう。
<code>.gyp</code> ファイルでは次の項目が使用できます:</p>
<ul>
<li>角括弧 (<code>[</code>と<code>]</code>) とカンマ (<code>,</code>) で要素を区切ったリスト (List)</li>
<li>キー (Key) と値 (Value) のペアであるディクショナリ (Dictionary, 辞書または連想配列)</li>
<li>文字列</li>
<li>数値(整数と小数)</li>
</ul>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'key'</span><span class="p">:</span> <span class="s1">'string-value'</span><span class="p">,</span>
<span class="s1">'list'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'element1'</span><span class="p">,</span>
<span class="s1">'element2'</span><span class="p">,</span>
<span class="s1">'element3'</span><span class="p">,</span>
<span class="p">],</span>
<span class="s1">'numbers'</span><span class="p">:</span> <span class="p">[</span>
<span class="mi">1</span><span class="p">,</span>
<span class="s1">'2'</span><span class="p">,</span>
<span class="s1">'+3'</span><span class="p">,</span>
<span class="s1">'-4'</span><span class="p">,</span>
<span class="s1">'5.0'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">}</span>
</pre></div>
<p>JSON における文字列はダブルクオーテーションで括りますが、GYP ではシングルクオーテーションで括られた文字列も有効です。慣例的にはシングルクオーテーションがよく使われます。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'key'</span><span class="p">:</span><span class="s1">'string-value'</span><span class="p">,</span>
<span class="p">}</span>
</pre></div>
<p>数値は文字列と同じくシングルクオーテーションで括って指定します。符号なし整数はシングルクオーテーションを付けずに指定することができます。</p>
<div class="highlight"><pre><span></span>{
'numbers': [
1,
'2',
'+3',
'-4',
'5.0',
],
}
</pre></div>
<p>また、GYP の入力フォーマットでは次のような末尾のカンマが許可されています:</p>
<div class="highlight"><pre><span></span><span class="p">[</span>
<span class="s1">'The'</span><span class="p">,</span>
<span class="s1">'Marshmallow'</span><span class="p">,</span>
<span class="s1">'Times'</span><span class="p">,</span>
<span class="p">]</span>
</pre></div>
<blockquote>
<p><strong>Note:</strong>
JSON では末尾のカンマは許可されていませんが、GYP の入力フォーマットとして採用されている Python Data Structures では許可されています。
末尾のカンマが許可されていることで、リストの末尾に要素を追加したり、要素の順番を並べ替えることが容易にできます。
末尾のカンマの取り外しを気にする必要がなく、とても重宝します。</p>
</blockquote>
<h3>コメントの書き方</h3>
<p><code>.gyp</code> ファイルではコメントを書くことができます。シャープ記号(<code>#</code>)から行末まで有効です。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="c1"># comment out</span>
<span class="p">}</span>
</pre></div>
<h2><a name="dictionary-keywords"></a>ディクショナリのキーワード一覧</h2>
<p><code>.gyp</code> ファイルではディクショナリのキーとして多くのキーワード(予約語)が出てきます。以下にその例を挙げます。</p>
<ul>
<li><code>'conditions'</code></li>
<li><code>'variables'</code></li>
<li><code>'includes'</code></li>
<li><code>'targets'</code></li>
<li><code>'targets_default'</code></li>
<li><code>'configurations'</code></li>
<li><code>'default_configuration'</code></li>
</ul>
<p>次のコードはディクショナリを用いた GYP の例です:</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'conditions'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">[</span><span class="s1">'OS == "win"'</span><span class="p">,</span> <span class="p">{</span>
<span class="c1"># windows settings</span>
<span class="p">}],</span>
<span class="p">],</span>
<span class="err"> </span><span class="s1">'variables'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span><span class="s2">"variable_name%"</span><span class="p">:</span> <span class="s2">"variable-value"</span><span class="p">},</span>
<span class="p">],</span>
<span class="s1">'includes'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'common.gypi'</span><span class="p">,</span>
<span class="p">],</span>
<span class="s1">'targets_default'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'default_configuration'</span><span class="p">:</span> <span class="s1">'Release'</span><span class="p">,</span>
<span class="s1">'configurations'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'Debug'</span><span class="p">:</span> <span class="p">{</span>
<span class="p">},</span>
<span class="s1">'Release'</span><span class="p">:</span> <span class="p">{</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">{</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<p>ディクショナリの各セクションについて紹介します。</p>
<h2><a name="conditions"></a>条件分岐 (conditions)</h2>
<p>GYP では条件分岐の制御を行えます。<code>'conditions'</code> セクションに条件分岐を書きます。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'conditions'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">[</span><span class="s1">'OS == "win"'</span><span class="p">,</span> <span class="p">{</span>
<span class="c1"># windows settings</span>
<span class="p">}],</span> <span class="c1"># OS == "win"</span>
<span class="p">[</span><span class="s1">'OS == "mac"'</span><span class="p">,</span> <span class="p">{</span>
<span class="c1"># osx settings</span>
<span class="p">}],</span> <span class="c1"># OS == "mac"</span>
<span class="p">],</span>
<span class="p">}</span>
</pre></div>
<p>条件式には <code>and</code> や <code>or</code> といった Python の論理演算子を使用することができます。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'conditions'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">[</span><span class="s1">'OS == "mac" and target_arch == "x64"'</span><span class="p">,</span> <span class="p">{</span>
<span class="c1"># mac osx (64-bit)</span>
<span class="p">}],</span>
<span class="p">[</span><span class="s1">'OS == "linux" or OS == "android"'</span><span class="p">,</span> <span class="p">{</span>
<span class="c1"># linux or android</span>
<span class="p">}],</span>
<span class="p">],</span>
<span class="p">}</span>
</pre></div>
<h2><a name="variables"></a>変数 (variables)</h2>
<p>GYP には大まかに 3 つの変数があります。1 つ目は GYP によって事前に定義されている<strong>定義済み変数</strong>です。定義済み変数は大文字のスネークケース (例 <code>CAPITAL_LETTERS</code>) の命名規則が適用されています。もっともよく使う定義済み変数に <code>OS</code> が挙げられます。
2 つ目に、<strong>ユーザ定義変数</strong>があります。これは GYP を利用するユーザが定義する変数です。慣例的には、小文字のスネークケース (例 <code>lowercase_letters</code>) の命名規則が使われています。
3 つ目は <strong>自動変数</strong> です。これは、任意のディクショナリ内の、文字列を値に持つキーを参照できる変数です。キーと同じ名前にアンダースコア (<code>_</code>) の接頭辞がついた変数名になります。
それぞれの変数について見ていきましょう。</p>
<h3>定義済み変数</h3>
<p><code>OS</code> のようにすでに定義された変数を GYP の各ジェネレータモジュールが提供しています。
<code>OS</code> は <code>linux</code>, <code>mac</code>, <code>win</code> のように各プラットフォーム OS を示します。もちろん、これらは完全ではありません。
GYP のジェネレータが列挙するオペレーティングシステム名は <code>linux</code>, <code>mac</code>, <code>win</code> などに限ります。
そこで、GYP ユーザは定義済み変数を任意の値で置き換えることができます。
次のように、GYP のコマンドラインで <code>-DOS=os-name</code>オプションを使います:</p>
<div class="highlight"><pre><span></span>gyp example.gyp --depth<span class="o">=</span>. -f gypd -DOS<span class="o">=</span>monaos
</pre></div>
<p><strong>gypd</strong></p>
<p>上記の例で使用している <code>-f gypd</code> オプションについて紹介しておきましょう。
<code>gypd</code> は、GYP のデバッグ出力を行うジェネレータモジュールです。
<code>.gyp</code> ファイルと指定されたオプションを入力として GYP で処理し、その結果を<code>.gypd</code>ファイルに出力します。
<code>.gypd</code> ファイルは <code>.gyp</code> ファイルと同じフォーマットのプレーンテキストです。そのまま GYP の入力として使用することができます。</p>
<blockquote>
<p><strong>Note:</strong>
<code>gypd</code> モジュールを使用した場合、定義済み変数である <code>OS</code> が未定義となります。
そのため、<code>-DOS=monaos</code> のようにユーザが任意の値を指定する必要があります。
<code>-DOS=mac</code> や <code>-DOS=win</code> を指定することも可能です。</p>
</blockquote>
<p><strong>DEPTH</strong></p>
<p>例の中で出てきたもう1つのオプション <code>--depth</code> は <code>DEPTH</code> 値を設定するオプションです。
<code>DEPTH</code> は <code>.gyp</code> ファイルから任意のディレクトリまでの相対パスを示します。
後ほど <code>DEPTH</code> について紹介します。</p>
<h3>ユーザ定義変数</h3>
<p>変数を定義するときは <code>'variables'</code> セクションで行います。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'variables'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'variable_name'</span><span class="p">:</span> <span class="s1">'string-value'</span><span class="p">,</span>
<span class="s1">'number1'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="s1">'number2'</span><span class="p">:</span> <span class="s1">'2'</span><span class="p">,</span>
<span class="s1">'number3'</span><span class="p">:</span> <span class="s1">'3.0'</span><span class="p">,</span>
<span class="s1">'string_list'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'string-element1'</span><span class="p">,</span>
<span class="s1">'string-element2'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<p>変数にデフォルト値を設定する場合は、変数名にパーセント記号 (<code>%</code>) の接尾辞をつけます。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'variables'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'renderer%'</span><span class="p">:</span> <span class="s1">'direct3d'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<p>デフォルト値が設定された変数は、<code>gyp</code> コマンドを実行する際に任意で <code>-D</code> オプションをつけることで、値を上書きすることができます。変数 <code>renderer</code> の値を <code>'opengl'</code> に上書きする場合は、次のように <code>-D renderer=opengl</code> をコマンドライン引数に渡します。</p>
<div class="highlight"><pre><span></span>gyp example.gyp --depth<span class="o">=</span>. -f gypd -D <span class="nv">renderer</span><span class="o">=</span>opengl
</pre></div>
<p><code>-D</code> オプションで指定しなかった場合、<code>renderer</code> にはデフォルト値の <code>direct3d</code> が 割り当てられます。
また、コマンドラインから変数の値を受け取ることもできます。変数の値に <code>'<(identifier)'</code> を指定します。変数名に接尾辞としてパーセント記号 (<code>%</code>) をつけることに注意してください。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'variables'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'renderer%'</span><span class="p">:</span> <span class="s1">'<(renderer)'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">}</span>
</pre></div>
<p>変数を上書きするときと同じく <code>-D</code> オプションで値を指定します。</p>
<div class="highlight"><pre><span></span>gyp example.gyp --depth<span class="o">=</span>. -f gypd -D <span class="nv">renderer</span><span class="o">=</span>direct3d
</pre></div>
<blockquote>
<p><strong>Note:</strong>
コマンドラインからの入力を示す <code>'<(identifier)'</code> を変数の定義に用いた場合、
<code>gyp</code> コマンドを実行する際に必ず <code>-D</code> オプションを付けて変数値を指定する必要があります。</p>
</blockquote>
<p>ここまでの説明をふまえて、レンダリングエンジンのビルドを例にユーザ定義変数の使い方をまとめてみます。
これからビルドするレンダリングエンジンは、グラフィックス API に OpenGL を採用しています。
例外的に Windows プラットフォームのみグラフィックス API として Direct3D も利用できます。
Windows 向けのビルドに限り、デフォルトのレンダラを Direct3D とし、任意でレンダラを OpenGL か Direct3D のどちらか選択できるようにしましょう。
<code>.gyp</code> ファイルは次のようになります。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'variables'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'conditions'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">[</span><span class="s1">'OS != "win"'</span><span class="p">,</span> <span class="p">{</span>
<span class="s1">'renderer'</span><span class="p">:</span> <span class="s1">'opengl'</span><span class="p">,</span>
<span class="p">}],</span>
<span class="p">[</span><span class="s1">'OS == "win"'</span><span class="p">,</span> <span class="p">{</span>
<span class="s1">'renderer%'</span><span class="p">:</span> <span class="s1">'direct3d'</span><span class="p">,</span>
<span class="p">}],</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'my_rendering_engine'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'shared_library'</span><span class="p">,</span>
<span class="s1">'conditions'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">[</span><span class="s1">'renderer == "direct3d"'</span><span class="p">,</span> <span class="p">{</span>
<span class="s1">'sources'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'src/Direct3DRenderer.cpp'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">}],</span>
<span class="p">[</span><span class="s1">'renderer == "opengl"'</span><span class="p">,</span> <span class="p">{</span>
<span class="s1">'sources'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'src/OpenGLRenderer.cpp'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">}],</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="p">],</span> <span class="c1"># targets</span>
<span class="p">}</span>
</pre></div>
<p>この設定ファイルでは次の <code>gyp</code> コマンドとオプションが有効です。</p>
<div class="highlight"><pre><span></span>gyp example.gyp --depth<span class="o">=</span>. -f gypd -DOS<span class="o">=</span>mac
gyp example.gyp --depth<span class="o">=</span>. -f gypd -DOS<span class="o">=</span>linux
gyp example.gyp --depth<span class="o">=</span>. -f gypd -DOS<span class="o">=</span>win
gyp example.gyp --depth<span class="o">=</span>. -f gypd -DOS<span class="o">=</span>win -Drenderer<span class="o">=</span>opengl
gyp example.gyp --depth<span class="o">=</span>. -f gypd -DOS<span class="o">=</span>win -Drenderer<span class="o">=</span>direct3d
</pre></div>
<h3>自動変数</h3>
<p>後ほど出てくる <code>targets</code> セクションを例に紹介します。<code>'type': 'executable'</code> のようにディクショナリの中で、文字列を値に持つキーがあります。
<code>'type'</code> の値を自動変数 <code>_type</code> として参照することが出来ます。キーと同じ名前にアンダースコア (<code>_</code>) の接頭辞を付けることに気をつけてください。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'executable'</span><span class="p">,</span>
<span class="s1">'conditions'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">[</span><span class="s1">'_type == "executable"'</span><span class="p">,</span> <span class="p">{</span>
<span class="c1"># _type == "executable"</span>
<span class="p">}],</span>
<span class="p">[</span><span class="s1">'_type == "static_library"'</span><span class="p">,</span> <span class="p">{</span>
<span class="c1"># _type == "static_library"</span>
<span class="p">}],</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="p">],</span> <span class="c1"># targets</span>
<span class="p">}</span>
</pre></div>
<p>自動変数が有効に働くのは同じスコープ内になります。</p>
<h3>数学定数を設定する</h3>
<p>ここまでで記事の半分まで到達しました。少し長くなりましたので、ちょっとだけ休憩ということで寄り道しましょう。GYP は Python で作られたビルドツールです。変数を定義する際に Python のコードを実行することができます。試しに円周率 <code>pi</code> やネイピア数 <code>e</code> を定義してみましょう:</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'variables'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'pi'</span><span class="p">:</span> <span class="s1">'import math; print math.pi'</span><span class="p">,</span>
<span class="s1">'e'</span><span class="p">:</span> <span class="s1">'import math; print math.e'</span><span class="p">,</span>
<span class="p">},</span>
<span class="s1">'conditions'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">[</span><span class="s1">'pi > e'</span><span class="p">,</span> <span class="p">{</span>
<span class="c1"># OK</span>
<span class="p">}],</span>
<span class="p">],</span>
<span class="p">}</span>
</pre></div>
<p>(こんなこともできるなんてすごいぞ GYP !!)</p>
<h2><a name="includes"></a>共通の設定ファイルを読み込む (includes)</h2>
<p>複数のソフトウェア、モジュール、ライブラリが混在するプロジェクトでは複数の GYP ファイルを扱うことがほとんどです。2 つ以上の GYP ファイルの中で、複数箇所に同じ設定内容を記述することがあります。そのような場合、コピーする手間を省くために <code>includes</code> を使用します。
<code>includes</code> を使うことで、共通の設定をまとめ、必要なビルドーターゲットごとに設定を取り込むことができます。共通の設定を記述した <code>common.gypi</code> を読み込む例です:</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'includes'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'common.gypi'</span><span class="p">]</span>
<span class="p">}</span>
</pre></div>
<p>例えば、<code>common.gypi</code> を次のように記述しておくと、<code>includes</code> で取り込んだすべてのプロジェクト(ビルドーターゲット)に対して、Visual Studio C++ コンパイラの警告レベルを 4 (/W4) に一括して設定することができます。</p>
<div class="highlight"><pre><span></span><span class="c1"># common.gypi</span>
<span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'WarningLevel'</span><span class="p">:</span> <span class="s1">'4'</span><span class="p">,</span> <span class="c1"># /W4</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">},</span> <span class="c1"># target_defaults</span>
<span class="p">}</span>
</pre></div>
<p>こうした <code>includes</code> によって取り込まれる設定ファイルには、慣例的に <code>.gypi</code> (gyp include) 拡張子が使われています。中身は通常の <code>.gyp</code> ファイルと変わりません。また、例で取り上げた「共通の設定を記述した <code>.gypi</code> ファイル」には <code>'common.gypi'</code> の名前がよく使われています。</p>
<h2><a name="targets"></a>ビルドターゲット・ソースファイルの指定 (targets)</h2>
<p>ビルドターゲットを設定します。ビルドの対象となるソースコード、インクルードディレクトリのパス、依存するプロジェクト、出力形式(実行ファイルやスタティックライブラリ、ダイナミックリンクライブラリなど)を指定することが可能です。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'my_flight_game'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'executable'</span><span class="p">,</span>
<span class="s1">'include_dirs'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'../include'</span><span class="p">,</span>
<span class="p">],</span>
<span class="s1">'sources'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'../src/Game.cpp'</span><span class="p">,</span>
<span class="s1">'../src/Entity.cpp'</span><span class="p">,</span>
<span class="s1">'../src/Context.cpp'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="p">],</span> <span class="c1"># targets</span>
<span class="p">}</span>
</pre></div>
<p><strong>'target_name'</strong></p>
<p>ビルドターゲットの名前です。GYP 内でプロジェクトを指定するときに用いることがあります。</p>
<p><strong>'product_name'</strong></p>
<p>ビルドによって出力されるファイルの名前を指定します。これは出力された実行可能形式やライブラリのファイル名に用いられます。
スタティックライブラリ (<code>.a</code>, <code>.lib</code>) や 共有ライブラリ (<code>.dylib</code>, <code>.dll</code>)の場合、<code>lib</code> のプリフィクスが付きます。
例を示します。</p>
<table>
<thead>
<tr>
<th align="left">出力形式</th>
<th align="left">出力ファイル名 (例)</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">executable (Win32)</td>
<td align="left"><code>product-name.exe</code></td>
</tr>
<tr>
<td align="left">executable (Cocoa)</td>
<td align="left"><code>product-name.app</code></td>
</tr>
<tr>
<td align="left">static_libarary(Win32)</td>
<td align="left"><code>libproduct-name.lib</code></td>
</tr>
<tr>
<td align="left">shared_libarary(Win32)</td>
<td align="left"><code>libproduct-name.dll</code></td>
</tr>
<tr>
<td align="left">shared_libarary(Linux)</td>
<td align="left"><code>libproduct-name.so</code></td>
</tr>
<tr>
<td align="left">static_libarary(Linux, OS X)</td>
<td align="left"><code>libproduct-name.a</code></td>
</tr>
<tr>
<td align="left">shared_libarary(OS X)</td>
<td align="left"><code>libproduct-name.dylib</code></td>
</tr>
<tr>
<td align="left">framework (shared_library + mac_bundle)</td>
<td align="left"><code>product-name.framework</code></td>
</tr>
</tbody>
</table>
<p><strong>'type'</strong></p>
<p>ビルドの出力形式です。以下に指定できる形式を列挙します。</p>
<ul>
<li><code>executable</code></li>
<li><code>shared_library</code></li>
<li><code>static_library</code></li>
<li><code>none</code><sup id="fnref:2"><a class="footnote-ref" href="#fn:2" rel="footnote">2</a></sup></li>
</ul>
<blockquote>
<p><strong>Note:</strong>
OS X 用の <code>.framework</code> ファイルや Cocoa アプリケーション (<code>.app</code>) をビルドするためには <code>mac_bundle</code> の値を指定する必要があります。</p>
</blockquote>
<p><strong>'include_dirs'</strong></p>
<p>インクルードディレクトリのパスを指定します。</p>
<p><strong>'sources'</strong></p>
<p>ビルドするソースコードを指定します。通常はビルドに必要なインプリメントファイル(.cpp, .cc など)のみでかまいません。
生成されるプロジェクトファイルにヘッダファイルを含めたい場合は、ヘッダファイルも追加するといいでしょう。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s1">'sources'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'../include/trivial/Game.hpp'</span><span class="p">,</span>
<span class="s1">'../include/trivial/Entity.hpp'</span><span class="p">,</span>
<span class="s1">'../include/trivial/Context.hpp'</span><span class="p">,</span>
<span class="s1">'../src/Game.cpp'</span><span class="p">,</span>
<span class="s1">'../src/Entity.cpp'</span><span class="p">,</span>
<span class="s1">'../src/Context.cpp'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="p">],</span> <span class="c1"># targets</span>
<span class="p">}</span>
</pre></div>
<p><strong>'defines'</strong></p>
<p>プリプロセッサの定義です。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="n">defines</span><span class="s1">': [</span>
<span class="s1">'DEBUG'</span><span class="p">,</span>
<span class="s1">'_WIN32_WINNT=0x0600'</span><span class="p">,</span>
<span class="p">]</span>
<span class="p">}</span>
</pre></div>
<p><strong>'dependencies'</strong></p>
<p>依存しているプロジェクトを指定します。たとえば、ライブラリとアプリケーションの 2 つのビルドターゲットが存在するときに、アプリケーション側でライブラリを必要とするときは次のように 'dependencies' にターゲット名を指定します。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'trivial_library'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'static_library'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'trivial_application'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="n">executable</span><span class="p">,</span>
<span class="s1">'dependencies'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'trivial_library'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="p">],</span> <span class="c1"># targets</span>
<span class="p">}</span>
</pre></div>
<p>他の .gyp ファイルに記述されたビルドターゲットを指定することもできます:</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'dependencies'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'trivial.gyp:*'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">}</span>
</pre></div>
<p>明示的に任意のビルドターゲットのみを指定する場合は次のように記述します:</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'dependencies'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'trivial.gyp:trivial_library'</span><span class="p">,</span>
<span class="p">],</span>
<span class="p">}</span>
</pre></div>
<h3>複数ターゲットの指定</h3>
<p>Windows デスクトップ向けに開発している人にわかりやすく説明すると、ビルドターゲット 1 つにつき、Visual Studio のプロジェクトファイル(.vcxproj) が対応付けられます。また 'targets' とあるように複数のターゲットを列挙できます。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'trivial_library'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'static_library'</span><span class="p">,</span>
<span class="c1"># ...</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'trivial_engine'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'shared_library'</span><span class="p">,</span>
<span class="s1">'dependencies'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'trivial_library'</span><span class="p">]</span>
<span class="c1"># ...</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'trivial_application'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'executable'</span><span class="p">,</span>
<span class="s1">'dependencies'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'trivial_engine'</span><span class="p">]</span>
<span class="c1"># ...</span>
<span class="p">},</span>
<span class="p">],</span> <span class="c1"># targets</span>
<span class="p">}</span>
</pre></div>
<p>これは、1 つのアプリケーションで複数のプロジェクトファイルを使用する場合を想定しています。例えば、内製フレームワークのスタティックライブラリや、エンジンやプラグインの機能を提供するダイナミックリンクライブラリ (DLL) など、いくつものビルドターゲットが存在する場合に有用です。</p>
<h2><a name="target-defaults"></a>ビルドターゲット間で共通の設定を記述する (target_defaults)</h2>
<p>次のように <code>'targets'</code> 内で重複した記述がある場合を考えてみましょう。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'trivial_library'</span><span class="p">,</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_VERSION'</span><span class="p">:</span> <span class="s1">'com.apple.compilers.llvm.clang.1_0'</span><span class="p">,</span>
<span class="s1">'CLANG_CXX_LANGUAGE_STANDARD'</span><span class="p">:</span> <span class="s1">'c++0x'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'trivial_engine'</span><span class="p">,</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_VERSION'</span><span class="p">:</span> <span class="s1">'com.apple.compilers.llvm.clang.1_0'</span><span class="p">,</span>
<span class="s1">'CLANG_CXX_LANGUAGE_STANDARD'</span><span class="p">:</span> <span class="s1">'c++0x'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'trivial_application'</span><span class="p">,</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_VERSION'</span><span class="p">:</span> <span class="s1">'com.apple.compilers.llvm.clang.1_0'</span><span class="p">,</span>
<span class="s1">'CLANG_CXX_LANGUAGE_STANDARD'</span><span class="p">:</span> <span class="s1">'c++0x'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">},</span> <span class="c1"># targets</span>
<span class="p">}</span>
</pre></div>
<p>'targets' で列挙しているターゲット間において、共通の設定を行っている場合は 'target_defaults' を使うと便利です。</p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_VERSION'</span><span class="p">:</span> <span class="s1">'com.apple.compilers.llvm.clang.1_0'</span><span class="p">,</span>
<span class="s1">'CLANG_CXX_LANGUAGE_STANDARD'</span><span class="p">:</span> <span class="s1">'c++0x'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span> <span class="c1"># target_defaults</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">{</span>
<span class="p">{</span><span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'trivial_library'</span><span class="p">},</span>
<span class="p">{</span><span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'trivial_engine'</span><span class="p">},</span>
<span class="p">{</span><span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'trivial_application'</span><span class="p">},</span>
<span class="p">],</span> <span class="c1"># targets</span>
<span class="p">}</span>
</pre></div>
<h2><a name="depth"></a>DEPTH</h2>
<p><code>DEPTH</code> は <code>.gyp</code> ファイルから任意のディレクトリまでの相対パスを示します。
<code>.gyp</code> ファイルを処理する時に、この <code>DEPTH</code> の値を使って依存するファイルのパスを求めます。
例を示します:</p>
<div class="highlight"><pre><span></span>project-root/
|
|-- build/
| |-- common.gypi
| |-- MyFramework.gyp
|
|-- test
|-- TestApp/
| |-- TestApp.gyp
|
|-- Sample/
|-- UnitTest/
</pre></div>
<div class="highlight"><pre><span></span><span class="c1"># build/MyFramework.gyp</span>
<span class="p">{</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'MyFrameworkRuntime'</span><span class="p">,</span> <span class="c1"># (1)</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'static_library'</span><span class="p">,</span>
<span class="c1"># ...</span>
<span class="p">}</span>
<span class="p">],</span>
<span class="p">}</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="c1"># test/TestApp/TestApp.gyp</span>
<span class="p">{</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'TestApp'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'executable'</span><span class="p">,</span>
<span class="s1">'dependencies'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'<(DEPTH)/MyFramework.gyp:MyFrameworkRuntime'</span><span class="p">,</span> <span class="c1"># (2)</span>
<span class="p">],</span>
<span class="c1"># ...</span>
<span class="p">}</span>
<span class="p">],</span>
<span class="p">}</span>
</pre></div>
<p><code>.gyp</code> ファイルの中で<code>DEPTH</code> の値を取得するには <code><(DEPTH)</code> と記述します。
上記の例では (1) のビルドターゲットを (2) のようにして依存するターゲットとして指定しています。</p>
<p><code>DEPTH</code> は <code>--depth</code>オプションを使ってコマンドラインから設定することができます。
例えば、project-root ディレクトリから次のコマンドを実行することができます:</p>
<div class="highlight"><pre><span></span><span class="nb">cd</span> project-root
gyp test/TestApp/TestApp.gyp --depth<span class="o">=</span>./build -f gypd -DOS<span class="o">=</span>monaos
</pre></div>
<h1>最後に</h1>
<p>今回は入力フォーマットと基本的な使い方に焦点をあて紹介しました。
すべての機能を紹介することはできませんでしたが、GYP にはまだまだ頻繁に使用する機能があります。
例えば、Visual Studio 特有のオプションや Xcode 向けの設定といった各開発プラットフォーム向けの設定を記述できる機能が提供されています。
以下に該当するキーワードを挙げます。</p>
<ul>
<li><code>'msvs_settings'</code></li>
<li><code>'xcode_settings'</code></li>
<li><code>'make_global_settings'</code></li>
</ul>
<p>GYP は Chromium プロジェクトのために開発されたツールとはいえ、多くのケース、多くのプロジェクトに対応できる機能を備えています。
紹介できなかった「ビルド設定の継承」や「<code>'type': none</code>の使用どころ」、「OS X 向けアプリケーションのバンドル (<code>mac_bundle</code>) 」などその他の機能については、次回あらためて紹介します。お楽しみに!!</p>
<p><strong>追記</strong><br />
GYP の具体的な使い方に関して、次の記事を書きました。ご参考になりましたら幸いです。</p>
<ul>
<li><a href="/2014/10/gyp-build-settings-in-msvs2014.html">ビルドツール GYP で Visual Studio プロジェクトのあれこれを設定しよう</a></li>
<li><a href="/2014/12/msbuild-settings-with-gyp.html">GYP における MSBuild Settings (msbuild_settings) の使い方</a></li>
<li><a href="/2015/09/gyp-generate-xcode-project.html">GYP を使って Xcode プロジェクトを作ってみよう</a></li>
</ul>
<h1>参考文献</h1>
<ul>
<li><a href="https://code.google.com/p/gyp/wiki/GypLanguageSpecification">https://code.google.com/p/gyp/wiki/GypLanguageSpecification</a></li>
<li><a href="https://code.google.com/p/gyp/wiki/GypUserDocumentation">https://code.google.com/p/gyp/wiki/GypUserDocumentation</a></li>
</ul>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p><em><a href="https://code.google.com/p/gyp/wiki/GypLanguageSpecification#Overview">https://code.google.com/p/gyp/wiki/GypLanguageSpecification#Overview</a></em> に "The .gyp file syntax is a Python data structure." とある。 <a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:2">
<p><code>none</code> については日を改めて紹介します。 <a class="footnote-backref" href="#fnref:2" rev="footnote" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
</ol>
</div>今週の進み具合 #52014-01-20T07:19:00+09:00mogemimitag:enginetrouble.net,2014-01-20:2014/01/gamedev-weekly-2014-0120.html<p>1週間の進み具合を振り返ってみます。</p>
<h2>進み具合 (2014年1月11日 - 1月19日)</h2>
<p>ごめんなさい。
テクスチャ周りを実装する予定でしたが、1週間まるまる開発をサボってしまいました(汗)</p>
<p>さすがに何も進めていなかったのが後ろめたくなり、週末に <code>README.md</code> ファイルだけ更新しました。
OS X のみですが、テストアプリケーションをビルドできるよう手順を書きました。
よければ GYP を使ってビルドしてみてください。</p>
<h2>今週の予定</h2>
<p>先週に引き続き、テクスチャの実装を行います。</p>
<p>(はたして来週 "今週の進み具合 #6" があるのでしょうか…。)</p>今週の進み具合 #42014-01-12T01:11:00+09:00mogemimitag:enginetrouble.net,2014-01-12:2014/01/gamedev-weekly-2014-0112.html<p>あけましておめでとうございます。2014年もよろしくお願いします〜。
年末からお正月にかけて実装したことを振り返っていきます。</p>
<h2>進み具合 (2013年12月28日 - 2014年1月10日)</h2>
<p>エフェクトパラメータを実装しました。実装では、シェーダリフレクションでリンク済みのシェーダプログラムオブジェクトから Uniform Block を取得して、必要な Uniform Buffer オブジェクトを作成しました。これらを実装するにあたって、エフェクトリフレクションも追加しました。</p>
<h2>Uniform Block</h2>
<p>OpenGL の Uniform Buffer は Direct3D 11 における定数バッファ(Constant Buffer)に相当します。
今回は旧来の Interface Block を使用していない Uniform 変数には対応せず、Uniform Block のみサポートすることにしました。
旧来の GLSL Uniform 変数は次のような記述です:</p>
<div class="highlight"><pre><span></span><span class="c1">// Not supported</span>
<span class="k">vec4</span> <span class="n">Diffuse</span><span class="p">;</span>
<span class="k">vec4</span> <span class="n">Specular</span><span class="p">;</span>
<span class="k">vec4</span> <span class="n">Ambient</span><span class="p">;</span>
</pre></div>
<p>今回対応したのは次の Uniform Block を使用した記述です:</p>
<div class="highlight"><pre><span></span><span class="c1">// OK</span>
<span class="k">uniform</span> <span class="n">Material</span> <span class="p">{</span>
<span class="k">vec4</span> <span class="n">Diffuse</span><span class="p">,</span>
<span class="k">vec4</span> <span class="n">Specular</span><span class="p">,</span>
<span class="k">vec4</span> <span class="n">Ambient</span><span class="p">,</span>
<span class="p">};</span>
</pre></div>
<p>GLSL 上では Uniform 変数をブロック(<a href="http://www.opengl.org/wiki/GLSL_Interface_Block">Interface Block</a>)で囲むだけなので、古い GLSL コードから移行が簡単です。</p>
<p>また Uniform Block をサポートしたことで次のような POD な値を直接エフェクトパラメータ(定数バッファ)にコピーすることが可能になりました: </p>
<div class="highlight"><pre><span></span><span class="k">struct</span> <span class="n">Material</span>
<span class="p">{</span>
<span class="n">Color</span> <span class="n">Diffuse</span><span class="p">,</span>
<span class="n">Color</span> <span class="n">Specular</span><span class="p">,</span>
<span class="n">Color</span> <span class="n">Ambient</span><span class="p">,</span>
<span class="p">};</span>
<span class="n">Material</span> <span class="n">material</span> <span class="p">{</span> <span class="n">Color</span><span class="o">::</span><span class="n">White</span><span class="p">,</span> <span class="n">Color</span><span class="o">::</span><span class="n">White</span><span class="p">,</span> <span class="n">Color</span><span class="o">::</span><span class="n">White</span> <span class="p">};</span>
<span class="k">auto</span> <span class="n">parameter</span> <span class="o">=</span> <span class="n">effectPass</span><span class="o">-></span><span class="n">Parameters</span><span class="p">(</span><span class="s">"Material"</span><span class="p">);</span>
<span class="n">parameter</span><span class="o">-></span><span class="n">SetValue</span><span class="p">(</span><span class="n">material</span><span class="p">);</span>
</pre></div>
<p>Uniform Block を使用したことで、旧来の <code>glUniform</code> を使った場合より、バッファへのコピー回数が減ることが期待できます。</p>
<p>現世代(Direct3D 11 世代)のグラフィックス API と対応がとれて実装しやすかったです。Uniform Buffer オブジェクト(<code>GL_UNIFORM_BUFFER</code>)の扱い方自体は、OpenGL における Vertex Buffer(<code>GL_ARRAY_BUFFER</code>) や Index Buffer(<code>GL_ARRAY_ELEMENT_BUFFER</code>) と変わりなく、扱いやすかったです。</p>
<h2>今週の実装予定</h2>
<p>エフェクト周りが一段落したので、グラフィックスで残っているのは、テクスチャとレンダーターゲットになりました。今週はテクスチャを触っていきます。
エフェクトもそうですが、テクスチャは特にアセットと深い関係にあるので、アセットパイプラインについてもぼちぼち考えていきます。</p>
<p>昨年、OpenGL を触っていたときにマルチテクスチャ・マルチレンダーターゲット周りで痛い目にあったのでバグを混入しないよう気をつけます(汗)</p>
<p>(次回 "今週の進み具合 #5" はあるのでしょうか…。)</p>今週の進み具合 #32013-12-28T06:22:00+09:00mogemimitag:enginetrouble.net,2013-12-28:2013/12/gamedev-weekly-2013-1228.html<p>更新が遅くなりました(汗)<br />
今週実装したことを振り返っていきます。</p>
<h2>進み具合 (2013年12月15日 - 12月27日)</h2>
<p>GLSL シェーダのコンパイルとリンク、シェーダプログラムの適用、エフェクトパスの生成を実装しました。
また、エフェクトパスを使用するにあたって、入力レイアウトも作成しました。
実際に四角形を描画してみました。</p>
<p><img alt="Drawing rectangle" src="https://enginetrouble.net/images/2013/12/screenshot_2013_1228.png" /></p>
<p>どことなく哀愁を感じます(汗)</p>
<h2>入力レイアウト</h2>
<p>今回、入力レイアウトを作成するにあたって、OpenGL を使った実装では Vertex Array Object (VAO) を使用しました。入力レイアウトの概念をクライアント(フレームワークを使用するユーザ)に API として公開するのは悩んだところです。入力レイアウトを導入した理由は、よりパフォーマンスが得られ、現世代のグラフィックス API である Direct3D 11 と OpenGL 4 を使った実装と非常に相性がいいことが挙げられます。</p>
<p>現世代のゲームにおけるレンダリングでは、ハードウェアインスタンシング(またはジオメトリインスタンシング)など、複数の頂点バッファを同時に扱うケースが考えられます。これに対応するため 2 つの方法を考えました: </p>
<ul>
<li>(A) ドローコール毎に、現在バインドされている 1 から N 個の頂点バッファの頂点要素の情報を参照し、エンジン側で入力レイアウトを生成、バインドする。</li>
<li>(B) あらかじめ入力レイアウトを作成しておき、クライアント側でドローコール前に入力レイアウトをバインドする。</li>
</ul>
<p>(A) の方法では、クライアントに入力レイアウトオブジェクトを公開する必要はありません。エンジンの内部では、クライアントによってグラフィックスコンテキストに設定された複数個の頂点バッファを参照して入力レイアウトを生成します。フレーム毎に、(ID3D11InputLayout や Vertex Buffer Array といった)入力レイアウトオブジェクトを生成すれば、すぐさまグラフィックスメモリが不足します。そのため、あらかじめ CRC32 などを使って頂点要素情報からハッシュ値を計算し、これを頂点要素情報の識別子として頂点バッファごとに持たせておきます。グラフィックスコンテキストの内部で、識別子から入力レイアウトを検索し、存在しなければ入力レイアウトを生成する、というような実装になります。以下に疑似コードを載せます。</p>
<div class="highlight"><pre><span></span><span class="k">struct</span> <span class="n">VertexDeclaration</span>
<span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">VertexElement</span><span class="o">></span> <span class="n">elements</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">strideBytes</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="kt">uint32_t</span> <span class="n">hashKey</span><span class="p">;</span>
<span class="p">};</span>
<span class="k">struct</span> <span class="n">VertexBuffer</span>
<span class="p">{</span>
<span class="n">GLuint</span> <span class="n">vertexBuffer</span><span class="p">;</span> <span class="c1">// or ID3D11Buffer*</span>
<span class="n">VertexDeclaration</span> <span class="n">vertexDeclaration</span><span class="p">;</span>
<span class="p">};</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="n">std</span><span class="o">::</span><span class="n">map</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="kt">uint32_t</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">unique_ptr</span><span class="o"><</span><span class="n">InputLayout</span><span class="o">>></span> <span class="n">inputLayouts</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="n">VertexBuffer</span><span class="o">>></span> <span class="n">vertexBuffers</span><span class="p">;</span>
<span class="kt">void</span> <span class="n">GraphicsContext</span><span class="o">::</span><span class="n">Draw</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="kt">uint32_t</span> <span class="n">hashKey</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="nl">vertexBuffer</span><span class="p">:</span> <span class="n">vertexBuffers</span><span class="p">)</span> <span class="p">{</span>
<span class="n">hashKey</span> <span class="o">+=</span> <span class="n">vertexBuffer</span><span class="o">-></span><span class="n">vertexDeclaration</span><span class="p">.</span><span class="n">hashKey</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">auto</span> <span class="n">iter</span> <span class="o">=</span> <span class="n">inputLayouts</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">hashKey</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">iter</span> <span class="o">==</span> <span class="n">std</span><span class="o">::</span><span class="n">end</span><span class="p">(</span><span class="n">inputLayouts</span><span class="p">))</span> <span class="p">{</span>
<span class="n">inputLayouts</span><span class="p">[</span><span class="n">hashKey</span><span class="p">]</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">make_unique</span><span class="o"><</span><span class="n">InputLayout</span><span class="o">></span><span class="p">();</span>
<span class="p">}</span>
<span class="n">inputLayouts</span><span class="p">[</span><span class="n">hashKey</span><span class="p">]</span><span class="o">-></span><span class="n">Apply</span><span class="p">();</span>
<span class="c1">// draw call</span>
<span class="p">...</span>
<span class="p">}</span>
</pre></div>
<p>この方法では、あらかじめハッシュ値を生成し、識別子として頂点バッファごとに持つ必要があります。また、フレーム毎に入力レイアウトをマップから検索する必要もあります。以前はこの方法を採用していました。</p>
<p>今回は (B) を採用しました。Direct3D 11, OpenGL 4 以上をターゲットにしていることが大きな理由です。
対して (B) の方法では、クライアントに入力レイアウトを公開することになります。クライアント側で入力レイアウトオブジェクトを生成し、頂点バッファやシェーダプログラムと同じようにオブジェクトを描画毎に適用します。作成済みの入力レイアウトがグラフィックスコンテキストに設定されるため、内部で入力レイアウトのマップを持つ必要がなくなり、ハッシュ値を計算し保持しておく必要もありません。また入力レイアウトを検索する必要もありません。</p>
<div class="highlight"><pre><span></span><span class="k">struct</span> <span class="n">VertexDeclaration</span>
<span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">VertexElement</span><span class="o">></span> <span class="n">elements</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">strideBytes</span><span class="p">;</span>
<span class="p">};</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="n">InputLayout</span><span class="o">></span> <span class="n">inputLayout</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="n">VertexBuffer</span><span class="o">>></span> <span class="n">vertexBuffers</span><span class="p">;</span>
<span class="kt">void</span> <span class="n">GraphicsContext</span><span class="o">::</span><span class="n">SetInputLayout</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="n">InputLayout</span><span class="o">></span> <span class="k">const</span><span class="o">&</span> <span class="n">inputLayoutIn</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">inputLayout</span> <span class="o">=</span> <span class="n">inputLayoutIn</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">GraphicsContext</span><span class="o">::</span><span class="n">Draw</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">inputLayout</span><span class="o">-></span><span class="n">Apply</span><span class="p">();</span>
<span class="c1">// draw call</span>
<span class="p">...</span>
<span class="p">}</span>
</pre></div>
<p>(B) で気をつけることは、ユーザが適切な入力レイアウトオブジェクトを作成できるようにすることです。今回の実装ではシェーダリフレクションを使って、既定値の入力レイアウトを自動生成できる API を用意しました。</p>
<div class="highlight"><pre><span></span><span class="kt">void</span> <span class="n">MyGame</span><span class="o">::</span><span class="n">Intialize</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">effectPass</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">make_shared</span><span class="o"><</span><span class="n">EffectPass</span><span class="o">></span><span class="p">(</span><span class="n">graphicsDevice</span><span class="p">,</span> <span class="p">...);</span>
<span class="n">inputLayout</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">make_shared</span><span class="o"><</span><span class="n">InputLayout</span><span class="o">></span><span class="p">(</span><span class="n">graphicsDevice</span><span class="p">,</span> <span class="n">effectPass</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">MyGame</span><span class="o">::</span><span class="n">Draw</span><span class="p">()</span>
<span class="p">{</span>
<span class="p">...</span>
<span class="n">graphicsContext</span><span class="o">-></span><span class="n">SetInputLayout</span><span class="p">(</span><span class="n">inputLayout</span><span class="p">);</span>
<span class="err"> </span><span class="n">graphicsContext</span><span class="o">-></span><span class="n">SetVertexBuffer</span><span class="p">(</span><span class="n">vertexBuffer</span><span class="p">);</span>
<span class="err"> </span><span class="n">effectPass</span><span class="o">-></span><span class="n">Apply</span><span class="p">();</span>
<span class="p">...</span>
<span class="p">}</span>
</pre></div>
<h2>今年中の実装予定</h2>
<p>今年中にエフェクトパラメータを実装します。Direct3D だと定数バッファ(Constant Buffer)、OpenGL だと Uniform Buffer と呼ばれている箇所です。エフェクトパラメータが片付いたら、レンダーターゲットとテクスチャを実装する予定です。またシェーダとテクスチャに関してはアセットが絡んでくるので、ランタイムエンジンと切り分けて、少しずつアセットパイプラインを考える必要がありそうです。</p>
<p>(次回 "今週の進み具合 #4" はあるのでしょうか…。)</p>
<h2>感謝</h2>
<p>エフェクトの実装を終えてすぐにテスト描画してみたところ、はじめ四角形も何も表示されていませんでした。途方に暮れていたところ、pull request をいただきました。見事解決し、無事に四角形を描画することができました。pull request を送っていただいた <a href="https://github.com/bis83">@bis83</a> さん、ありがとうございます!</p>今週の進み具合 #22013-12-15T22:45:00+09:00mogemimitag:enginetrouble.net,2013-12-15:2013/12/gamedev-weekly-2013-1215.html<h2>進み具合 (2013年12月8日 - 12月14日)</h2>
<p>レンダーステートの実装を行いました。ラスタライザ、ブレンド、深度ステンシル、サンプラとそれぞれのステージに対応するステートの API の作成と、実際に Cocoa + OpenGL 上で動くように実装していきました。</p>
<h2>Cocoa 向けのビルドターゲットを Mavericks に引き上げ</h2>
<p>開発に使っている OS X のバージョンを 10.8 (Mountain Lion) から 10.9 (Mavericks) にアップグレードしました 。OS X 10.8 では OpenGL 3.2 まで対応していましたが、Mavericks からようやく OpenGL 4.1 まで対応しました。なお、今回のアップグレードで1世代前のドライバはおおむね GL 3.3 にとどまっているようです。<sup id="fnref:1"><a class="footnote-ref" href="#fn:1" rel="footnote">1</a></sup></p>
<p>趣味で作っているゲームは、OS X 10.8 以降をターゲットに開発を始めました。しかし 10.8 が OpenGL 3.2 までしか対応していなかっため、ビルドのターゲットバージョンを 10.9 以降に引き上げることにしました。サンプラステートを実装する上で、OpenGL 3.3 から追加されたサンプラオブジェクトを使用しているからです。
なにより、Mavericks へのアップグレードが無償で行えることが大きく作用しました。</p>
<p>これで Windows と同じく OpenGL 3.3 以降(できれば 4.x)を使ったゲーム開発が楽しめます。</p>
<h3>glGetString のログ</h3>
<p>せっかくなので Mountain Lion から Mavericks へのアップグレード前後での <a href="http://www.opengl.org/sdk/docs/man/xhtml/glGetString.xml">glGetString</a> のログを貼っておきます。</p>
<div class="highlight"><pre><span></span><span class="k">auto</span> <span class="n">version</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o"><</span><span class="kt">char</span> <span class="k">const</span><span class="o">*></span><span class="p">(</span><span class="n">glGetString</span><span class="p">(</span><span class="n">GL_VERSION</span><span class="p">));</span>
<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o"><<</span> <span class="s">"OpenGL Version: "</span> <span class="o"><<</span> <span class="n">version</span> <span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
</pre></div>
<p>それぞれ Intel HD Graphics 4000 を使用しています。
まず、OS X 10.8 で何も指定しなかった場合:</p>
<div class="highlight"><pre><span></span>// Mountain Lion
OpenGL Version: 2.1 INTEL-8.16.74
</pre></div>
<p>次に OS X 10.8 で <code>NSOpenGLPixelFormat</code> を作成する際 <code>NSOpenGLPFAOpenGLProfile</code>, <code>NSOpenGLProfileVersion3_2Core</code> を指定した場合:</p>
<div class="highlight"><pre><span></span>// Mountain Lion + NSOpenGLProfileVersion3_2Core
OpenGL Version: 3.2 INTEL-8.16.74
</pre></div>
<p>次に OS X 10.9 で <code>NSOpenGLPFAOpenGLProfile</code>, <code>NSOpenGLProfileVersion3_2Core</code> を指定した場合:</p>
<div class="highlight"><pre><span></span>// Mavericks + NSOpenGLProfileVersion3_2Core
OpenGL Version: 4.1 INTEL-8.18.26
</pre></div>
<h2>今週の実装予定</h2>
<p>今週は、エフェクトパスの作成とシェーダのコンパイルとリンクまで実装します。早く終えたら、エフェクトパラメータの自動生成まで触りたいです。</p>
<p>(来週はたして "今週の進み具合 #3" はあるのでしょうか…。)</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>OS X 10.9 の <a href="https://developer.apple.com/graphicsimaging/opengl/capabilities/index.html">OpenGL Capabilities Tables</a> によると、Radeon HD 2400 や Intel HD Graphics 3000 は OpenGL version 3.3 にとどまっています。 <a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
</ol>
</div>今週の進み具合 #12013-12-08T03:39:00+09:00mogemimitag:enginetrouble.net,2013-12-08:2013/12/gamedev-weekly-2013-1208.html<p>趣味で作っているゲームエンジンのリポジトリを <a href="https://github.com/mogemimi/pomdog">GitHub</a> で公開しました〜。
Git のリリースタグは切っていないので、当面の目標は、必要最小限の機能を追加してリリースすることです。
さくっと作っていきます!!</p>
<h2>進み具合 (2013年12月1日 - 12月7日)</h2>
<p>いま Cocoa + OpenGL で実装しています。やっとビューを指定した色でクリアすることができたところです…(汗)</p>
<p><img alt="Cocoa + OpenGL" src="https://enginetrouble.net/images/2013/12/screenshot_2013_1208.png" /></p>
<p>Cocoa と OpenGL が落ち着いたら、Windows Desktop もしくは Metro をターゲットプラットフォームにして実装したいです。</p>
<p>目標は、今年中に Cocoa + OpenGL 向けのレンダリングシステムを実装し終えることです。Cocoa でのサウンドと入力は後回しにして、先に Direct3D 11 を触りたいです。</p>
<p>今週は、グラフィックスリソースの生成を行うグラフィックスデバイスと、各レンダリングステートを実装します。</p>
<p>(はたして "今週の進み具合 #2" はあるのでしょうか…!)</p>ブログに Pelican を使うことにしました。2013-10-21T02:14:00+09:00mogemimitag:enginetrouble.net,2013-10-21:2013/10/moving-my-blog-to-pelican.html<p>ブログのページ生成に <a href="http://docs.getpelican.com/">Pelican</a> を使うことにしました。
Pelican は Python で作られた静的サイトジェネレータです。
<a href="http://docutils.sourceforge.net/rst.html">reStructuredText</a>
または Markdown で書かれた記事から html のページを自動生成してくれます。
これまで記事を Markdown で書いていたので、Pelican への移行はスムーズでした。</p>
<h2>静的サイト生成</h2>
<p>生成されるのは html のページです。<sup id="fnref:1"><a class="footnote-ref" href="#fn:1" rel="footnote">1</a></sup>
ページの生成はローカル環境で行います。
生成されたページをアップロードすれば、すぐにでもウェブサイトとして公開することができます。
MySQL のようなデータベースや php の実行環境をサーバ側で用意する必要はありません。
そのため Pelican では Dropbox へのアップロード(パブリッシュ)もサポートしています。</p>
<p>Pelican が行うのは静的なページ生成のみです。
動的なコメントフォームやアクセス解析などは他のサービスを使って実現します。
今回、コメントフォームに Disqus を使うことにしました。
Disqus の導入も Pelican でサポートされています。</p>
<h2>Pelican は簡単?</h2>
<p>Pelican では Python のコーディングをすることはほとんどありません。
Pelican を使ってブログを作るために、easy-install, pip, virtualenv, それと Fabric について知る必要がありました。
(僕は Python 製の GYP を触っていたくらいで、Python についてほとんど詳しくありません。)</p>
<p>今回 Pelican 用のテーマも見よう見まねで作ってみました。
テーマ作成で扱うのは html と css のみです。コツをつかんでからはさくさく進みました。</p>
<p>Pelican は Python によるプラグインもサポートしています。
sitemap の生成にプラグインを使いました。<sup id="fnref:2"><a class="footnote-ref" href="#fn:2" rel="footnote">2</a></sup></p>
<p>導入からテーマ作成、移行までちょうどまるまる 1 週間 Pelican を触っていました。
ブログ以外にもソフトウェアのオンラインドキュメントを書くのに重宝しそうです。</p>
<p>今秋は Pelican でページを作ってみてはいかがでしょうか。</p>
<h2>参考文献</h2>
<ul>
<li><a href="http://docs.getpelican.com/">Pelican の公式ドキュメント</a></li>
<li><a href="https://github.com/getpelican/pelican">Pelican の GitHub リポジトリ</a></li>
</ul>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>PDF_GENERATOR オプションで PDF ページも作成できるみたいです。 <a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:2">
<p><a href="https://github.com/getpelican/pelican-plugins">Pelican プラグインのリポジトリ</a>。sitemap を使用しました。 <a class="footnote-backref" href="#fnref:2" rev="footnote" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
</ol>
</div>OpenGL 3.3 と過ごした夏休み20132013-09-16T15:57:00+09:00mogemimitag:enginetrouble.net,2013-09-16:2013/09/summer-vacation-2013.html<p>日記サボってしまってごめんなさい!更新間隔をあけないようにします…(汗)</p>
<p>今年も残るところ3ヶ月ちょっとで終わります。このところ外も涼しくなってきました。
9月が終わっちゃう前に、夏の間にやってみたことを振り返ってみます。</p>
<h2>OpenGL 3.3</h2>
<p>今夏を一言で表すと残念なこと(?)に "OpenGL 3.3" で片づけられます。休みの日は 1 日中触っていました。
もともと趣味で作っていたゲームエンジンでは、グラフィックス API に Direct3D 11 を使用していました。
そんな中、ここ 1 年ほどモバイル向けの OpenGL ES を触る機会があったことと、OSX でゲームを動かしてみたいことも相まって OpenGL に手を出しました。</p>
<p>OpenGL のバージョンが 3.3 なのには理由があります。1 つは OpenGL 3.3 から導入された Sampler Object が利用できることです。Sampler Object が導入されたことで、テクスチャとサンプラーの分離ができます。Direct3D 11 ではテクスチャとサンプラーがそれぞれ独立していたので、それに合わせて Sampler Object を利用する必要がありました。
もう 1 つの理由は、OpenGL 3 からハードウェアインスタンシングがサポートされていることです。こちらも元々エンジンが Direct3D 11 のハードウェアインスタンシングをがんがん利用していたため、OpenGL でも実装する必要がありました。ちなみに OpenGL だと Geometry instancing と呼ぶ場合が多いようです。</p>
<h2>ハードウェアインスタンシング(Hardware instancing)</h2>
<p>パーティクルにしても、スプライトバッチにしてもインスタンシングは必要不可欠です。今回はキューブブロックを並べてます。</p>
<h3>ハードウェアインスタンシング</h3>
<p><img alt="Hardware instancing" src="https://enginetrouble.net/images/2013/09/screenshot_20130916_hardwareinstancing.jpg" /></p>
<p>ブロックをたくさん出してもフレームレート 60 fps をキープしています。
せっかくなのでワイヤーフレーム表示してみました。</p>
<p><img alt="Hardware instancing(wireframe)" src="https://enginetrouble.net/images/2013/09/screenshot_20130916_hardwareinstancing_wireframe.jpg" /></p>
<p>もう何がなんだかわかりません(汗)</p>
<h2>遅延シェーディング</h2>
<h3>Albedo</h3>
<p><img alt="Deferred shading(Albedo)" src="https://enginetrouble.net/images/2013/09/screenshot_20130916_deferred_albedo.jpg" /></p>
<h3>World-space normal</h3>
<p><img alt="Deferred shading(World-space normal)" src="https://enginetrouble.net/images/2013/09/screenshot_20130916_deferred_worldspacenormal.jpg" /></p>
<h3>Depth</h3>
<p><img alt="Deferred shading(depth)" src="https://enginetrouble.net/images/2013/09/screenshot_20130916_deferred_depth.jpg" /></p>
<p>G-buffer (Geometry buffer) にそのままアルベドとワールド空間の法線と深度情報をそれぞれ書き出しています。特に工夫した点はなく、ピュアな遅延シェーディングです。G-buffer の内訳は次の通りです:</p>
<table>
<thead>
<tr>
<th></th>
<th>Format</th>
<th align="left">RGB</th>
<th align="left">A</th>
</tr>
</thead>
<tbody>
<tr>
<td>RT0</td>
<td>R8G8B8A8(Unorm)</td>
<td align="left">Diffuse Albedo RGB</td>
<td align="left">None</td>
</tr>
<tr>
<td>RT1</td>
<td>R10G10B10A2</td>
<td align="left">World-space normal XYZ</td>
<td align="left">None</td>
</tr>
<tr>
<td>RT2</td>
<td>R32(Float)</td>
<td align="left">Depth</td>
<td align="left"></td>
</tr>
</tbody>
</table>
<p><del>K●LLZONE 2 です。</del>
Specular intensity を RenderTarget 0 (RT0) で未使用だった Alpha に格納するのもよさそうです。(今回はスペキュラーマップを用意していませんでした。)</p>
<p>ライティングした結果が次です。</p>
<p><img alt="Half-lambert + Phong shading" src="https://enginetrouble.net/images/2013/09/screenshot_20130916_phong.jpg" /></p>
<p>ハーフランバート(Half-lambert) をベースにしたフォンシェーディングを行っています。このスクリーンショットだと鏡面反射がいまいちわかりにくいですね…(汗)。</p>
<h1>バンプマッピング</h1>
<p>バンプマッピングもしてみました。こちらはバンプマッピング後の G-buffer のノーマルマップの様子です。</p>
<p><img alt="Bump mapping(World space normal)" src="https://enginetrouble.net/images/2013/09/screenshot_20130916_bumpmapping_normal.jpg" /></p>
<p>アルベドなしのフォンシェーディングです。鏡面反射光でギラギラしてます。</p>
<p><img alt="Bump mapping + Phong shading" src="https://enginetrouble.net/images/2013/09/screenshot_20130916_bumpmapping_phong.jpg" /></p>
<h1>被写界深度ブラー(Depth of field)</h1>
<p>先ほどの最終イメージには、被写界深度ブラーをかけています。深度情報を使ってブラーをかけただけですが、スクリーンショットの見栄えがぐーんとよくなります。ゲームプレイ時は邪魔になりそうですが、スクリーンショット時はピンポンブラーにして強めにかけてもいいかもしれません。</p>
<h1>エッジ検出(Edge detection)</h1>
<p>ピクセルシェーダで G-buffer の法線・深度情報を使ってエッジ検出もしました。</p>
<p><img alt="Edge detection" src="https://enginetrouble.net/images/2013/09/screenshot_20130916_edgedetection.jpg" /></p>
<p>トゥーンレンダリングと組み合わせたいですね~。フォンシェーディングと合わせてみたら、デコボコとテカリがエッジに重なっていまいちな結果になりました。</p>
<p><img alt="Edge detection + Phong shading" src="https://enginetrouble.net/images/2013/09/screenshot_20130916_edgedetection_phong.jpg" /></p>
<h1>未解決のバグ</h1>
<p>OpenGL での実装はほとんど終わっていますが、たちの悪いバグが 1 個あります(汗)。3 週間ずっと未解決のままです。なかなか原因がわからず、お手上げ状態です。エンジンの組み方に問題があるのは確かなようです。</p>
<p>バンプマッピングでは、アルベドマップと法線テクスチャの 2 つをシェーダの中でサンプリングします。また遅延シェーディングでは、書き込んだ G-buffer をサンプリングします。G-buffer は複数のテクスチャに過ぎません。こういった「マルチテクスチャ」はごく当たり前のように使うことになります。エンジンに潜んでいるバグは、この「マルチテクスチャ」が特定の条件で使えないというものです。先に挙げたバンプマッピングの場合、テクスチャユニット0 にセットされたテクスチャ(アルベドテクスチャ)以外をサンプリングできません。テクスチャユニット1 にセットされたテクスチャ(法線マップ)ではなく、テクスチャユニット0 のアルベドテクスチャをサンプリングしてしまいます。単純なマルチテクスチャや、遅延シェーディングの場合、問題なくサンプリングできています。Hardware instancing + 遅延シェーディング + バンプマッピングの場合、G-buffer 書き込み時のみなぜかマルチテクスチャが上手くできないようです。早いところ原因をつかみたいです。</p>
<h1>秋からやること</h1>
<p>半月以上かけてもバグの原因がわからないとなると、非常に飽きてしまい、プログラムを書いていても面白くないものです。そこで、OpenGL からいったん離れて、再び Direct3D に戻ることにしました。ちょうど区切りもいいので OpenGL で動かすのはいったん後回しにします。兎にも角にも今作っているゲームをがんがん作っていきます。マルチテクスチャのバグはその後で暇を見つけて原因を探すことにします。グッバイ OpenGL。</p>
<p>日記を始めた理由の 1 つに、実装したことと次にやることを書くことで、いろいろな誘惑を振り切ってゲームの完成が遠のかないようにする、という目的がありました。…が、そんな当初の目的もすっかり忘れていました(汗)</p>
<p>実装がんばるよー!!</p>ビルドオートメーションツールGYPを使おう2015-09-27T00:00:00+09:00mogemimitag:enginetrouble.net,2013-07-06:2013/07/getting-started-with-gyp.html<p>GYP (Generate Your Projects) をご存知でしょうか。その名の通り、GYP はビルドツールで、ウェブブラウザの Chromium や JavaScript Engine の V8、そして JavaScript の実行環境に V8 を採用している Node.js など様々なプロジェクトで採用されています。
GYP はオープンソースです。<a href="https://code.google.com/p/gyp/">Google Code で公開</a>されており、誰でも手に入れることができます。
GYP は C++ をはじめ、ビルドを必要とする C や Objective-C を使用したプロジェクトにおいて、ビルドオートメーションを実現するために役に立ちます。
ただ、CMake や SCons に比べまだまだユーザが少なく、ドキュメントも豊富ではありません。そこで今日は GYP について紹介します。</p>
<h2>GYP ってどんなもの?</h2>
<p>例えばあなたが、C++ で書かれたスーパーエキセントリックでヘビーな、マルチプラットフォームをターゲットにしたゲームエンジンの開発者だとしましょう。数百、数千に渡るヘッダーファイルと、プラットフォームごとのインプリメントファイル、そしてそれらを検証するテストコードと、エンジンを初めて使う人のために用意したサンプルゲームのコードがあります。もちろんソースコードだけでは何の意味もありません。それらをビルドする必要があります。Visual Studio でビルドするためには Visual Studio 用のプロジェクトファイルが必要です。また Xcode でビルドするためには Xcode 用のプロジェクトファイルが必要になります。Linux でビルドするためには make ファイルを用意する必要があるでしょう。</p>
<h3>無慈悲にもプロジェクトは拡大していく</h3>
<p>今また、エンジンに新たなソースコードが加わりました!あなたは Visual Studio を開き、プロジェクトファイルに項目を追加します。すかさず OSX を立ち上げ Xcode のプロジェクトも変更します。ああ、次は make ファイルも編集しなきゃ。
新たに iOS, Android もサポートすることになりました。追加されたプラットフォームごとのインプリメントファイルや、テストコードも増え、いよいよ大変に。ユーザのために書いたチュートリアルのコードもビルドを前にして今となっては煩わしい。
もし、Visual Studio のプロジェクトや Xcode のプロジェクト、あるいは make ファイルを 1 つのテキストファイルで管理できたら。そして、コマンドをたたくだけでビルドに必要なファイルを自動生成することができたら、ずっと楽になるのに!
その悩みを解消するのが GYP (Generate Your Projects) です。</p>
<h2>GYP を手に入れよう</h2>
<p>お使いのコンピュータに Subversion は入っていますか?もしすでにインストールされていれば話は早いです。Subversion を使って最新の GYP を手に入れることができます:</p>
<div class="highlight"><pre><span></span>$ svn --version
$ svn co http://gyp.googlecode.com/svn/trunk gyp
</pre></div>
<p>もし、Subversion よりも Git を使うのが好みであれば、次のようにして入手できます:</p>
<div class="highlight"><pre><span></span>$ git clone https://chromium.googlesource.com/external/gyp.git gyp
</pre></div>
<p>本来の GYP の Subversion リポジトリから git-svn で引っ張ってくることもできますが、あまりお勧めしません。なぜならとても時間がかかるからです<sup id="fnref:1"><a class="footnote-ref" href="#fn:1" rel="footnote">1</a></sup>。
「それでもいいよ」と言う物好きな方のために git-svn を利用する方法も載せておきます:</p>
<div class="highlight"><pre><span></span>$ git svn clone -s http://gyp.googlecode.com/svn gyp
</pre></div>
<blockquote>
<p><strong>EDIT (May 8, 2015)</strong><br />
この記事を書いた当時、GYP は Subversion (SVN) でバージョン管理されていましたが、
2015 年 2 月に、SVN から Git に移行し、リポジトリは googlecode から
googlesource (<code>https://chromium.googlesource.com/external/gyp</code>) に移動しました。</p>
</blockquote>
<h2>Python 2.x をいれておこう</h2>
<p>GYP は Python で書かれたソフトウェアです。GYP を使うには Python 2.x<sup id="fnref:2"><a class="footnote-ref" href="#fn:2" rel="footnote">2</a></sup> の実行環境が必要になるので一緒に手に入れておきましょう。Python 3 では上手く動作しないことがあるのでバージョンを確認しておきましょう:</p>
<div class="highlight"><pre><span></span>$ python --version
Python 2.7.2
</pre></div>
<h2>GYP のセットアップ</h2>
<p>Python と GYP は手に入れましたか?手に入れた GYP は最初に一度だけセットアップする必要があります。</p>
<p><strong>Linux や Mac OSX の場合</strong></p>
<div class="highlight"><pre><span></span>$ <span class="nb">cd</span> gyp
$ <span class="o">[</span>sudo<span class="o">]</span> python setup.py install
</pre></div>
<p><strong>Windows の場合</strong></p>
<div class="highlight"><pre><span></span>$ <span class="nb">cd</span> gyp
$ python setup.py install
</pre></div>
<p>Linux や Mac OSX の場合、セットアップ後 <code>gyp</code> コマンドで GYP が使えるようになります。</p>
<div class="highlight"><pre><span></span>$ which gyp
/usr/local/bin/gyp
</pre></div>
<h2>GYP を使ってみよう</h2>
<p>触ってみないとわからないものです。早速 GYP を使ってビルドしてみましょう。今回は、非常に小さな謎のライブラリ "Trivial Engine" とそのテストコードをビルドします。必要な GYP ファイルとビルド対象となるソースコードは次の5つです。</p>
<ul>
<li>build/common.gypi</li>
<li>build/trivial.gyp</li>
<li>include/trivial/Entity.h</li>
<li>src/Entity.cpp</li>
<li>test/EntityTest.cpp</li>
</ul>
<p>例として扱うサンプルコードは <a href="https://github.com/mogemimi/gyp-getting-started">GitHub で公開</a>しています。Git を使って最新版を入手することができます。</p>
<div class="highlight"><pre><span></span>$ git clone git://github.com/mogemimi/gyp-getting-started.git
</pre></div>
<p>完全を期すため、ソースコードを載せておきます:</p>
<p><strong>include/trivial/Entity.h</strong></p>
<div class="highlight"><pre><span></span><span class="cp">#pragma once</span>
<span class="cp">#include</span> <span class="cpf"><string></span><span class="cp"></span>
<span class="k">namespace</span> <span class="n">Trivial</span> <span class="p">{</span>
<span class="k">class</span> <span class="nc">Entity</span>
<span class="p">{</span>
<span class="k">public</span><span class="o">:</span>
<span class="kt">char</span> <span class="k">const</span><span class="o">*</span> <span class="n">GetName</span><span class="p">()</span> <span class="k">const</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">SetName</span><span class="p">(</span><span class="kt">char</span> <span class="k">const</span><span class="o">*</span> <span class="n">name</span><span class="p">);</span>
<span class="k">private</span><span class="o">:</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">name</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">}</span><span class="c1">// namespace Trivial</span>
</pre></div>
<p><strong>src/Entity.cpp</strong></p>
<div class="highlight"><pre><span></span><span class="cp">#include</span> <span class="cpf"><trivial/Entity.h></span><span class="cp"></span>
<span class="k">namespace</span> <span class="n">Trivial</span> <span class="p">{</span>
<span class="kt">char</span> <span class="k">const</span><span class="o">*</span> <span class="n">Entity</span><span class="o">::</span><span class="n">GetName</span><span class="p">()</span> <span class="k">const</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">name</span><span class="p">.</span><span class="n">c_str</span><span class="p">();</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">Entity</span><span class="o">::</span><span class="n">SetName</span><span class="p">(</span><span class="kt">char</span> <span class="k">const</span><span class="o">*</span> <span class="n">name</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">this</span><span class="o">-></span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span><span class="c1">// namespace Trivial</span>
</pre></div>
<p><strong>test/EntityTest.cpp</strong></p>
<div class="highlight"><pre><span></span><span class="cp">#include</span> <span class="cpf"><iostream></span><span class="cp"></span>
<span class="cp">#include</span> <span class="cpf"><trivial/Entity.h></span><span class="cp"></span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">static_assert</span><span class="p">(</span><span class="n">__cplusplus</span> <span class="o">==</span> <span class="mi">201103L</span><span class="p">,</span> <span class="s">"C++11 supported."</span><span class="p">);</span>
<span class="n">Trivial</span><span class="o">::</span><span class="n">Entity</span> <span class="n">entity</span><span class="p">;</span>
<span class="n">entity</span><span class="p">.</span><span class="n">SetName</span><span class="p">(</span><span class="s">"Hello, GYP"</span><span class="p">);</span>
<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o"><<</span> <span class="n">entity</span><span class="p">.</span><span class="n">GetName</span><span class="p">()</span> <span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<p><strong>build/common.gypi</strong></p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'variables'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'conditions'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">[</span><span class="s1">'OS == "mac"'</span><span class="p">,</span> <span class="p">{</span>
<span class="s1">'target_arch%'</span><span class="p">:</span> <span class="s1">'x64'</span><span class="p">,</span>
<span class="p">},</span> <span class="p">{</span>
<span class="s1">'target_arch%'</span><span class="p">:</span> <span class="s1">'<(target_arch)'</span><span class="p">,</span>
<span class="p">}],</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'default_configuration'</span><span class="p">:</span> <span class="s1">'Release'</span><span class="p">,</span>
<span class="s1">'conditions'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">[</span><span class="s1">'target_arch == "arm"'</span><span class="p">,</span> <span class="p">{</span>
<span class="c1"># arm</span>
<span class="p">}],</span> <span class="c1"># target_archs == "arm"</span>
<span class="p">[</span><span class="s1">'target_arch == "ia32"'</span><span class="p">,</span> <span class="p">{</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'ARCHS'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'i386'</span><span class="p">],</span>
<span class="p">},</span>
<span class="p">}],</span> <span class="c1"># target_archs == "ia32"</span>
<span class="p">[</span><span class="s1">'target_arch == "mipsel"'</span><span class="p">,</span> <span class="p">{</span>
<span class="c1"># mipsel</span>
<span class="p">}],</span> <span class="c1"># target_archs == "mipsel"</span>
<span class="p">[</span><span class="s1">'target_arch == "x64"'</span><span class="p">,</span> <span class="p">{</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'ARCHS'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'x86_64'</span><span class="p">],</span>
<span class="p">},</span>
<span class="p">}],</span> <span class="c1"># target_archs == "x64"</span>
<span class="p">],</span>
<span class="s1">'configurations'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'Debug'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'defines'</span><span class="p">:[</span><span class="s1">'DEBUG=1'</span><span class="p">],</span>
<span class="s1">'cflags'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'-g'</span><span class="p">,</span> <span class="s1">'-O0'</span><span class="p">],</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'Optimization'</span><span class="p">:</span> <span class="s1">'0'</span><span class="p">,</span> <span class="c1"># /Od</span>
<span class="s1">'conditions'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">[</span><span class="s1">'OS == "win" and component == "shared_library"'</span><span class="p">,</span> <span class="p">{</span>
<span class="s1">'RuntimeLibrary'</span><span class="p">:</span> <span class="s1">'3'</span><span class="p">,</span> <span class="c1"># /MDd</span>
<span class="p">},</span> <span class="p">{</span>
<span class="s1">'RuntimeLibrary'</span><span class="p">:</span> <span class="s1">'1'</span><span class="p">,</span> <span class="c1"># /MTd</span>
<span class="p">}],</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'LinkIncremental'</span><span class="p">:</span> <span class="s1">'2'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_OPTIMIZATION_LEVEL'</span><span class="p">:</span> <span class="s1">'0'</span><span class="p">,</span> <span class="c1"># -O0</span>
<span class="p">},</span>
<span class="p">},</span> <span class="c1"># Debug</span>
<span class="s1">'Release'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'cflags'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'-O3'</span><span class="p">],</span>
<span class="s1">'msvs_settings'</span><span class="p">:{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'Optimization'</span><span class="p">:</span> <span class="s1">'2'</span><span class="p">,</span> <span class="c1"># /O2</span>
<span class="s1">'InlineFunctionExpansion'</span><span class="p">:</span> <span class="s1">'2'</span><span class="p">,</span>
<span class="s1">'conditions'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">[</span><span class="s1">'OS == "win" and component == "shared_library"'</span><span class="p">,</span> <span class="p">{</span>
<span class="s1">'RuntimeLibrary'</span><span class="p">:</span> <span class="s1">'2'</span><span class="p">,</span> <span class="c1"># /MD</span>
<span class="p">},</span> <span class="p">{</span>
<span class="s1">'RuntimeLibrary'</span><span class="p">:</span> <span class="s1">'0'</span><span class="p">,</span> <span class="c1"># /MT</span>
<span class="p">}],</span>
<span class="p">],</span>
<span class="p">},</span>
<span class="s1">'VCLinkerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'LinkIncremental'</span><span class="p">:</span> <span class="s1">'1'</span><span class="p">,</span>
<span class="s1">'OptimizeReferences'</span><span class="p">:</span> <span class="s1">'2'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_OPTIMIZATION_LEVEL'</span><span class="p">:</span> <span class="s1">'3'</span><span class="p">,</span> <span class="c1"># -O3</span>
<span class="p">},</span>
<span class="p">},</span> <span class="c1"># Release</span>
<span class="p">},</span> <span class="c1"># configurations</span>
<span class="s1">'variables'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'component%'</span><span class="p">:</span> <span class="s1">'static_library'</span><span class="p">,</span>
<span class="p">},</span>
<span class="p">},</span> <span class="c1"># target_defaults</span>
<span class="p">}</span>
</pre></div>
<p><strong>build/trivial.gyp</strong></p>
<div class="highlight"><pre><span></span><span class="p">{</span>
<span class="s1">'includes'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'common.gypi'</span><span class="p">],</span>
<span class="s1">'make_global_settings'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">[</span><span class="s1">'CXX'</span><span class="p">,</span><span class="s1">'/usr/bin/clang++'</span><span class="p">],</span>
<span class="p">[</span><span class="s1">'LINK'</span><span class="p">,</span><span class="s1">'/usr/bin/clang++'</span><span class="p">],</span>
<span class="p">],</span>
<span class="s1">'target_defaults'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'msvs_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'VCCLCompilerTool'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'WarningLevel'</span><span class="p">:</span> <span class="s1">'4'</span><span class="p">,</span> <span class="c1"># /W4</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_VERSION'</span><span class="p">:</span> <span class="s1">'com.apple.compilers.llvm.clang.1_0'</span><span class="p">,</span>
<span class="s1">'CLANG_CXX_LANGUAGE_STANDARD'</span><span class="p">:</span> <span class="s1">'c++0x'</span><span class="p">,</span>
<span class="s1">'MACOSX_DEPLOYMENT_TARGET'</span><span class="p">:</span> <span class="s1">'10.8'</span><span class="p">,</span> <span class="c1"># OS X Deployment Target: 10.8</span>
<span class="s1">'CLANG_CXX_LIBRARY'</span><span class="p">:</span> <span class="s1">'libc++'</span><span class="p">,</span> <span class="c1"># libc++ requires OS X 10.7 or later</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="s1">'targets'</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'trivial_engine'</span><span class="p">,</span>
<span class="s1">'product_name'</span><span class="p">:</span> <span class="s1">'TrivialEngine'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'static_library'</span><span class="p">,</span>
<span class="s1">'include_dirs'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'../include'</span><span class="p">,</span>
<span class="p">],</span>
<span class="s1">'sources'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'../src/Entity.cpp'</span><span class="p">,</span>
<span class="p">],</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'GCC_INLINES_ARE_PRIVATE_EXTERN'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span> <span class="c1"># '-fvisibility-inlines-hidden'</span>
<span class="s1">'GCC_SYMBOLS_PRIVATE_EXTERN'</span><span class="p">:</span> <span class="s1">'YES'</span><span class="p">,</span> <span class="c1"># '-fvisibility=hidden'</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="s1">'target_name'</span><span class="p">:</span> <span class="s1">'trivial_test'</span><span class="p">,</span>
<span class="s1">'product_name'</span><span class="p">:</span> <span class="s1">'TrivialTest'</span><span class="p">,</span>
<span class="s1">'type'</span><span class="p">:</span> <span class="s1">'executable'</span><span class="p">,</span>
<span class="s1">'dependencies'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'trivial_engine'</span><span class="p">,</span>
<span class="p">],</span>
<span class="s1">'include_dirs'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'../include'</span><span class="p">,</span>
<span class="p">],</span>
<span class="s1">'sources'</span><span class="p">:</span> <span class="p">[</span>
<span class="s1">'../test/EntityTest.cpp'</span><span class="p">,</span>
<span class="p">],</span>
<span class="s1">'xcode_settings'</span><span class="p">:</span> <span class="p">{</span>
<span class="p">},</span>
<span class="p">},</span>
<span class="p">]</span> <span class="c1"># targets</span>
<span class="p">}</span>
</pre></div>
<p>おっと、逃げないでください!サンプルにしてはあまりにも長いコードに思わずびっくりしたかもしれませんが、ひとまずここは騙されたと思って、早速ビルドしてみましょう。</p>
<h2>MSBuild (Visual Studio 2012) でビルドしてみよう。</h2>
<p>Visual Studio 2012 のソリューションファイル、プロジェクトファイルを作ってみましょう。コマンドプロンプトまたは MinGW + MSYS を開き、次を実行します。(説明のために、GYP の場所を <code>tools/gyp</code> ディレクトリ<sup id="fnref:3"><a class="footnote-ref" href="#fn:3" rel="footnote">3</a></sup>にしています。)</p>
<div class="highlight"><pre><span></span>$ python tools/gyp/gyp build/trivial.gyp --depth<span class="o">=</span>. -f msvs -G <span class="nv">msvs_version</span><span class="o">=</span><span class="m">2012</span> -D <span class="nv">target_arch</span><span class="o">=</span>ia32
</pre></div>
<p>成功すると <code>build</code> ディレクトリ内に各プロジェクトファイルが生成されます。
生成されたソリューションファイルをビルドしてみましょう。先に MSBuild を利用するためにパスを通しておきます。環境に合わせて適宜置き換えてください。コマンドプロンプトを開き:</p>
<div class="highlight"><pre><span></span>$ <span class="nb">set</span> <span class="nv">Path</span><span class="o">=</span>C:<span class="se">\W</span>indows<span class="se">\M</span>icrosoft.NET<span class="se">\F</span>ramework<span class="se">\v</span>4.0.30319<span class="p">;</span>%PATH%
</pre></div>
<p>MSBuild を実行します。</p>
<div class="highlight"><pre><span></span>$ MSBuild.exe build<span class="se">\t</span>rivial.sln
</pre></div>
<p>これでビルドができました!試しに動かしてみましょう。</p>
<div class="highlight"><pre><span></span>$ build<span class="se">\D</span>ebug<span class="se">\T</span>rivialTest.exe
Hello, GYP
</pre></div>
<p>ちなみに MSBuild でリリースビルドするには次を実行します。</p>
<div class="highlight"><pre><span></span>$ MSBuild.exe build<span class="se">\t</span>rivial.sln /p:Configuration<span class="o">=</span>Release
</pre></div>
<h2>Xcode (Apple LLVM Clang++) でビルドしてみよう。</h2>
<p>Xcode のプロジェクトファイルを生成してみましょう。
ターミナルを開き、</p>
<div class="highlight"><pre><span></span>$ gyp build/trivial.gyp --depth<span class="o">=</span>. -f xcode --generator-output<span class="o">=</span>./build.xcodefiles/
</pre></div>
<p>ターゲットアーキテクチャを明示的に指定する場合は次のように target_arch 変数を指定します。</p>
<div class="highlight"><pre><span></span>$ gyp build/trivial.gyp --depth<span class="o">=</span>. -f xcode -D <span class="nv">target_arch</span><span class="o">=</span>ia32 --generator-output<span class="o">=</span>./build.xcodefiles/
$ gyp build/trivial.gyp --depth<span class="o">=</span>. -f xcode -D <span class="nv">target_arch</span><span class="o">=</span>x64 --generator-output<span class="o">=</span>./build.xcodefiles/
</pre></div>
<p>Xcode のプロジェクトファイル <code>build/trivial.xcodeproj</code> が生成されます。ではビルドしてみましょう。</p>
<div class="highlight"><pre><span></span>$ xcodebuild -project build.xcodefiles/build/trivial.xcodeproj
</pre></div>
<p><code>xcodebuild</code> でリリースビルドを行う場合は次のようにします:</p>
<div class="highlight"><pre><span></span>$ xcodebuild -project build.xcodefiles/build/trivial.xcodeproj -configuration Release
</pre></div>
<p>実際にビルドしたプログラムをターミナルから実行してみます。</p>
<div class="highlight"><pre><span></span>$ build/build/Release/TrivialTest
Hello, GYP
</pre></div>
<p>感動しました?
初めて GYP をプロジェクトに導入してビルドが正常に通った日のことを僕は今でも鮮明に覚えています。
ええ、あれはとても晴れた日で、思わず感動して、えーと、そう、あの日の晩ごはんは確か…。</p>
<h2>GYP を実際のプロジェクトで使ってみよう。</h2>
<p>少々長くなりました(汗)
GYP について少しでもおわかりいただけたでしょうか。GYP は非常に強力なビルドオートメーションツールです。
あなたのプロジェクトが今よりずっと規模が大きくなって複雑になったとしても、そのときはきっと役に立ってくれるはずです(巨大なソフトウェアである Chromium で使用されているくらいですから!)。</p>
<p>今回サンプルとして用意した <code>.gyp</code> ファイルは、実際に開発で使用しているファイルを流用したものです。
サンプルとして最小におさまるよう努力しましたが、できる限りこのまま本来のプロジェクトに使っていただけるような形にしています<sup id="fnref:4"><a class="footnote-ref" href="#fn:4" rel="footnote">4</a></sup>。
もし次回機会があれば、お話できなかった GYP の機能について紹介します。</p>
<p><strong>追記</strong><br />
GYP の具体的な使い方に関して、次の記事を書きました。ご参考になりましたら幸いです。</p>
<ul>
<li><a href="/2014/01/gyp-basic-functions.html">GYP (Generate Your Projects) の入力フォーマットと基本的な使い方</a></li>
<li><a href="/2014/10/gyp-build-settings-in-msvs2014.html">ビルドツール GYP で Visual Studio プロジェクトのあれこれを設定しよう</a></li>
<li><a href="/2014/12/msbuild-settings-with-gyp.html">GYP における MSBuild Settings (msbuild_settings) の使い方</a></li>
<li><a href="/2015/09/gyp-generate-xcode-project.html">GYP を使って Xcode プロジェクトを作ってみよう</a></li>
</ul>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>およそ1~2時間くらい。 <a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:2">
<p>Python 2.6 または 2.7 がオススメです。Python ウェブサイトから取得できます。その他に chromium のリポジトリから Python を取得することもできます。Subversion を使って <em>"svn co http://src.chromium.org/svn/trunk/tools/third_party/python_26@89111 tools/python"</em> <a class="footnote-backref" href="#fnref:2" rev="footnote" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:3">
<p>V8 や node.js など GYP を採用しているプロジェクトでは、GYP 本体の場所を <code>project-root/tools/gyp</code> ディレクトリに指定するなどプロジェクトディレクトリ下にインストールすることも少なくないようです。 <a class="footnote-backref" href="#fnref:3" rev="footnote" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
<li id="fn:4">
<p>プロジェクトファイル生成時にターゲットアーキテクチャを指定できるように追加しています。また、C++11 を利用できるようにしています。その他、各プラットフォーム向けに必要な設定を行っています。 <a class="footnote-backref" href="#fnref:4" rev="footnote" title="Jump back to footnote 4 in the text">↩</a></p>
</li>
</ol>
</div>ゲームエンジンはじゃじゃ馬娘。2013-06-30T15:39:00+09:00mogemimitag:enginetrouble.net,2013-06-30:2013/06/game-engines-are-complex-beasts.html<p>ゲームエンジンはとても繊細です。ちょっと触れば暴れまわる、そんな困った生き物です。(そう、彼らは生きています。)
しばらく放っておいたエンジンを久しぶりに触ってみたら、もう大変。お手上げ状態でした。機嫌を怒らせないよう、慎重に触っていたのに!</p>
<p>少し前に見た忍道2の開発ブログで、内製ゲームエンジンのことを「じゃじゃ馬」と例えていたことが印象的でした<sup id="fnref:1"><a class="footnote-ref" href="#fn:1" rel="footnote">1</a></sup>。
手なずけるまで時間がかかったからだとか。
そういえば、ゲームエンジンアーキテクチャの日本語版でも、ゲームエンジンのことを「じゃじゃ馬」と表現していました<sup id="fnref:2"><a class="footnote-ref" href="#fn:2" rel="footnote">2</a></sup>。
この由来は、サブシステムが複雑にからみ合い、カバーする範囲も非常に広く、扱いが難しいことからきています。とても的確な表現です。</p>
<p>あばれ馬ではなく、じゃじゃ馬という表現がこの上なくしっくりきますね。
じゃじゃ馬という言葉には少し可愛らしさがあります<sup id="fnref:3"><a class="footnote-ref" href="#fn:3" rel="footnote">3</a></sup>。
じゃじゃ馬娘とは言いますが、あばれ馬娘とは言いません。腫れものであれどゲームエンジンが愛おしいのは、そういうことです。
じゃじゃ馬娘、最高!</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p><a href="http://www.shinobido2.com/blog/?p=386">http://www.shinobido2.com/blog/?p=386</a> 忍道2 の開発者によるブログ「忍道瓦版」。記事「“ハラキリエンジン”との戦い」の中で内製のハラキリエンジンをじゃじゃ馬と表現しています。 <a class="footnote-backref" href="#fnref:1" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
<li id="fn:2">
<p>『ゲームエンジンアーキテクチャ』 ジェイソン・グレゴリー著、大貫宏美、田中幸訳、今給黎隆、桐山忍、鴨島潤、湊和久監修、ソフトバンククリエイティブ。日本語版 252 ページ参照。「じゃじゃ馬」という訳が素晴らしいです。 <a class="footnote-backref" href="#fnref:2" rev="footnote" title="Jump back to footnote 2 in the text">↩</a></p>
</li>
<li id="fn:3">
<p>『ゲームエンジンアーキテクチャ』の原著 <em>Game Engine Architecture</em> では 'complex beasts' (複雑なじゃじゃ馬)と表現しているようです。 <a class="footnote-backref" href="#fnref:3" rev="footnote" title="Jump back to footnote 3 in the text">↩</a></p>
</li>
</ol>
</div>ようこそ、enginetrouble ブログへ。2013-06-23T00:34:00+09:00mogemimitag:enginetrouble.net,2013-06-23:2013/06/welcome-to-the-enginetrouble-blog.html<p>ブログを始めました!
いつも文章の書き出しについて悩みます。何を書けばいいのやら。</p>
<h2>"engine trouble" とは一体何なのでしょう。</h2>
<p>「エンジン (engine)」 と聞いて、何を思い浮かべますか。
ランタイムエンジン?ゲームエンジン?言語の処理系?
あるいは V8 (JavaScript Engine), OGRE, Irrlicht, CryENGINE, polycode,
Unity, cocos2d, Creation Engine, それとも自動車の V8?</p>
<p>ソフトウェアプログラマは日夜、エンジンを扱っています。
ゲームエンジンの上で動くビデオゲームを開発したり、スクリプトエンジンを使ったり。
ウェブブラウザの上では JavaScript エンジンが動いています。身の回りはエンジンだらけです。</p>
<p>けれどもそう簡単にエンジンは思ったように動いてくれないものです。
なぜなら、あなたの手を煩わせるのがエンジンの仕事の1つだからです。
(それでも手のかかる子ほどかわいいものです。)</p>
<p>僕は趣味でゲームプログラミングをしています。もっぱら、休日はゲームエンジン作りに夢中です。
ゲーム開発をしていると、日々いろんなことが起きます。
エラーが出てプログラムが動かないなんて困ったこともあれば、思い通りに動いてくれることもあります。
そんな他愛もない話をこれから書いていきます。</p>
<p>どうぞよろしくお願いします。</p>Welcome to the enginetrouble blog.2013-06-23T00:34:00+09:00mogemimitag:enginetrouble.net,2013-06-23:2013/06/welcome-to-the-enginetrouble-blog-en.html<p>I started writing my blog today.
The beginning of the entry often worries me. I don't know what to write.</p>
<h2>What is "engine trouble"?</h2>
<p>What comes to your mind when you hear the words of "engine"?
Runtime engine, game engine, or language implementations?
V8 (JavaScript Engine), OGRE, Irrlicht, CryENGINE, polycode,
Unity, cocos2d, Creation Engine, or V8 (car engine) ?</p>
<p>Software programmers frequently work with engines.
They develop a video game with game engines or use scripting language engines.
JavaScript engine is used in web browsers.
There are a lot of engines available for them.</p>
<p>However, engines that we use don't always run easily.
That is because the function of engines is to put you into trouble.
It is one of engine's roles.
It is a handful... but I like it.</p>
<p>Game programming is my hobby. I'm addicted to developing a game engine on holiday.
There is usually so much going on when I develop a video game.
Sometimes the code doesn't work due to errors, though sometimes it runs as intended.
In the blog, I will write about such trivial things.</p>
<p>Thanks for reading!</p>