ぷろみん

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

喜びは映像にしないと半分も伝わらない

2015年のジャーナルですが面白かったので紹介します。

音声に含まれる感情の認識(pdf)

日常的な事柄を例に使っており非常に読みやすいので、直接読む方が良いかもしれません。
特に興味深かった箇所を引用します。

「悲 しみ」の よ うに 音声 だ け で あっ て も 60%程 度伝 わ る もの が あ る 一方 ,「喜 び」で は ,映像等すべ て 見 れ ば 69%程 度伝 わ る の に, 音声 だ け で は 32% しか伝わ らない

私達が小さな小窓であっても顔映像を好むのは、この37%の喜びを理解できるからかもしれませんね。

MSVCでも頑張ってC++20で導入されるspanを使う

wandboxではspanを利用できるので動作確認だけならそちらが簡単です。

何故人はstringを使わないのか

stringは内部バッファに文字列を蓄えるので例えば

std::vector<std::string> Split(const std::string& text);

みたいな関数があった時に入力のテキストと出力の分割文字列で重複があるので無駄が多いと感じます。
そこで、区間を表す構造体が欲しくなります。

span

struct Span{
  char* pointer;
  size_t size;
};
std::vector<Span> Split(const std::string& text);

これで最小限の情報だけで関数を表現できたが、区間を入手したらその区間をイテレートしたいと思うのは自然なことでしょう。
分割したいアイテムが文字列じゃない場合を想定してテンプレートでSpanを書きたくなるかもしれません。
自分で書くのが億劫になってきました。

ちなみに文字列用のspanとして専用の<string_view>が存在するので、本来ならこっちを使うべきです。
MSVCの最新版では現在でも利用することができます。
string_viewをstring_spanにする案もあったそうですが、通らなかったんですかね。
string_viewはstringとほぼ同じ動作をし、無駄なnull終端を管理しません。
文字列を扱う場合はstring_viewを使いましょう。

#include <iostream>
#include <span>
#include <string_view>

template<class T>
void Print(T&& t){
    std::cout << t.size();
    for(auto i : t){
        std::cout << ",[" << i << "]";
    }
    std::cout << std::endl;
}

int main(){
    const char text[] = "ab";
    Print(std::span(text));
    Print(std::string_view(text));
}

//出力
//3,[a],[b],[]
//2,[a],[b]

utf8

更に横にそれますが、C++20になってchar8_tというutf8用の型が提供されるようになったので実際に使われる文字列はstd::u8string_viewが多くなるでしょう。

spanの使い道

コンテナの一部区間を出力したり、バイナリのチャンク分けの際に活躍します。
これらは上記と同じ問題が発生します。そこでspanの出番です。

ただ、概念を考えれば分かる通りspanやviewのスコープは参照元よりも短い必要があります。

現在のMSVCの最新版でもまだspanは導入されていません。C++20で入ることを考えると待っていたらリリースされそうではありますが、今欲しいとなると中々面倒です。

spanの導入

spanはCppCoreGuidelinesで提案されている概念の実装であるGSL (Guidelines Support Library)に含まれています。
今回はその中でもMicrosoftによる実装であるms-gslを利用します。

$ git init
$ git submodule add https://github.com/microsoft/vcpkg
$ cd vcpkg
$ ./bootstrap-vcpkg.bat
$ ./vcpkg.exe install ms-gsl
$ cd ..
$ touch main.cpp
$ touch CMakeLists.txt
$ cmake . -DCMAKE_TOOLCHAIN_FILE=vcpkg/scripts/buildsystems/vcpkg.cmake
$ cmake --build .

cmakeがinstall済みではない場合vcpkgが使っているものが以下にあります。
vcpkg\downloads\tools\cmake-3.14.0-windows\cmake-3.14.0-win32-x86\bin\cmake.exe

// main.cpp
#include <gsl/span>
// 上記と重複部分省略

int main() {
  Print(gsl::make_span("ab"));
}
# CMakeLists.txt
cmake_minimum_required(VERSION 3.1)

find_path(include_root_dir NAMES gsl/span)
include_directories(${include_root_dir})

add_executable(main main.cpp)

Texture Atlasの生成

Texture Atlas

