ぷろみん

プログラミング的な内容を扱ってます

最近OpenGLを触っていたので、オススメの勉強法

検索

検索ワード + wikiで検索すると情報の精度が格段に上がる。
これはOpenGL規格を策定しているkhronosが運営しているwikiに素晴らしい情報が集約しているから。

https://www.khronos.org/opengl/wiki

この機能は非推奨だよとか、この機能の代わりにこの機能を使うことを検討しようとか書いてあるので自然とGL力がつく。

また、関数の実際の使い方サンプルを検索したければ

https://github.com/google/angle

から検索するのがオススメ。
angleはその性質から全OpenGLの機能を網羅しているはずなので、全機能のサンプルが検索できるはず。

使うライブラリ

OpenGL 4.6

https://github.com/skaslev/gl3w

gl3wがオススメ。
シンプルでパブリックドメインなので自身のプロジェクトに組み込みやすい。
また、仕組み的にOpenGLの新規規格への追従が速そう。


Windowの作成

C++でWindowの作成に特化したものを私は知らない。
誰か知ってたら教えて!

Windowを借りるだけだと大げさだがglfw3を利用している。

https://github.com/glfw/glfw

自前でウィンドウを作ることが簡単であるが故にみんな自分で作って組み込んでしまう。
私も何度も作ってきたので、もう作りとうない。

C++でslice windows

Ranges library (C++20) - cppreference.com が楽しそうなので遊んでみます。
filter / mapに対応してくれるのはありがたいです。

range/v3がイテレータライブラリのようで
slidingやzip等の欲しい機能はそろってそうに見えました。

しかし、記事内のpositionsを下記のように渡すと期待通り動作しませんでした。
helperが必要そう?
動作して違う値を表示してしまうあたりオフセットがずれている感じがします。

for(const auto& [p0, p1] : positions | ranges::views::sliding(2))

