Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JSSST2021-Siv3D

Ryo Suzuki
September 03, 2021

 JSSST2021-Siv3D

日本ソフトウェア科学会 第 38 回大会 / 情報可視化やインタラクションのためのライブラリ Siv3D の機能強化と C++17, C++20 への対応

Ryo Suzuki

September 03, 2021
Tweet

More Decks by Ryo Suzuki

Other Decks in Programming

Transcript

  1. 情報可視化やインタラクションの ためのライブラリ Siv3D の機能強化と C++17, C++20 への対応 鈴木 遼 (Siv3D

    開発者) 上田 和紀 坂井 滋和 早稲田大学 C++ ライブラリ「Siv3D」における API 設計の工夫, 最新 C++ 仕様への対応, 普及や発展の知見を紹介 JSSST2021 2021-09-03 jssst2021.wordpress.com
  2. 1.1 Siv3D の概要 1/4 • 可視化やインタラクションに関わるプログラムを、 短く簡単な C++ コードで記述できるフレームワーク •

    MIT ライセンス、直近 1 年間の SDK ダウンロード数は約 1 万回 • 動作プラットフォーム: Windows / macOS / Linux / Web • 2011 年に初公開、2016 年にオープンソース化、現在もアクティブに開発 2 github.com/Siv3D/OpenSiv3D
  3. 1.2 Siv3D の特徴 1/3 非常に短いコード • サンプル動画はいずれも 30~200 行未満の C++

    ソースコード 最新の C++ を学べる • C++17, C++20 を積極的に活用 豊富な機能を一貫した API で提供 • 2,200 ファイル (20 万 LoC) のソースコード + 90 のサードパーティ OSS (数万ファイル) を統合 6 Abseil, AngelScript, Boost, Box2D, Catch2, cereal, DirectX Math, fmt, Font Awesome, FreeType, GLEF, GLFW, GSL, HarfBuzz, libcurl, libjpeg-turbo, libogg, libpng, libtiff, libvorbis, libwebp, Lua, M+ Fonts, Material Design Icons, msdfgen, muparser, nanoflann, nlohmann/json, Noto CJK Font, Noto Emoji Font, Oniguruma, OpenCV, Opus, pffft, Quirc, Recast & Detour, SFMT, simde, Sol2, SoLoud, SoundTouch, stb_truetype, tinyobjloader, xxHash, zlib, Zstandard ほか 44 (OpenSiv3D GitHub リポジトリ ThirdParty.md に一覧記載) CEDEC 2020 講演スライド
  4. 1.2 Siv3D の特徴 2/3 オープンソース • 2016 年のオープンソース化以降、5,000 コミット 以上のコード追加・変更

    • コミッタとして26 人が開発に参加 軽量・高速 • Windows 版の SDK のインストーラのサイズは 120 MB 未満 • C++ / SIMD による高速な実行コード • GPU によるレンダリング (Direct3D 11 / OpenGL 4.1 / WebGL 2.0) ユーザコミュニティ • 500 人以上が参加する Slack コミュニティ • 定期的なユーザミーティング 7
  5. 1.2 Siv3D の特徴 3/3 • 標準的な C++ で書かれているが、ユーザはほとんどのプログラムを、 Siv3D が提供する便利な型や関数を使って記述

    • 描画やインタラクションのための DSL 風 (Java にとっての Processing) 8 # include <Siv3D.hpp> void Main() { Scene::SetBackground(ColorF{ 0.8, 0.9, 1.0 }); const Texture food{ U"🍿"_emoji }; const Texture chick{ U"🐥"_emoji }; while (System::Update()) { Circle{ Scene::Center(), 100 }.draw(); food.drawAt(Scene::Center()); chick.drawAt(Cursor::Pos()); } }
  6. 1.3 発表内容について Siv3D で活用実績のある API 設計技法、開発生産性の向上、ユーザコミュニ ティの継続に関する知見を (C++) ライブラリ開発者と共有 2.

    最新の C++ 仕様 (C++17, C++20) の活用事例、使い勝手の良いユーザ コードを実現する C++ API 設計の工夫 3. ライブラリ普及・発展のための取り組み、ユーザコミュニティの運営 4. 使用した C++ 開発ツールの紹介 5. Siv3D の今後の課題、C++ への要望、こんなツールがほしいという提言 9 2. C++ ライブラリ設計 3. ユーザコミュニティの運営 4. 開発ツール 5. 今後の課題と, C++やツールへの要望 6. おわりに
  7. 1.4 関連記事 • API Design for C++ (2011) • Game

    Engine Architecture Third Edition (2018) • Game Programming Patterns (2014) • C++ Core Guidelines (~現在) 10 https://www.sbcr.jp/product/4797369151/ https://www.borndigital.co.jp/book/19115.html https://book.impress.co.jp/books/1114101121 https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
  8. 1. はじめに 2. C++ ライブラリ設計 3. ユーザコミュニティの運営 4. 開発ツール 5.

    今後の課題と, C++やツールへの要望 6. おわりに 柔軟な形状定義のためにコンストラクタをどう増やした? 2.1 C++ コードの表現力をどう向上した? 2.2 C++17, C++20, どのような機能が便利だった? 2.3 たくさんのサブシステム、どう管理してる? 2.4 グラフィックスプログラミングに RAII が便利 2.5 ヘッダファイルを良いドキュメントにするために何をした? 2.6 11
  9. 2.1 コンストラクタオーバーロード 1/3 • 一般に、図形を扱う C++ ライブラリは、 図形をいくつかのクラスに分類 • それぞれのコンストラクタについて、関

    数オーバーロードによって、目的の形状 を最小限の入力から定義する方法を提供 • 基準座標(左上、中心、...)をどう区別? • 静的メンバ関数 Rect::FromCenter(...); • tag dispatch Rect{ Center{}, ... }; • enum class Rect{ Rect::Center, ... }; • 新しい型を作る Rect{ Center{ ... }, ... }; 12 struct Rect { int x, y, w, h; // 左上が (0,0), サイズが size explicit Rect(int size); // 左上が (0,0), サイズが w, h Rect(int w, int h); // 左上が x, y, サイズが size Rect(int x, int y, int size); // 左上が x, y, サイズが w, h Rect(int x, int y, int w, int h); // Point 型の値から構築 Rect(Point pos, int size); // Point 型の値から構築 Rect(Point pos, int w, int h);
  10. 2.1 コンストラクタオーバーロード 2/3 • Siv3D では、C++ に本来無い名前付き引数をエミュレートするクラスを実装 • ゼロオーバーヘッド、最小限のユーザコード増で、 コンストラクタのオーバーロードを増やす

    13 void Main() { // 左上が (0, 0), サイズが 200 Rect r1{ 0, 0, 200 }; // 中心が (0, 0), サイズが 80x40 Rect r2{ Arg::center = Point{ 0, 0 }, 80, 40 }; // 左下が (0, 0), サイズが 80x40 Rect r3{ Arg::bottomLeft = Point{ 0, 0 }, 80, 40 }; // 中心が (10, 20),サイズが 80x40 Rect r4{ Arg::center(10, 20), 80, 40 }; 実装: github.com/Reputeless/NamedParameter
  11. 2.1 コンストラクタオーバーロード 3/3 • Siv3D の線分クラス Line, コンストラクタが充実している例 14 Vec2

    pos1{ 1, 2 }, pos2{ 3, 4 }, dir{ 5, 6 }; // pos1 と pos2 を結ぶ線分 Line a{ pos1, pos2 }; // pos1 と (pos1 + dir) を結ぶ線分 Line b{ pos1, Arg::direction(dir) }; // pos1 と (10, 20) を結ぶ線分 Line c{ pos1, 10, 20 }; // pos1 と、pos1 から 45°方向に 10 移動した点を結ぶ線分 Line d{ pos1, Arg::angle = 45_deg, 10 };
  12. 2.2 コードの表現力の向上 1/3 以前の Siv3D の書き方 新しい Siv3D の書き方 ①

    ユーザ定義リテラル(サフィックス)を積極的に活用 ② boolean literal を名前付きの値に置き換える 「YesNo クラステンプレート」を実装・活用 15 Timer timer{ 15, true }; Timer timer{ 15s, StartImmediately::Yes };
  13. 2.2 コードの表現力の向上 2/3 • ユーザ定義リテラル: 指定したサフィックスの付いた数値・文字列リテラルに ついて、その値を引数に関数を呼ぶ、C++ における糖衣構文 Siv3D での例:

    16 値 戻り値の型 説明 10s Seconds 秒 90_deg double 度数法の角度をラジアン角に変換 std::sin(90_deg) 2_pi double n * 円周率を計算 0xf030_icon Icon 画像としてロードするアイコンセットの ID U"🐈"_emoji Emoji 画像をロードする際、絵文字としてロード (ただの文字列だと、画像ファイルのパスと見なす) U"{}"_fmt(n) FormatHelper Python スタイルの文字列フォーマットの実行
  14. 2.2 コードの表現力の向上 3/4 • YesNo クラステンプレ―ト • enum class のように強い型付けを持ちつつ、

    bool 型であるかのように扱える値を 1 行で 簡単に作れる 17 template <class Tag> struct YesNo { struct Helper { bool yesNo; }; explicit constexpr YesNo(bool yesNo) noexcept : m_yesNo{ yesNo } {} constexpr YesNo(Helper helper) noexcept : m_yesNo{ helper.yesNo } {} // contextually converted to bool // の文脈で bool 型の値としてふるまう explicit constexpr operator bool() const noexcept { return m_yesNo; } // それ以外の文脈ではこの関数を使う constexpr bool getBool() const noexcept { return m_yesNo; } // 〇〇::Yes と記述できるように static constexpr Helper Yes{ true }; // 〇〇::No と記述できるように static constexpr Helper No{ false }; private: bool m_yesNo; }; using FormatDrive = YesNo<struct FormatDrive_tag>; using EnableLog = YesNo<struct EnableLog_tag>; void G(bool) {} void F(FormatDrive f, EnableLog e) { if (e) { } G(f.getBool()); } int main() { F(FormatDrive::No, EnableLog::Yes); F(EnableLog::Yes, FormatDrive::No); // エラー }
  15. 2.2 コードの表現力の向上 4/4 • Siv3D では 33 種類の YesNo クラス特殊化が使われている

    18 YesNo クラス特殊化 用途 StartImmediately 即座に開始するか Lossless ロスレス圧縮にするか AllowFlip 回転を許可するか SkipValidation バリデーションをスキップするか Recursive 再帰的に処理するか AllowUndo やり直しを許可するか Loop ループするか Timer timer{ 15s, StartImmediately::Yes };
  16. 2.3 C++17, C++20 規格の活用 1/2 コンストラクタへの [[nodiscard]] (戻り値を無視してはいけない属性) • 一時オブジェクト作成

    → メンバ関数による操作 (Siv3D で頻出のパターン) • 未使用変数とは異なり、各種 C++ コンパイラで警告を出せなかった • 上記のようなコードに警告を出し、間違いを容易に発見できるように 19 void Main() { // 無意味だが警告されなかった Rect{ 100, 50, 40, 30 }; Rect{ 100, 50, 40, 30 }.draw(); }
  17. 2.3 C++17, C++20 規格の活用 2/2 Designated Initializer (集成体に対する、メンバ変数名を指定した初期化) • メンバ変数の多い構造体の初期化

    20 BoundingBox A() { return{ 0.1, 0.2, 0.3, 0.4 }; } BoundingBox B() { BoundingBox b; b.xMin = 0.1; b.yMin = 0.2; b.xMax = 0.3; b.yMax = 0.4; return b; } BoundingBox C() { return{ .xMin = 0.1, .yMin = 0.2, .xMax = 0.3, .yMax = 0.4 }; }
  18. 2.4 サブシステムの初期化と終了処理 (要約) • Siv3D のようなフレームワークは、各サブシステムをシングルトンクラスと する設計が一般的 • サブシステムには依存関係があり、 正しい順番で初期化と終了処理が行われる必要があり、普通の

    static は NG • Siv3D は std::tuple を使って、新しいサブシステム追加時の変更が 小さくなるような管理方法を提供 • サブシステムの初期化と終了処理のタイミングを短いコードで一括して制御 • Siv3D のようなフレームワーク開発が便利になる技法 21
  19. 2.5 RAII 活用のレンダーステート管理 1/2 • RAII: コンストラクタでリソース確保、デストラクタでリソース解放という、 C++ の基本的なイディオム •

    管理対象をリソースからステートに拡張し、グラフィックスプログラミング における、シェーダの適用、レンダーステート変更、座標変換行列スタック の操作に応用したクラスを提供 • 状態の明示的なリセットが不要になり、コードが短縮、見通しよく 22 Siv3D のクラス 説明 ScopedRenderStates2D スコープが有効な間、コンストラクタに渡したレンダーステートを適用 Transformer2D スコープが有効な間、渡した行列を現在の座標変換行列に乗算 ScopedCustomShader2D スコープが有効な間、渡したシェーダプログラムを描画に使用 ScopedRenderTarget2D スコープが有効な間、渡したレンダーテクスチャを描画対象とする
  20. 2.5 RAII 活用のレンダーステート管理 2/2 23 void A() { ScopedRenderStates2D s{

    BlendState::Additive, SamplerState::ClampNearest }; Draw(); } // s のデストラクタが // レンダーステートをリセット void B() { A(); // ここでは A() の中での // レンダーステート変更の影響を受けない Draw(); } void C() { // スケールを push Transformer2D t1{ Mat3x2::Scale(2.0) }; // 2 倍のスケールで描画される Draw(); { // スケールを push Transformer2D t2{ Mat3x2::Scale(1.5) }; // 3 倍のスケールで描画される Draw(); } // t2 のデストラクタがスケールを pop // 2 倍のスケールで描画される Draw(); } // t1 のデストラクタがスケールを pop
  21. 2.6 ヘッダファイルのドキュメント性向上 • .hpp / .cpp の構成は、ヘッダ内のインライン関数の記述が一覧性を損ねる • インライン関数や詳細実装を記述するヘッダファイル .ipp

    を追加し、 .hpp ファイルのコード記述を最小化、ドキュメント密度を高める構成に • constexpr な Hidden friends 関数の本体は .ipp に記述できない制約が残る 24 /// @brief ドキュメント class A { public: /// @brief ドキュメント A() = default; /// @brief ドキュメント constexpr A(int a, int b); ... #include "detail/A.ipp" constexpr A::A( const int a, const int b) : m_data{ a * b } { } constexpr int A::getData() const noexcept { return m_data; } #include "A.hpp" void A::update() { // ... } std::ostream& operator << (std::ostream& os, const A& a) { return os << a.getData(); }
  22. 1. はじめに 2. C++ ライブラリ設計 3. ユーザコミュニティの運営 4. 開発ツール 5.

    今後の課題と, C++やツールへの要望 6. おわりに 25 ユーザコミュニティ拡大のために何をした? 3.1 コミュニティ運営に役立ったツールやサービスは? 3.2
  23. 3.1 ユーザコミュニティ運営の目的 Siv3D の目的: 「ゲームやメディアアートを楽しく簡単に開発したい」 • 多数のユーザによって、様々な実行環境、ツール、デバイス、技術との組み 合わせが検証される → 機能の強化

    (例: Siv3D Linux 版、Web 版はユーザ発) • 増加するユーザの質問やトラブルに対応できる熟練ユーザを育成する → サポートの負担の抑制 • Siv3D という共通の言語を多くの人が共有することで、ゲーム・アプリ開発、 プログラミング技術の情報交換が円滑になり、個々人の学習、コミュニティ、 文化の発展が促進 → 開発体験の向上 26
  24. 3.1 ユーザコミュニティ運営 1/3 • Siv3D のユーザコミュニティの運営、拡大のための 3 つの施策 (勉強会、実装会、チャレンジ) ①

    勉強会 • Siv3D の開発環境のセットアップ方法やチュートリアルを解説 • 数名~数十名が参加、 2~6 時間、年数回開催 • 地方各地で開催する際は、現地の大学のサークルやプログラミング教室など、 ローカルのコミュニティと協力 • (最近はオンライン) 27
  25. 3.1 ユーザコミュニティ運営 2/3 ② 実装会 • コードレビューなど技術的なメンタリング • 数名~数十名が参加、4~6 時間、毎月開催

    • コアユーザおよびコミッタが主な参加者 • 参加人数を絞る代わりに一人あたりに 多くの時間を割き、高度な使い方の講習を行う • 実装会参加後にコミッタとなったメンバーが 18 名 • (最近はオンライン) 28 2019 年の実装会
  26. 3.1 ユーザコミュニティ運営 3/3 ③ チャレンジ • Siv3D に新たに実装したい機能のリストを提示し、ユーザコミュニティの有 志に解決してもらうイベント。Google Summer

    of Code に着想 • 年 1 回、数か月にわたりオンラインで開催。累計約 30 名が参加 • 同じ課題を、複数の参加者が協力・競争して取り組むことも • 技術的なサポートやコードレビューを頻繁に提供することでサポート • Siv3D のイベントを通して初めて OSS にコミットする中学生や高校生も 29 • GeoJSON パーサ • 結果のポリゴン化 お題の例: 世界地図や日本地図を表示
  27. 3.2 コミュニティ運営を支えるサービス Slack • メッセージやファイルをやり取りできるグループウェア Discord • ビデオ通話サービス。オンライン勉強会、実装会の開催プラットフォーム Whereby •

    Web ブラウザ上で簡易に使えるビデオ通話サービス。複数人同時の画面共有 機能を持つ。1~2 人との小規模な会議に使用 GitHub Sponsors • GitHub 上で OSS 開発者が金銭的な支援(寄付)を受け取れるサービス • Siv3D は受け付け開始から 2 年で 20 万円超が集まる 30
  28. 1. はじめに 2. C++ ライブラリ設計 3. ユーザコミュニティの運営 4. 開発ツール 5.

    今後の課題と, C++やツールへの要望 6. おわりに 31 Siv3D ではどのような C++ 開発ツールを使った? 4.1 Siv3D の C++ プログラムの Web 移植、どんな感じ? 4.2
  29. 4.1 Siv3D を支える C++ 開発ツール 1/2 Wandbox • 数十種類以上の C++

    コンパイラによるコンパイルと、コードの実行結果を 確認できるオンラインコンパイラ Compiler Explorer • 様々なバージョンのコンパイラやコンパイルオプション設定で C++ コード をコンパイルし、そのアセンブリ出力を確認できるオンラインコンパイラ Quick C++ Benchmark • 複数の異なるコードの実行時間を相対的に比較するベンチマーク機能を備え たオンラインコンパイラ。バックエンドは Google Benchmark 32
  30. 4.1 Siv3D を支える C++ 開発ツール 2/2 PVS-Studio • C++ や

    C#, Java を対象とした静的コード解析ツール • 実行時性能に影響するコード、冗長なコード、 タイプミス、バグが疑われる箇所等を検出 33 m_fonts.add(std::move(font), U"({0} {1} size: {2})"_fmt( font->getFamilyName(), font->getStyleName(), fontSize)); const double minX = std::min({bezier.p0.x, bezier.p1.x, bezier.p2.x}); const double maxX = std::max({bezier.p0.x, bezier.p1.x, bezier.p2.x}); const double minY = std::min({bezier.p0.x, bezier.p1.y, bezier.p2.y}); const double maxY = std::max({bezier.p0.y, bezier.p1.y, bezier.p2.y});
  31. 4.2 Web ブラウザ動作対応 • Siv3D の C++ コードを,WebAssembly / JavaScript

    に変換する ツールチェーンを、Emscripten 等を利用して整備 • OS 依存の API を使用していないなど、条件が良い場合、 既存の C++ コードを 2~3 行変更するだけで Web ブラウザ対応 • Chrome / Edge / Firefox / Safari (preview) で動作 • ライブラリの対応作業: • レンダリングエンジン: OpenGL 4 の既存実装をベースに WebGL 2.0 移植 • 70 のソースファイルの追加。全体 (2,200 ファイル) からは少ない割合 34
  32. 1. はじめに 2. C++ ライブラリ設計 3. ユーザコミュニティの運営 4. 開発ツール 5.

    今後の課題と, C++やツールへの要望 6. おわりに 35 Siv3D, 今後は何が課題? 5.1 C++ にどういう改善があると嬉しい? 5.2 フレームワーク開発、どういうツールがあると嬉しい? 5.3
  33. 5.1 Siv3D の今後の目標・課題 ① Web 版のサイズ圧縮 • ページアクセスから実行まで 5~15 秒。ファイルのダウンロードが律速

    • 機能を削り、ファイルサイズをコンパクトにするビルドオプション • 単一データから複数のウェイトや属性を生成できる variable fonts の使用 ② サブセットライブラリ • 現在は、提供される機能の一部だけを独立に抜き出して再利用することが困難 (Siv3D が管理するサブシステム群に依存するため) • スタンドアローンなサブセットライブラリを提供し、活用機会を増やす • その部分の改良やテストが加速し、本体に反映される効果を期待 36
  34. 5.2 C++ への要望 1/2 ① 移植性のある乱数ユーティリティの実装 • 標準ライブラリの乱数分布 (distribution) や要素のシャッフルに使われる

    細かいアルゴリズムが規定されていない • 乱数生成エンジンの出力は一貫している一方で、 分布関数の実装差異が移植性を損ねている • Siv3D では共通の実装を用意 37 int main() { std::mt19937 rng{ 12345 }; std::uniform_int_distribution<int> ud{ 1, 6 }; std::cout << ud(rng) << '¥n'; // 実装により結果が異なる }
  35. 5.2 C++ への要望 2/2 ② constexpr 数学関数 • ベクトル正規化の計算で使われる平方根関数 std::sqrt()

    は constexpr でないため、次のコードはコンパイルできない ③ 標準ライブラリ関数の char16_t / char32_t サポート • wchar_t 型は処理系によってサイズが異なるため、Siv3D では char32_t 型を採用(UTF-8 よりも日本語や絵文字をユーザコードで処理しやすい) • 正規表現、std::format, 標準入出力など、C++ 標準ライブラリの文字列処 理関数のテンプレート引数に chart16_t / char32_t 型を指定できない 38 constexpr Vec3 v = Vec3{ 1.0, 1.0, 1.0 }.normalized();
  36. 5.3 開発・整備が望まれるツールの提言 1/2 ① テスト用のファイルコレクション • 各種ファイル形式の読み込みの検証に使える,あらゆる種類・内部形式の テストファイル(画像、オーディオ、動画、構造化テキスト) ② 軽量なカラー絵文字フォント

    • Siv3D は共通のカラー絵文字フォントファイルをアプリケーションに同梱 • 約 3,600 文字収録、ファイルサイズは 8.6MB(Zstandard 圧縮) • 構成ファイル群の中で最大の容量 • Unicode Emoji は例年 100~300 文字前後のペースで追加 • 高圧縮率のベクター画像形式や、ファイルサイズを優先してデザインされた 絵文字フォントの登場を期待 39
  37. 5.3 開発・整備が望まれるツールの提言 2/2 ③ 言い換え表現に強いドキュメント検索 • ユーザがドキュメントやライブラリヘッダ、Web で使い方を検索する際、 言い換え表現がヒットせず、別の用語を使っていれば得られていたはずの 情報にたどり着けないことがある

    • 言い換え表現を検索時のヒントとして登録し、検索結果に反映してくれる ドキュメンテーションジェネレータや検索エンジンがあると便利 40 用語 言い換え例 描画 表示、描く、出す、出力 音声 オーディオ、サウンド、音楽、 効果音、BGM, SE 透過 半透明、透ける 用語 言い換え例 座標 位置、場所、地点 動画 ビデオ、ムービー テキスト 文字列、文章、メッセージ フレームレート FPS
  38. 6. おわりに • Siv3D の実例を通して、C++ ライブラリ API 設計の技法と、 ライブラリの開発や普及、ユーザコミュニティの運営に関する知見を共有 •

    C++ の言語の進化は加速、 C++14 から C++20 にかけて規格書のページは 495 ページ増 • C++ ライブラリ開発者がユーザの期待や言語の要求水準に応えていくために、 API 設計技法や開発生産性向上に関する議論、知見の共有がますます必要に 41 github.com/Siv3D/OpenSiv3D