1回のレンダリングで利用できるテクスチャの枚数は限られているので、複数のテクスチャを1枚のテクスチャにまとめることは非常に重要です。
この色々なテクスチャをまとめたテクスチャをTexture Atlasと呼びます。
今回はこれの作り方をいくつかメモしておきます。

Knapsack problem

入れ物に可能な限り多くのテクスチャを格納するという観点から考えるとナップサック問題として取り扱えるように思えます。
更にMulti-dimensional knapsack problemの項目を読むと2Dナップサック問題の解法としてIHS (Increasing Height Shelf) algorithmというものが存在しているらしいです。

Increasing Height Shelf

まだ論文をちらっと眺めただけですが、最大の高さのアイテムに合わせたラインにそって並べる方法のように見えます。
下記で実装されている方法がそのような挙動っぽいので下記アルゴリズムとの違いを調査したいところです。

下が揃っているラインを複数段作っていくからShelfなんですね。面白いネーミング。

Skyline Bottom-Left algorithm

https://github.com/nothings/stb/blob/master/stb_rect_pack.h
上記ではSkyline Bottom-Left algorithmを利用しているそうです。

英語の専門用語

概要

いざ学問としての英語を勉強しようとしても、日本語だと受験勉強や資格、PV目当てのブログみたいなのばかり出てきて効率が悪いです。
そこで、学問としての英語で使われている専門用語が出発地点とならないかなと列挙してみました。

Mood

日本語では法と訳されています。文法を学んでいる時に~法とか言われても法則にしか見えないので、ややこしい訳語を当ててしまったものですね。
仮定法(Subjunctive mode)や法助動詞(Modal auxiliary)等の日本語が割り当てられています。

Grammatical mood - Wikipedia

直接法(Realis mood)非直接法(Irrealis mood)が対比されています。
非直説法は対訳が見つからなかったので私が暫定的に付けたものです。
直説法が事実だと表明する際に使うのに対して非直説法は実現していない事柄を表明します。
学びたてでまだまだ確かなことは言えませんが、英語には実現した事柄と実現していない事柄を明白に分ける表現が重視されているように感じます。

このMoodが文章の発話者、記述者の気持ちを表現するものということを知ると同じ対訳が与えられている英語表現にも違いが見えてきます。
法助動詞(may, shall, must, will)はwikipediaを読む限りでは複合動詞として機能しているようなので動詞に主観を導入していると考えられます。
実際、関さん関連の書籍でもmustを客観的状況で使う場合はhave toを、willを客観的状況で使う場合はbe going toを、wouldを客観的状況で使う場合はused toを使うと書かれています。

上記概念が面白いのは文章に全く関係ない第三者がひっそりと登場していることです。
例えばHe must work.という文章は彼が働くべきだと考えている人によって表現されたものであり、根拠がなくても使うことができます。 一方、He has to work.とした場合には暗黙的にしろ明示的にしろ客観的な根拠を元に働くべきだと表明していることになります。これは文章の表現者は彼が働かなくても良いと思っているが気持ちを隠して表現した場合等にも該当します。
日本語だと「~すべき」という表現だけでは主観なのか客観なのか分からないので不思議な感じです。 例えば「彼と同じ境遇でも働いている人がいるので、彼も働くべきだ。」という日本語は根拠も付いているので客観的なように見えて実は感情的で主観的な文章です。 日本語でhave toを表現するには「彼と同じ境遇でも働いている人はいるので、彼も働くべきだという意見も出ている。」となるでしょうか。 反対に主観だと明示する場合には「彼も働くべきだと私は考えている。」とかですかね。 こうして考えると日本語は基本は主観で表現されているのかもしれません。そうだとすると、wikipediaや本での参考文献への参照が英語のものと比べると遥かに少ないことにも納得できます。 主観の場合根拠の提示義務がないですから。

end-focus

新情報、旧情報、そしてend-focusです。 英語では新しい情報、強調したい情報を最後にもってくることで特定の文脈を強調するようです。
文章の先頭に新情報を持ってくることを嫌うので仮主語としてThere等を用いることがあります。
また、受け身も目的語が旧情報の場合それを主語とすることで新情報を後ろに持ってくることを目的として作られる場合もあるようです。
同じ意味の文章が多数ある場合が多いですが、それぞれ強調する場所が異なってくると考えれば良さそうです。

Strong form / Weak form