|でつなげるお試し実装

 for(const auto& [p0, p1] : positions | slice_windows2()) {

がしたい。

view_adaptorが必須なのかと思っていたら vies::enumerate などはview_facadeしか使っていない。

view_closureにアダプタ生成部分を包んで置いておくというのが重要なようで
後はRangeとview_closureのoperator |を定義しているだけで普通のC++実装っぽい。

struct slice_windows2_fn {
  template<typename Rng>
  auto operator()(Rng && rng) const {
    return rng | ranges::views::sliding(2);
  }
};
using slice_windows2 = ranges::views::view_closure<slice_windows2_fn>;

でいける。
この戻り値で独自のview_adaptorとかview_facadeを返す。

return slice_windows2_view(rng);

としてやれば下記実装を|でつなげることができる。

view_facadeのお試し実装

#include <iostream>
#include <vector>
#include <range/v3/all.hpp>

namespace {

struct float2 {
    float x;
    float y;
};
    
using List = std::vector<float2>;
    
class slice_windows2_view : public ranges::view_facade<slice_windows2_view> {
  public:
    slice_windows2_view() = default;
    explicit slice_windows2_view(const List& data) : it0_(data.cbegin()), it1_(data.cbegin()), end_(data.cend()) {
        ++it1_;
    }

  private:
    friend ranges::range_access;
    List::const_iterator it0_;
    List::const_iterator it1_;
    List::const_iterator end_;

    std::tuple<const float2&, const float2&> read() const {
        return {*it0_, *it1_};
    }

    bool equal(ranges::default_sentinel_t) const {
        return it1_ == end_;
    }

    void next() {
        ++it0_;
        ++it1_;
    }
};
    
}

int main()
{
    const std::vector<float2> positions = {{
        {1.0f, 2.0f},
        {3.0f, 4.0f},
        {5.0f, 6.0f},
    }};

    for(const auto& [p0, p1] : slice_windows2_view(positions)) {
        std::cout << "(" << p0.x << ", " << p0.y << ")" << std::endl;
        std::cout << "(" << p1.x << ", " << p1.y << ")" << std::endl;
        std::cout << std::endl;
    }
}

現在の妥協案

namespace{
    struct float2 {
        float x;
        float y;
    };
}

int main()
{
    const std::vector<float2> positions = {{
        {1.0f, 2.0f},
        {3.0f, 4.0f},
        {5.0f, 6.0f},
    }};
    assert(!positions.empty());

    const auto slice_windows2 = [](const auto& list) {
        std::vector<std::pair<float2, float2>> result;
        for(size_t i = 0; i < list.size() - 1; i++) {
            result.emplace_back(std::make_pair(list[i], list[i + 1]));
        }
        return result;
    };
    
    for(const auto& [p0, p1] : slice_windows2(positions)) {
        std::cout << "(" << p0.x << ", " << p0.y << ")" << std::endl;
        std::cout << "(" << p1.x << ", " << p1.y << ")" << std::endl;
        std::cout << std::endl;
    }
}
(1, 2)
(3, 4)

(3, 4)
(5, 6)

view_adaptorの実装お試しだけ上手くいかなかった。

my_view:::view_adaptor{std::move(rng)}

がダメと言われる。参考にしている実装が違うのか?

何かを深く学ぶには参考書やカンファレンスよりもブログの方が良い

私の友人がセキュリティスペシャリストに一発合格していたので、どうやって学習したか聞いてびっくりしたことがあった。

友人は「セキュリティに詳しい人のブログを頻繁に読んでいた」と答えたのだ。
参考書でも過去問でもなくブログが一番自身の助けになったと彼は考えたという事実に驚いた。

しかし、考えてみると納得のいく部分は多い。
私が人よりも少しばかりC++に詳しくなったのもC++の規格に詳しい人のブログを愛読していたためだった。

思うに、ブログにはどうでも良い情報が含まれているからこそ大事なのだと思う。
C++の草案や提唱された経緯を知ることはC++のプログラミングには役に立たないかもしれない。
しかし、C++の向かっている方向を知ることは現代の良いコードに近づく良いヒントになる。

variant, visit, concept等を見ても継承以外のアプローチを求めているには明らかだし、autoを積極的に使った方が移植性が高くなる機能追加が多いことも分かる。
継承を使っているコードやautoを禁止しているプロジェクトが扱いづらくなっていくということが簡単に読み取れる。

「愚者は経験に学び、賢者は歴史に学ぶ」という言葉で表現されるように今一度歴史を追ってみると楽しいと思う。
機能紹介の参考書には継承を使うのはやめましょうとは書いてないし、新しい機能が解決する問題についても触れていないことが多い。
新機能は開発者に「更新に追従するの大変だよ」と言わせるために追加されるわけではないのだ。

コードを綺麗に書くとはスコープを最小にすること

コードを綺麗に書こうという話は聞けどもそれが何を意味するのかは分からないものだ。
人によって言っていることが違ったりもする。
汚いコードの特徴はいくらでも挙げることができるが綺麗なコードの特徴は分かりにくい。

私はスコープ、生存期間を最小にすることが綺麗にコードを書くということだと思う。

変数名もスコープが広いから複数の概念が衝突するので複雑な名前を付けざるを得なくなる。

実際の問題は複雑で複数の事柄が絡み合って簡単には変数の生存期間を短くさせてくれない。
しかし、少しでも変数の生存期間を短くしようという努力が綺麗なコードを生むのだ。

ソースコードのコメントよりも作業ログの方がコスパが良い

個人的にはソースコードのコメントに残すに堪えない情報こそが大事だったりすると思うので、ソースコードのコメントよりはissue等の別ドキュメントに作業ログを残すことが大事なように思われる。
例えばAしてもダメでBしても駄目だったからCしたらコード汚くなったけどなんかいけた。といった情報はソースコードのコメントには書きづらいが結構有益な情報になったりする。
ソースコードのコメントにURLを貼るのは賛否あるがソースコードと連携した作業メモが良さそうに思う。

Aの仕様とBの仕様の接続性悪くてビビる。

とかの愚痴みたいな10秒で書いたコメントも中々有益でコスパが良い。