C++幼女先輩

プログラミング成分多め

右辺値参照~完全転送まで100%理解するぞ! part4 Universal Reference その前に

こうやって 人に見せようと書く事は自分の理解になる

復習DEATH

前回の復習。右辺値参照を関数の引数にするには 一見コピーに見えるが void hoge( X x ) とプロトタイプすればよい 呼び出し側で hoge( std::move(x) ) とすればムーブコンストラクタが使われる という事 で 復習

#include <iostream>

using namespace std;

class X{
public:
    X(){
        cout << "constuct" << endl;
    }
    explicit X(X& x){
        cout << "copy" << endl;
    }
    explicit X(const X& x){
        cout << "const copy" << endl;
    }
    explicit X(X&& x){
        cout << "move" << endl;
    }
    ~X(){
        cout << "destuct" << endl;
    }
};

void hoge(X x){
    cout << "hoge(X)" << endl;
}

int main()
{
    {
        X x;
        hoge( x );
    }
    cout << "____" << endl;
    {
        const X x;
        hoge( x );
    }
    cout << "____" << endl;
    {
        X x;
        hoge( std::move(x) );
    }
    cout << "____" << endl;
    {
        hoge( X() );
    }
    cout << "____" << endl;
    {
        hoge( std::move(X()) );
    }
}



constuct
copy
hoge(X)
destuct
destuct
____
constuct
const copy
hoge(X)
destuct
destuct
____
constuct
move
hoge(X)
destuct
destuct
____
constuct
hoge(X)
destuct
____
constuct
move
hoge(X)
destuct
destuct

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

std::move() で呼ぶとムーブされ、何もつけないとコピーされる。正しい使い方だ。 少し気になるのは X() と右辺値で直接呼んだ場合は一時オブジェクトが生成されず効率的だが std::move( X() ) とすると、一時オブジェクトを生成し、それを右辺値にキャストしムーブコンストラクタが発生する (最適化で このへんは最適化されると思うけど、実装依存なので 可能なら コードで最適化しておきたい)

本題

上のコードで納得いかないところ オブジェクトの参照だけで良い時にも無駄にオブジェクトのコピーが走る 納得いかないので 調査をする

前回は一緒に受け取るようにした 参照、const参照、右辺値参照に分割してみる (先にこちらから説明すべきであった 反省)

#include <iostream>

using namespace std;

class X{
public:
    X(){
        cout << "constuct" << endl;
    }
    explicit X(X& x){
        cout << "copy" << endl;
    }
    explicit X(const X& x){
        cout << "const copy" << endl;
    }
    explicit X(X&& x){
        cout << "move" << endl;
    }
    X& operator =(X& x){
        if(&x==this) return(*this);
        cout << "copy assignment" << endl;
        return(*this);
    }
    X& operator =(const X& x){
        if(&x==this) return(*this);
        cout << "const copy asignment" << endl;
        return(*this);
    }
    X&operator =(const X&& x){
        if(&x==this) return(*this);
        cout << "move asignment" << endl;
        return(*this);
    }
    ~X(){
        cout << "destuct" << endl;
    }
    
    void foo(){};
};

static X xx;
/*
void hoge(X x){
    xx = x;
    cout << "hoge(X)" << endl;
}
*/
void hoge(X& x){
    xx = x;
    cout << "hoge(X&)" << endl;
}
void hoge(const X& x){
    xx = x;
    cout << "hoge(const X&)" << endl;
}
void hoge(X&& x){
    xx = std::move(x);
    cout << "hoge(X&&)" << endl;
}


int main()
{
    cout << "____" << endl;
    {
        X x;
        hoge( x );
        x.foo();
    }
    cout << "____" << endl;
    {
        const X x;
        hoge( x );
    }
    cout << "____" << endl;
    {
        X x;
        hoge( std::move(x) );
    }
    cout << "____" << endl;
    {
        hoge( X() );
    }
    cout << "____" << endl;
    {
        hoge( std::move(X()) );
    }
    cout << "____" << endl;
}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

面倒なので static変数にコピーしているが、普通はクラスのメンバに代入するだろう (explicit付けたよ! 前回の例では つけられないが ムーブコンストラクタにはexplicit付けるべき)

左辺値参照が引数に入ってきたら、中身を自分にコピー 右辺値参照であればムーブ

バッチリ!!

ところがこれを前回のように hoge(X x) で両方を受け取ると困った事になる