英語の発音には強形 (Strong form)と弱形 (Weak form)という2種類の発音があるようです。
通常の会話では弱形が使われるので省略した感じの発音に聞こえるようです。

有声化

母音に挟まれた[t]は有声化して[d]と発音するという現象らしいのですが、簡単に調べただけでは有力な情報を得られませんでした。

変種

variety(変種)は文法規則の亜種を指します。例えば3単現に -s を付けない規則等が変種としてありました。

語彙拡散

語彙拡散(lexical diffusion)Wikipedia にもあるように、ある語彙の発音が他の語彙に伝搬していくという現象。最終的に全ての同型の語彙に伝搬するという説と、全部には伝搬しないという説があるらしいです。

該当する専門用語が見つからなかったもの

英語ではまだ起きていないことには原形を使うという考えがあると関さん関連書籍に書いてあったのですが、該当する概念の専門用語は見つけられませんでした。

to不定詞は未来志向で、動名詞は反復のイメージという記述は散見されるが該当する専門用語は見つけられませんでした。
未来志向ということはまだ実現していない、そういうイメージが持てると面白いですね。

その他面白そうな読み物

上記の単語を調べている内に出会った良さそうな情報源。

英語音韻論
英語の発音に関する情報が良くまとまっています。かなり専門的で難しく感じます。

hellog〜英語史ブログ
英語の歴史が書かれています。

英語研究教室 | 美誠社(英語教育図書出版)
英語の文法に合致しているかを学問的に検証するということを行っていて面白いです。

文字がレンダリングされるまで

概要

utf8が普及した昨今でも文字のレンダリングは全然簡単になりません。
その理由を日本語のレンダリングを通して学びましょう。

今回は文字列自体の複雑さにはあまり触れません。
サロゲートペアとかで検索すれば良い解説記事を見つけることができるかと思います。

日本語

日本語のレンダリングを目標とした時に、まず日本語を定義する必要があります。
定義がなければ、日本語文字の画像を全部集めることができません。

十分なプリレンダリング空間が確保できる場合はISO/IEC 8859を選択すれば良いと思います。
それ以外の選択肢はDirectX9を使う必要があり2048x2048のテクスチャしか使えない等の環境要因か、 各ロケールへの最適化等の場合に利用が考えられます。

以下に候補が列挙されています。 http://www.unicode.org/Public/MAPPINGS/

JIS X 0208

一番基礎的な文字セットです。
日本語に対応したと言うには十分ですが、~等の扱いが難しい記号、半角カタカナ、ASCIIを含めない等状況によって加工する必要があるセットです。
最低限の実装には便利ですがunicode.orgからはOBSOLETE扱いされています。

Unicodeの中のJIS X 0208に当たる文字を取得 - 強火で進め

コードページ932

マイクロソフトが使っている基準だから多分大丈夫だろうという選択です。
ただ、Windowsしか考慮していないのでutf16を扱う必要があり嬉しくないことが多いです。

Microsoftコードページ932 - Wikipedia
https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP932.TXT

ISO/IEC 8859

unicodeで定義される全ての文字を一般的に扱う場合に便利な規格です。
全ての文字に対応していれば日本語にも対応しているだろうというアプローチです。
ただ、その分対応している文字は多岐に渡り全ての文字を簡単な方法でプリレンダリングしておくことは困難です。
日本語の表示という目標に対してのアプローチとしては基本的にオーバースペックです。

フォント

日本語を定義したことで必要なコードポイント一覧が完成しました。
画像を得るためにはコードポイントと関連付けられるデータフォーマットを選択する必要があります。

Apple Advanced Typography(AAT)

Apple Advanced Typography - Wikipedia
上記を見ると「え、もはや文字じゃないよね」って感じのサインのような画像が掲載されています。
文字は文字です。諦めてレンダリングしましょう。

しかし、複雑な文字をレンダリングするとなると複数の実装方法が存在してしまうのは仕方ないことと言えるかもしれません。
AATは文字の変形の役割を果たすシェーピングスクリプトをフォントデータに埋め込む方法をとりました。

OpenType Layout

OpenTypeフォントとも呼ばれたりします。
AATとは違って規格でシェーピングスクリプトまで決まっているようです。
なのでフォントデータは規格を通してシェーピングスクリプトを選択できるので最低限の情報を埋め込むことで目標を達成できます。