xx = x; と書けば ムーブ出来る時でもコピーになるし xx = std::move(x); では、常にムーブになる

ではやはり、 参照、const参照、右辺値参照 と常に3種類の関数が必要なのか??? また 引数がふえれば 6種類、9種類・・・ 組み合わせ爆発してしまう

そこで登場するのが Universal Reference

次回ご期待!

右辺値参照~完全転送まで100%理解するぞ! part3 関数呼び出し

いまいちまだ 納得していない部分もある(理解不足

実践的な関数呼び出し

右辺値参照はムーブセマンティクスのために使われる ムーブセマンティクスは主に ・ 所有権の移動 ・ コピーのコストを削減(ポインタのすげ替え)

で使われる

所有権の移動

コピーされたら困る(インスタンスが複数になってはダメ)ものに 例えば スレッドや、ユニークポインタ等で使う

#include <iostream>
#include <thread>

using namespace std;

int main()
{
  thread thread_;
  
  
  // ムーブコンストラクタが呼ばれるので threadは2個出来ない
  thread_ = thread( [] { cout << "done" << endl; });  
  
  // コピーコンストラクタ不可能
  // thread thread2_ = thread_;   

  if (thread_.joinable()) thread_.join();

}

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

上記コードは std::threadは コピーコンストラクタがdeleteされ、ムーブコンストラクタしか無い thread = thread( [] { cout << “done” << endl; });
は、右辺値参照であるため、ムーブコンストラクタが呼ばれ、結果的に threadの所有権が thread
に渡るので 1個しかスレッドが作成されない もし仮に コピーコンストラクタで動いた場合は 一時オブジェクトとしてスレッドが作成され、さらに同じものを thread_にコピーし スレッドは2個作成されるものと思われる

thread thread2 = thread;
は、もちろんコピーコンストラクタは無いため コンパイルエラーになる

同じく unique_ptrもコピーコンストラクタが無く、ムーブコンストラクタのみ許可されているため ポインタが2個になる事はない

コピーのコストを削減

こちらが、よく使う方。

#include <iostream>

using namespace std;

class X{
public:
    X(X& x){
        cout << "copy" << endl;
    }
    X(X&& x){
        cout << "move" << endl;
    }
};


int main()
{
    X x;
    X x1 = x;          // lvalue
    X x2 = move(x);    // rvalue
    X x3 = move(X());  // rvalue
}



copy
move
move

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

復習になるけど、lvalueとrvalueにより コピーとムーブになる

このことを踏まえ 正しい関数呼び出しは

#include <iostream>

using namespace std;

class X{
public:
    X(){
        cout << "constuct" << endl;
    }
    X(X& x){
        cout << "copy" << endl;
    }
    X(X&& x){
        cout << "move" << endl;
    }
    ~X(){
        cout << "destuct" << endl;
    }
};

X hoge(X x){
    return( move(x));
}

int main()
{
    X x;
    hoge(move(x));
}


constuct
move
move
destuct
destuct
destuct

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

上記が正しい関数プロトタイプである

実際には hogeのreturnには std::moveを書かなくとも、moveしてくれるので書かないのが普通である

ただし、見てわかるとおり hogeを呼ぶ際に std::moveを忘れて hoge(x) とすると

constuct copy move destuct destuct destuct

と、当然のように コピーコンストラクタが走るので、使うときによく確認しなければならない

もちろん、コピー禁止にするなら hogeの引数を X&& にすればよい

#include <iostream>

using namespace std;

class X{
public:
    X(){
        cout << "constuct" << endl;
    }
    X(X& x){
        cout << "copy" << endl;
    }
    X(X&& x){
        cout << "move" << endl;
    }
    ~X(){
        cout << "destuct" << endl;
    }
};

X hoge(X&& x){
    return( move(x));
}

int main()
{
    X x;
    hoge(move(x));
}


constuct
move
destuct
destuct

と、さらに処理が速くなり、左辺値の場合はコンパイルエラーになるので、ムーブオンリーにできる

まとめ

大体の場合は、関数プロトタイプでは 実体を渡せばいい X hoge(X x) 関数を呼ぶときに 右辺値であればムーブ、左辺値であればコピーされる コピーしたくない時は、呼ぶときに ムーブセマンティクス忘れないように!

右辺値参照~完全転送まで100%理解するぞ! part2 VisualStudioの罠

はじめに

おまけ。 右辺値参照を理解するうえで大きな妨げになった VisualStudioでの挙動(バグ?? 詳細希望)

左辺値は暗黙で右辺値参照にキャスト出来ない

バグ? の前に基礎から 左辺値(参照)は 左辺値にのみ 右辺値(参照)は 右辺値にのみ 暗黙キャストがゆるされる

#include<iostream>

void foo1(int& i) {}
void foo2(int&& i) {}


auto main() -> int{

    int i;
    int &i1(i);                        // lvalue -> lvalue ref         well-formed
    int &i2(i1);                   // lvalue ref -> lvalue ref     well-formed
// int &i3(1);                     // rvalue -> lvalue ref          ill-formed

    int &&ii(1);                  // rvalue -> rvalue ref         well-formed
// int &&ii2(ii);                  // lvalue ref -> rvalue ref      ill-formed    iiは右辺値を代入しているがそれ自体は左辺値(アドレスが存在する)
    int &&ii3(std::move(ii));      // rvalue ref -> rvalue ref     well-formed


    foo1(i);                        // lvalue -> lvalue ref         well-formed
    foo1(i1);                       // lvalue ref -> lvalue ref     well-formed
// foo1(1);                        // rvalue -> lvalue ref          ill-formed

    foo2(1);                       // rvalue -> rvalue ref         well-formed
// foo2(ii);                       // lvalue ref -> rvalue ref      ill-formed    iiは右辺値を代入しているがそれ自体は左辺値(アドレスが存在する)
    foo2(std::move(ii));            // rvalue ref -> rvalue ref     well-formed
}

上記の通り、右辺値参照と左辺値参照は 暗黙で変換できないので std::moveでキャストする必要がある

VCの不具合??

stringやvector等で試すと不思議なことがおこった

#include<iostream>

void Str(std::string& s) {
}

auto main() -> int{
  Str( std::string("right value"));  // rvalue -> lvalue ref   ill-formだがVisualStudioでは通る・・
}

clanやgccで試したが 当然エラーになるし VisualStudioでも int等で試すとエラーになるが stringやvector等のコンテナだと いずれも上記のコードが通るので 移植性を考える場合は 上記のコードを書かないようにすべきと思われる

右辺値参照~完全転送まで100%理解するぞ! part1 右辺値参照とは

TL;DR

はじめに、私が右辺値参照を調査したのは、何番煎じかわからない 解説をしたいわけではなく VisualStudioでの動作の違い、universal referenceの特殊化したときの動き、複数の引数をuniversal referenceしたとき デフォルト引数・・・ など、応用したときに色々と悩んだから それを自分の中でまとめようとおもう

とはいっても説明

右辺値参照は、実はそんなにも難しくないと思う 難しいと思う人は、名前に騙されて勘違いされているだけで たった3つの事柄を知れば 理解が一気に深まるとおもう 自分はそうだった。

とりあえず 最初にその一言をいうと

右辺値は名前が悪い。 一時オブジェクト あるいはアドレスを持たないものと考えると良い

std::moveは名前が悪い。移動ではなく右辺値へのキャスト演算子である

関数の仮引数で右辺値参照(hoge&&) で受けても左辺値(hoge&)であるので右辺値にするには std::move()でキャストする

私の場合はこの3つで一気に80%まで理解できた

何番煎じかわからないが 右辺値参照について調査する

右辺値とは

一時オブジェクトだが 細かいのは他の人のブログみてもらいたい

std::string s("hoge");   // &sとアドレスが取れる=実態があるので左辺値
foo( s );               // 左辺値を関数に渡している

str::string("hoge");    // アドレス取れない一時オブジェクト
foo( str::string("hoge") ) // 右辺値を関数に渡している

右辺値の考えが重要になったのは、例えば上記の場合、右辺値を関数に渡すときにオブジェクトのコピーが走る 一時オブジェクトは コピーせず破壊しても良いので無駄である ので、渡すときにコピーを行わず所有権を渡す(ポインタをすげかえる)事が出来れば パフォーマンスがあがる

同様に左辺値も、所有権を渡したいシーンもあるだろう。そんな時は std::move()を使い 右辺値にキャストし所有権を移動出来るようにする

ムーブコンストラクタ

class Hoge{
public:
  Hoge(Hoge &obj){} // コピーコンストラクタ
  Hoge(Hoge &&obj){} // ムーブコンストラクタ
};

書式は上のように hoge&& と、&を2個重ねる

実際に実装しようとするとなかなかに面倒くさい。 移動元のdestructorも呼ばれるためデータが消されないように注意する 自分のムーブを禁止しなければならない 移動代入演算子 operator=(Hoge&&)も実装してね noexpectに気をつけろ ・・・

Hoge(Hoge&&) = default; と、デフォルトのムーブコンストラクタも作成可能だが 条件次第では作れない事もあるので自分で実装する方法を知らねばならない

Microsoftにとても良いページがあったので これを見て設定してほしい https://msdn.microsoft.com/ja-jp/library/dd293665.aspx

std::move()

最初に std::move()は 右辺値へのキャストのみ(移動はしない)と書いた 右辺値参照にキャストするだけだと それが上記のムーブコンストラクタと関係する

#include<iostream>

using namespace std;

void foo( string& s) {
  cout << "lvalue" << endl;
};
void foo( string&& s) {
  cout << "rvalue" << endl;
};


auto main() -> int{
  string s("s");

  foo(string("hoge"));
  foo(s);    
  foo(std::move(s));
}



rvalue
lvalue
rvalue

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

sは左辺値なので、string& の方が呼ばれる。コンストラクターでいうと コピーコンストラクタである が、std::move()をつかうと string&&になり 右辺値参照となっている コンストラクターでいうと ムーブコンストラクタになる

仮引数では左辺値参照である

ここは解せないと最初に感じるポイントだろう string&& s(“hoge”); のようなコードを考えてみる sには右辺値参照の値が入っている 一見 sは右辺値参照であると思うが、思い出してほしい 右辺値というのは アドレスがとれない実体のない一時オブジェクトということだ しかしこの sには 実体がある ので、 右辺値参照をもった左辺値である ややこしい。 ので 関数のプロトタイプ void hoge(int &&i) のiは 左辺値なのである

#include<iostream>

using namespace std;

void foo2( string& s) {
  cout << "lvalue" << endl;
};
void foo2( string&& s) {
  cout << "rvalue" << endl;
};

void foo( string& s) {
  foo2(s);
};
void foo( string&& s) {
  foo2(s);
};

auto main() -> int{
  string s("s");

  foo(string("hoge"));
  foo(s);    
  foo(std::move(s));

}



lvalue
lvalue
lvalue

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

解せないかもしれないが、仮引数の時は Hoge&&で受け取っていても左辺値参照になっている これを解決するためには、左辺値参照で入ってきた仮引数をふたたび右辺値参照にキャストする必要がある

先ほどの foo(string&&)からfoo2を呼ぶときに std::move()を使い右辺値参照にキャストすればよい

void foo( string&& s) {
  foo2(std::move(s));
};


rvalue
lvalue
rvalue

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

これで、仮引数の時は左辺値参照になっているというのが実証できた

とりあえず、ざっと一通りの右辺値参照の基礎をかいた 多分 この程度知っているという人が多いと思うので 読み飛ばしてください

Railsはじめました

はじめに

私 みやたけゆき がRailsを始めるなんて冗談だろ?? 確かに私は なんでもかんでもRailsの風潮には否定的であるが 正しくRailsを使うのは ありと思ってる

RailsというかRubyには根本的な弱点 遅いという問題があるのに 速度が必要なリアルタイム処理にRailsを使ったり 遅いならサーバ台数を増やせばいいじゃない? それランニングコスト増えてますよ! など 気に入らない事も多い

サーバ台数が増えれば当然マシン代も増えるが、管理コストも増える事を忘れてはいけない 例えば以前 Railsで100台のサーバが必要だったサービスを C++で書き替えて 1台で余裕になった事もある

そんなわけで Rails2日目ですが Railsの良しあしを今の段階でかいていく

論じない事

Rubyは書いていて楽しい言語

楽しさは人それぞれであるし、私はC++が一番手になじみ楽しい それになにより、プログラマが楽しいからという理由で言語を選択すべきではない

Rubyは日本発だ

私は人並み以上に日本が好きだし愛国心はあるので、Rubyが世界一の言語になってほしいと願う ただ、もしRubyが世界一の言語でない場合は、その言語にこだわり 世界一のサービスを作れないというのは 日本を愛するものとしては 悲しい 世界一のサービスを作るためには、国産だからという理由で使うのは 真の愛国心ではない

日本語情報が多い

英語読め。 PythonだってJavaだって日本語で十分情報がある

Railsエンジニアは多いから集めやすい

今回は 派遣会社目線ではないし、それを言うならJavaエンジニアの方が集めやすいとおもわれる

スクリプト言語の功罪

私はコンパイルできる言語の方が事前にエラーを検知できるので好きだが 単体テストかけばいいじゃん という宗派もあったりで スクリプトの功罪については直接は語らない 同じく GCについても

デメリット

あえてデメリットから書く

遅い

Rubyの最大の弱点で、言語仕様みるかぎりどうしようもない。。。 mrubyとか方法はあるんだろうけど 根本的に遅い スレッドセーフになってないのでネイティブスレッド基本使えない 動的型付け言語 演算子オーバーロードできたり 最適化しにくい などなど 私にとっては この時点でかなり厳しい

コードに柔軟性が高い

シンタックスシュガーが多く、同じ処理を何通りにも書くことができる これをコード書くのが楽しいと表現する人もいるが ビジネスで考えるとPythonのように、一通りの書き方しかない方が良いにきまっている 特に Perl時代のPerl風構文が残っていると殺意がおこるひとも多いだろう

Railsの学習コスト

Railsはかなり大きなフレームワークのため学習コストが高い

メリット

言語が比較的簡単

といってもC++との比較 JavaPythonと比べたらどうなんだろうか Pythonとあまり変わりないきがする

Rails Console

便利ですね Pythonにもあると思うけど

総合評価

私がRailsにあまり好意的でないのは上でもかいたが とにかく 実行速度が遅く、その他はPythonと変わらない PythonにもDjangoというWebフレームワークもあるし コードの難易度も変わらないと思う

よくRailsは開発速度が速いというが、実際は Pythonで書いても同じだと思う Rubyの方がコード量が少ないというが、タイピングよりアルゴリズムを考える方が重要なので たいしたメリットではない Javaのようにコード記述量はIDEでサポートすればいいわけだし

ただ 現実では日本ではスタートアップのモックアップとしてRailsが好まれるので モックアップスピードとしては速いのだろう ただし、海外ではPythonが多いので、たいして変わらないと思われる しかしRubyは実行速度が遅いため、スケールしたら他の言語で書き直す事が多い さらに Pythonは同じ機能は一つの書き方しか出来ないのでメンテナンスに有利 そういうのを考えると、たいして開発速度かわらないのなら Pythonで作るべきだと思うし GoやJavaC#を極めて ライブラリを作り開発した方が良いと思う

それに そもそも BaaSサービスを使って作る方がモックアップ速いので 作り直し前提であれば BaaSを使いこなすのも良いと思う

そんなわけで、やはり 私はあまりRailsに対して必要性をあまり感じていない PythonとかGoでいいじゃん!

ただ、mrubyなど速度の速いRubyにたいしては期待しているが、使ってないからまだわからず

#キャッシュレス社会を#求めて#クレジットカード整理し#ブラックカード目指す

はじめに

中国の人から、中国ではほとんどのものがウェイボー決済で買える。QRコード読み取って決済すると自動販売機から商品が出てくる ときき 驚いた 日本ではスマホを使った決済の場合は SUICAEDYFelicaのような Bluetooth的なもので行うが、iPhoneにはその機能がなく 独自でカード発行して自動チャージにするとか行う しかしQRコードならiPhoneでも出来る ローテクノロジーで解決させる さすが中国流と思った

そんなわけで日本のお店はカード払い出来ないところも多い 10年ほど前に一度 現金をほぼ持たず カードだけで生活を試みたが 高級なデパートでしか使えず苦労した覚えがあるが 今は SUICAなどで比較的小さなお店でもチャンスがある

キャッシュレス社会を作るために日本の弱点をさぐるために 再び キャッシュレスを目指すことにした

10年前より圧倒的に簡単なはずだ

それとついでに クレジットカードをまとめて、ついでなので ブラックカードを目指そうと思う

ブラックカード

とは、各クレジットカードで最高級のものである アメックスセンチュリオンは年会費30万、その他15万とか10万。そこそこの年会費がかかる ただし 完全招待制で、クレジットカードをよく使い 金持ち認定されると招待される サービスとしては コンシェルジュサービスで、例えば 六本木で8名でフランス料理ディナーの予約など頼むと いい店を見つけて予約してくれたり 上手く使えば 十分元とれる

しかし、楽天ブラックカードは 実質はプラチナカードレベルなんだが 見た目はブラックで、みんなから尊敬されかねない 年会費も3万程度と ネタに手に入れるには十分だろう いちおうコンシェルジュサービスもあるようだ

ということで 楽天ブラックカードを手に入れるプランを考える

カード整理

わたしは 無料有料ふくめ カードをいっぱいもっている。これでも沢山解約したんだけどね・・

セゾンが 無料、クラブオン、アメックスプラチナ エポス ゴールド 楽天 ゴールド ライフ ノーマル ビックカメラ ・・・

フリーランス時代に作れるうちに作っておけと いっぱい作ったが整理しきらん・・ 無料カードはそのまま残すとして アメックスプラチナは解約することにした。

そして 主に使うカードは3枚にした

楽天 プレミアムカード JCBEdy

ブラックにするために これは必須。JCBカードにしたのは nanacoへのポイントをするため。 nanacoで税金などを払うと楽であろう 下記の戦略のため 今年はあまり使わない プレミアムカードの特典として ファーストパスが利用できるので、空港でVIPラウンジいける ブラックにするインビテーション条件は謎で 年500万クレジットカード払っても来ないという人もいたり 挑戦してみようとおもう

Eposゴールド VISA

年50万程の買い物をすると 一生ゴールドが無料になる 多分招待されたので無料のはずだが 一度確認してくる もし無料になっていなければ 今年無料枠にするため 買い物がんばる たしか 持っているだけで旅行保険入るので便利

イオン ゴールド Master?VISA WAON

年会費無料。ただしインバイト必要 ノーマルカード(SUICAダメ)を作り 年100万等の条件をみたせばインバイトくる 年会費無料のゴールドなので取っておこう

その他

nanacoカード作る Suicaはとりあえず モバイルで、カードで払う。本当は ビックSuicaオートチャージしたいが・・

ロードマップ

楽天をプレミアムにするためには いったんノーマルを解約必要なので解約する nanaco契約 イオンカード契約 Eposカード 無料になっているか確認 定期的な支払いをまとめる。とりあえず楽天カード

Eposとイオンカードでそれぞれ 50万、100万 以上の買い物をがんばる 税金は楽天カード nanacoで払う

イオンとEposをゴールドにしたら 楽天カードに集約し ブラック目指す

Vulkan LunarG-SDK Sampleで勉強する 01-init_instance

あらすじ

前回 あらたにVulkanAPIを勉強する事にし、ドライバのインストールと 現在オフィシャルの LunarGという VulkanSDKをインストールし Sampleのビルドまで行った

ので、Sampleを参考に Vulkanの勉強をする

サンプル一覧

Samplesの下だけでも46個あった。 非常に多いのだが1つだけ注意点

なぜかこのサンプルたち、見た限りでは レンダーループが作られておらず 1秒後に終了するように作っている

おそらくは、WindowsAndroidLinuxに対応するために、レンダーループを共通化するのが面倒だったのでは? と思うが 真意不明

46個のサンプルのうち チュートリアルになっている15個から見てみよう

チュートリアル01は init_instanceで、Vulkanのインスタンスを初期化するサンプルのようである チュートリアルを順にならべると

init_instance Vulkanインスタンスの初期化 enumerate_device デバイスの列挙 init_device デバイス初期化 init_command_buffer コマンドバッファの初期化 init_swapchain スワップチェインの初期化 init_depth_buffer 深度バッファの初期化 init_uniform_buffer Uniformバッファの初期化 init_pipeline_layout パイプラインレイアウト初期化 init_descriptor_set でスクリプタセット初期化 init_render_pass レンダーパス初期化 init_shaders シェーダー初期化 init_frame_buffers フレームバッファ初期化 init_vertex_buffer 頂点バッファ初期化 init_pipeline パイプライン初期化 draw_cube キューブ描画

やれやれ・・ たかがキューブ描画のために、初期化こんなにあるのか・・

01-init_instance

#include <iostream>
#include <cstdlib>
#include <util_init.hpp>

#define APP_SHORT_NAME "vulkansamples_instance"

int sample_main(int argc, char *argv[]) {
    struct sample_info info = {};
    init_global_layer_properties(info);

    /* VULKAN_KEY_START */

    // initialize the VkApplicationInfo structure
    VkApplicationInfo app_info = {};
    app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
    app_info.pNext = NULL;
    app_info.pApplicationName = APP_SHORT_NAME;
    app_info.applicationVersion = 1;
    app_info.pEngineName = APP_SHORT_NAME;
    app_info.engineVersion = 1;
    app_info.apiVersion = VK_API_VERSION_1_0;

    // initialize the VkInstanceCreateInfo structure
    VkInstanceCreateInfo inst_info = {};
    inst_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    inst_info.pNext = NULL;
    inst_info.flags = 0;
    inst_info.pApplicationInfo = &app_info;
    inst_info.enabledExtensionCount = 0;
    inst_info.ppEnabledExtensionNames = NULL;
    inst_info.enabledLayerCount = 0;
    inst_info.ppEnabledLayerNames = NULL;

    VkInstance inst;
    VkResult res;

    res = vkCreateInstance(&inst_info, NULL, &inst);
    if (res == VK_ERROR_INCOMPATIBLE_DRIVER) {
        std::cout << "cannot find a compatible Vulkan ICD\n";
        exit(-1);
    } else if (res) {
        std::cout << "unknown error\n";
        exit(-1);
    }

    vkDestroyInstance(inst, NULL);

    /* VULKAN_KEY_END */

    return 0;
}

短いので全部乗せた。 初回なので少しだけ丁寧に

vkで始まる関数がVulkanのAPIである かなりC言語臭いが、それをラップしてC++っぽくしたライブラリもあるので今度紹介する

#include <util_init.hpp>

これはサンプルの独自のUtilであるが、大したことをしていない。今回で言えば sample_mainの定義がある程度

ここで少し気になる sample_main() という関数 そう、エントリーポイント関数がないやん? きっとこれがエントリーポイントだろ? すぐわかるが、一応調べると

#ifndef __ANDROID__
int main(int argc, char **argv) { sample_main(argc, argv); }
#endif

Android以外は単純に sample_mainを呼ぶだけである

Androidの場合は Android_handle_cmdで sample_mainを登録したり NDKから呼ぶため特別処理になっている

とりあえずは sample_mainはエントリーポイントだと思って良い

レイヤー列挙

init_global_layer_properties(info); の中身は

    do {
        res = vkEnumerateInstanceLayerProperties(&instance_layer_count, NULL);
        if (res)
            return res;

        if (instance_layer_count == 0) {
            return VK_SUCCESS;
        }

        vk_props = (VkLayerProperties *)realloc(
            vk_props, instance_layer_count * sizeof(VkLayerProperties));

        res =
            vkEnumerateInstanceLayerProperties(&instance_layer_count, vk_props);
    } while (res == VK_INCOMPLETE);

    /*
     * Now gather the extension list for each instance layer.
     */
    for (uint32_t i = 0; i < instance_layer_count; i++) {
        layer_properties layer_props;
        layer_props.properties = vk_props[i];
        res = init_global_extension_properties(layer_props);
        if (res)
            return res;
        info.instance_layer_properties.push_back(layer_props);
    }
    free(vk_props);

    return res;

となっている。 まずレイヤーとは何かだが、まだ詳細につかめていないが Vulkanのコマンドに割り込んで色々なことをするものらしい 例えば デバッグに使う

vkEnumerateInstanceLayerProperties の2番目の引数にnullptrを入れると、取得可能なレイヤー数が取れるので レイヤー数を取得したあと reallocで個数分確保している まことにC言語である

列挙中に極まれにレイヤーが追加されることがあるらしく VK_INCOMPLETE が返ってきたら、カウントからやり直すようになっている

インスタンス作成

vkCreateInstanceを呼ぶことで、Vulkanのインスタンスを作成する これが チュートリアル01のゴールだ。

インスタンス作成は単純で、VkApplicationInfoとVkInstanceCreateInfo を設定して渡せばよい 第一引数に VkInstanceCreateInfo  第二引数は 通常はnullptrでよい。必要があれば下記 MemoryAllocationを読む事 第三引数に Vulkanのインスタンスが入る

MemoryAllocation https://vulkan.lunarg.com/doc/view/1.0.33.0/linux/vkspec.chunked/ch10.html

vkDestroyInstance でインスタンスを解放すれば終了

初回なので丁寧に見たが、面倒なだけで そこまで難しくはなさそうだ