TrueType

OpenType Layoutに対応しようという動きもあったようですが、責務が複雑になることを嫌って分化したようです。
おなじみのTTFフォーマットです。
正しい位置にレンダリングするのはレイアウトエンジンの役割でTrueTypeはコードポイントを指定した位置にラスタライズする役割を担います。

レンダリングAPI

フォントのラスタライズまでできたら後はテクスチャに焼いてレンダリングするだけです。
しかし、DirectXOpenGLといったレンダリングAPIはただの出入り口でありプラットフォームとは別の問題です。
MacOSでのVulkanはMetalを使っていますし、Windows10のDirectX9はDirectX11や12を使っています。そして、その中では更にベンダーの固有取り決めを通して役割を果たしているでしょう。

よくある要望としてDirectX9に対応して欲しいというものがありますが、それはDirectX9 APIに対応して欲しいのか、DirectX9対応のデバイス全てに対応して欲しいのかはっきりさせる必要があります。
DirectX9対応のデバイスに対する動作確認環境を用意することは難しい割に対象ユーザがほとんど存在していないのでメリットが少ないです。

以下は私の推測であり、確実な情報ではありません。
動作環境としてはまずWindows Vistaを用意してもらう必要があります。また、DirectX9対応と書かれた最古のGPUも準備する必要があります。
これはXPぐらいからのDirectX9は内部的にDirectX9.1以降を使っていると私が考えているためです。
DirectX9.1/9.2/9.3は公式なバージョニングかは不明ですが、DirectX11以降に設定できるD3D_FEATURE_LEVEL_9_1と対応しています。
これらから生成できる互換スワップチェインは厳密にはDirectX9との互換性がありません。
この互換性がないことについてはMicrosoft公式が言及していたので確実だと思います。

まとめ

DirectX9で日本語表示して欲しいという要望は曖昧過ぎる。

参考

Chromeとかで使われている文字レンダリング技術の作者のサイト
http://behdad.org/text/

C++でgRPC for Windows

必要な前提知識

ライブラリのビルド

gRPCはビルドパスが複雑な上、公式のWindows対応が少し弱いため試すだけで一苦労になりがちです。 単にやってみたいだけなら安定して色々できるGoを使うことをお勧めします。Windowsでも簡単です。
これもBazelがWindowsで安定に動作するようになれば、そっちに移行すべきだと思います。 現状ではgRPCの単純なビルドでも失敗するので採用し辛いです。

ここで利用するのはvcpkgです。
vcpkgの最大の利点はMicrosoftが管理していることです。これでWindowsのツールチェインの取得を失敗するようなことは限りなく少なくなるでしょう。 私はいつもGit for WindowsというBash付きの環境を導入するのですが、vcpkgはportable gitを利用しているようなので、それに合わせた方が環境を汚さないかもしれません。

git clone https://github.com/Microsoft/vcpkg
cd vcpkg

Windows環境の場合、コマンドプロンプトpowershellで実行しないと失敗する時があるので注意。 後、Visual Studioの英語の言語パックがないと失敗する。

bootstrap-vcpkg.bat

これでvcpkg.exeがビルドされる。

vcpkg.exe install grpc

これでx86版のライブラリがビルドされます。

ビルドは内部的にはCMakeが使われているので、ライブラリの全ての機能を使うためにはCMakeを使う必要があります。

手動

結局はパス問題なので、パスを好きな手段で繋げることでCMakeを使わずにビルドすることもできます。 vcpkgは親切にprotobufferのコンパイルと同時にProtoファイルのコンパイラであるprotoc.exeもビルドしてくれています。

その配置先である以下のバイナリを利用してProtoファイルから.pb.ccと.pb.hを生成します。 vcpkg/installed/x86-windows/tools/protobuf/protoc.exe

protocの引数については今回のテーマではないので以下のリンク等を参考にしてください。

protocプラグインの書き方 - Qiita
grpc/CMakeLists.txt at master · grpc/grpc · GitHub

gRPCのファイル生成にはプラグインが必要です。プラグインのパスは以下です。
vcpkg/installed/x86-windows/tools/grpc/grpc_cpp_plugin.exe

上記パスを引数に含めたprotocの実行によりProtoファイルから.grpc.pb.ccと.grpc.pb.hが生成されます。 これでファイル生成作業は終了です。

ビルドするには以下のパスを通してください。
vcpkg/installed/x86-windows/debug/lib
vcpkg/installed/x86-windows/include
実行環境には以下のパスを通してください。
vcpkg/installed/x86-windows/debug/bin

後は普通にビルドできます。

CMake

vcpkgは各ビルドしたライブラリをパッケージ化してくれます。 ただ、そのままではパッケージを見つけることができないのでCMake実行時にパッケージを見つけてくれるスクリプトを読み込ませます。

詳細を知りたい方は以下を読んでください。
vcpkg/integration.md at master · Microsoft/vcpkg · GitHub

方法としてはcmakeの実行時にvcpkg.cmakeのパスを渡すだけです。

cmake .-DCMAKE_TOOLCHAIN_FILE=C:\vcpkg\scripts\buildsystems\vcpkg.cmake

上記によりfind_packageによりinstallしたライブラリが見つかるようになります。

ただ、見つかるパッケージについては
vcpkg/installed/x86-windows/share/gRPCTargets.cmake
等を読む必要があり不便に感じます。

install終了時に

The package grpc:x86-windows provides CMake targets:

find_package(gRPC CONFIG REQUIRED) # Note: 8 target(s) were omitted. target_link_libraries(main PRIVATE gRPC::gpr gRPC::grpc gRPC::grpc++ gRPC::grpc_cronet)

等とは言ってくれているのですが。

protoファイルからCMakeで.pb.ccと.pb.hの生成を行うには以下のようにします。

find_package(protobuf REQUIRED)
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS foo.proto)

grpcの方のファイル生成サポートはgRPC::grpc_cpp_pluginだけなので下記のようにadd_custom_commandでコマンドを走らせる必要があります。
https://github.com/grpc/grpc/blob/master/examples/cpp/helloworld/CMakeLists.txt

後は各出力を自身のプロジェクトに組み込みビルドするだけです。 Protoファイルを書き換えてビルドするとそのまま反映されるので便利です。

C++20時代のポリモーフィズム

概要

ポリモーフィズム良いですよね。
でも、継承嫌ですよね。
MixInみたいな綺麗な継承が使える場面も現実的なサービスを考えると難しい。
そこで簡単に追加、削除ができ可読性も悪くないstd::variantとconceptを使ったC++20時代のポリモーフィズムを紹介します。

コード

#include <iostream>
#include <vector>
#include <string>
#include <variant>

struct A{};
struct B
{
    std::string ToString()
    {
        return "B";
    }
};
struct C{};
struct D{};
struct E{};

using VariantType = std::variant<A, B, C, D, E>;

template<class T>
concept bool Printable = requires(T t){
    t.ToString();
};

void PrintIfPrintable(VariantType variant)
{
    std::visit([](auto&& arg)
        {
            if constexpr (Printable<decltype(arg)>)
            {
                std::cout << arg.ToString() << std::endl;
            }
        }, variant);
}

int main()
{
    std::vector<VariantType> variants;
    variants.emplace_back(A());
    variants.emplace_back(B());
    variants.emplace_back(C());
    variants.emplace_back(D());
    variants.emplace_back(B());
    variants.emplace_back(E());

    for(const auto& variant : variants)
    {
        PrintIfPrintable(variant);
    }
}
// 出力
B
B

visitがやや複雑なのを除けば素直な実装なのではないでしょうか。

variant

variantは状態として特定の型を持つという非常にポリモーフィズムと親和性の高い特徴を持っています。
現在の型にアクセスするためにはstd::visitを使わなければならないという制約は付くもののvariantで定義した型全てに同様の処理を加えることが簡単にできます。
また、継承によるポリモーフィズムにありがちな、このインターフェース継承しているクラスが見つからない問題を解決できることは素晴らしいです。
継承しているクラスは定義ジャンプで到達できないので大規模なプロジェクトだと数十秒かかる検索で探さないといけません。

concept

上記コードはWandboxで動作します。
Compiler optionsに-fconceptsを付けるのを忘れずに。
コンセプトの強さはこの雑に使い方書いたら動くという所にあります。
かなり実務よりな機能なので早く標準に入って欲しいです。