基本環境
- Arch Linux(x86_64)
インストール時に指定したパッケージグループ
- base
- base-devel
環境インストール後にインストールしたパッケージ
- xorg
- libjpeg
- cairo
最初から入っていたけどよそでは入ってないかも知れないパッケージ
- gdbm
時間が空いたが動画を取得したので貼ってみる。動かしてるのはwOCE用のサンプルアプリで画像ビューア。無難なの...と選んだ結果「オフィスに貼ってあるアレ」的なフォルダの中身を表示させてみた。
また動画の終わりのあたりでマウスカーソルを動かして奥行きをデモしたりしてる。
なんて思ってたんだが実はテクスチャの更新が遅いのは更新回数が原因だった。
どうやらテクスチャの更新は思ってた以上に回数依存ぽくて、一度に更新するサイズを拡大したらメインスレッドでの更新でPBOも使ってないにもかかわらず一発で十分なほど改善された。
これだとスレッド化してPBOで更新するようにすれば大きめのテクスチャをまるごとでも更新できそうなんだけど、それをすると通信路が詰まりそうなので今の方法で問題が出るまで放置することにする。てかおそらく今でもリモートで動かすと詰まりそうだし。
「キャスト」というのは違うかも知れん。画面を動画でキャプチャする機能を復活した。
今回はPBOを大量に用意してフルフレームで「ぬるんぬるん」キャプチャできる上に通常動作への影響が感じられないくらいの低負荷にできた。
最初はPBOではなくテクスチャに格納するようにしていたのだが、テクスチャからメインメモリに読めるはずのglGetTextureが全く機能せず画像が真っ黒になってしまうのでPBOを使ってみたのだが、画像の取り出しも高速化されているハズで満足な結果になった。
しかし、動作確認に使ったサンプル画像がアレなので動画の公開はまた今度〜(ぉ
透過画像が暗く表示されるので何事かと思ったが、Cairoが改版されて透過色の扱いが普通になったのが原因だった。
以前はCairoでは透過の場合RGBの各要素に不透過率が乗じられた状態で格納されてた。こうするとそのまま足せばいいのである意味簡単なのだがCairoを特別扱いする必要があった。
で、これがなくなって普通にRGBにはそのままの値が入るようになったので、普通に扱えば良くなった。というワケ。
敵は己自身(のミス)だけだったわけだけどorz
インデックスの値とか調べたところで三倍三倍繰り返してたのが原因で、インデックス数の三倍をglDrawElementsに投げててsegfaultで落ちるとか悩んでた。インデックス数はポリゴン数の三倍なので元々三倍されているというマヌケだ。
VBO使えないとキャラ出せないのでどうしようかとも思ったが、このあたりはこれで大丈夫そう。
窓の描画ではほぼメリットのなかったVBOだが、背景画像や部屋、キャラを含むオブジェクトの描画では必要になるのでとりあえずエクイレクタングラーな背景画像もサポートするために着手してみた。流石に球の頂点を毎回プロセッサから送りつけるのは無駄が許容範囲を超えるしついでにスカイボックスもVBO化しときたい。毎回同じ頂点なんだし。
終了時に画面をOFFするようには組んであったが終了する方法がなかったので追加して画面がちゃんとOFFされることを確認できた。んしんし。
他に参照と(後)constポインタとポインタの原則を守ってない部分があったので修正。この原則は...
キャラメイクについて考えてみたのだが、MMOだとそこにいるキャラ全部のデータをグラボに読ませなきゃならない関係上、好きなように-例えばFBX読ませたりとかみたいな-キャラ作らせるわけには行かなくて、その上wOCEはコミュニケーションツールに重点を置くのでゲームと違って「なうろーでぃんぐ」が許されない。なのでデータはなるべく小さくなるべく使い回さなきゃならん。
今のは知らないけどDC版のPSOがああいう、種族、身長、太さ、色みたいな限られたキャラメイクになってたのはそういう理由からだと推測していて、今だともうちょっとリソース使えるにしても似たようなものになりそう。そのへんの研究のためにやってみるかな...。
テクスチャのアップデートがクッソ遅いのできっちりコントロール下に置かないとフレームレートを維持できないのが理由で処理を再び集中化した...のだが、わかっているつもりではあったがここまで遅いとは思わなかった。フレームあたり8x8のタイルでせいぜい200枚くらいしか更新できない。
というところで今のところテクスチャサイズが二冪ではないのを思い出した。どのみちグラボで完結する処理に比べたらクッソ遅いんだろうけど二冪にしたら多少は高速化できるかも知れぬ。
--二冪テクスチャを作ってやってみたが大して変わらなかった。転送サイズ拡大のほうが効くかも。
VRHMDのプロファイルから画面名を読んで、XRandRで読んだEDIDに書いてある名前と一致してる画面が見つかったらONにして所定位置へ配置するようにした。これで一応画面配置に関する問題は解決。
いちいちXRandRを起動してるのでlibXRR使いたいところだけど手間なので後回しにしてこの件はひとまず終了っと。
テクスチャ更新はかなり重い処理なのでフレーム更新に影響が出ないようにコントロールする必要があるのだが、別スレッドでこの処理をするとまったくコントロールできなくなってテクスチャ更新がまとまって入ってくるとフレームレートどころの話ではなくなる。RiftCV1なんかはフレームレートが乱れるとブラックアウトして回復しないようだし、ダメダメだ。
困ったことにフレームバッファのスワップはVSYNCに近い場合でもなければVSYNC待ちをしないので、メインスレッドでメッセージ処理をしようとすると無駄に描画するばかりでほとんど処理が進まなくなる。これがスレッド化した理由なのだが。
どうやらフレームバッファをスワップした後にglFinishを呼んでおくとVSYNCを待ってくれるようなので、結構苦労してマルチスレッド化したのだが元に戻すかあるいはテクスチャ更新だけメインスレッドですることになりそう。
--RiftCV1がブラックアウトするのはUSB3の電源が原因のようだ。追加電源使うボード使ってるのだが、DK2も挿してるのがダメっぽい。
接続されてる端子がわかってれば何も考えずにXRandRをspawnすればいいのだが、その端子を探すためにはEDIDを読まなければならないので調べてみた(get-edidはうちの環境では画面ひとつ分しか読んでくれなかった)。
libxrandrのmanページがメモ同然なのでXRandRのソースを読んだらstaticおじさん感が溢れ出してくる上に処理自体が結構めんどくさい。libXRandRが低レベルすぎる感。これは将来的には自前で書くとしても今は自前で書かずにXRandRをspawnして結果をパースするほうが良さそうだ。
「xrandr --prop」とかでEDIDを取得できたらEDIDのオフセット54からの長さ18bytesの4つのブロックのうち、先頭が00 00 00 fc 00になっているブロックに画面の名前が入ってるので、それが検出されたVRHMDと一致してたらその端子名を使って、画面有効化なら「xrandr --output 端子名 --auto -pos 場所」、画面無効化なら「xrandr --output 端子名 --off」をspawnすればいいようだ。
OpenHMDからRiftCV1の歪み補正情報を得たので試してみた。
のだけれど、RiftCV1は設定しないと画面として認識してくれないのでDK1/2のように一度画面を設定すれば繋がってる限り画面の設定が保存されるってわけには行かない。
KDEは接続のパターンごとに設定を覚えているけれど(そしてよくデスクトップと画面の対応を滅茶苦茶にしてくれて腹立たしいけれど)XFce4は全く覚えてくれない(が、変更してもデスクトップと画面の対応は安定している)...という感じに環境によって追加された画面の扱いがまちまちなのでここいらでXRandR使って自前で設定すべきなのかもと。どのみち他所の環境で動かすようになったら必要になるし。
シェルからのリクエストで子プロセスへの通信路を確保して起動できるようにした...んだけどメニューとか出すためにはレイアウタを完成させないとならん。とりあえず描画内容を反映できるからとメニューとか先に「てやーっ!」て作っても結局「リストコントロールを使ったメニューコントロール」を作るのだから二度手間だ...てわけでレイアウタでやることを書いてみる。
レイアウタはサイズを持つWidgetのサイズを決めるもので、レイアウタを使うことでユーザプログラムは配置計算から解放される。要するに項目を追加してったり、予めサイズが決まってるところを分活したりする時に確保する幅や割合だけで宜しくやってくれるってわけ。
しかし、追加したり割り当てたりするのはいいんだけど、どういうインターフェイスにするべきか悩むところ。縦用とか横用とか用意するのかとか。
Rift他大抵のVRHMDは光学系の歪みを描画内容を歪ませることで補正しているが、この副作用として実際のフレームバッファより大きな領域を必要とする。この関係で描画領域が実画面サイズと一致している場合フレームバッファ外から画素を拾う必要がある。
となるとテクスチャとして確保されたフレームバッファオブジェクトの外側をアクセスするわけで、デフォルトの設定では左右の外側が繰り返しになってしまって見苦しい、
というワケでフレームバッファオブジェクトの設定をGL_CLAMPへ変更。この設定だと外側へのアクセスは外周の半分の値を得られるのでちょうどいい塩梅にぼやけた感じになってくれる。最初歪み補正テクスチャの属性をいじって変わらんなーと悩んでたのはナイショだ。
TB::Threadではスレッド本体を自動で起動しているのだが、これがタイミングが悪いと継承する前のスレッド本体(純粋仮想関数)を呼び出してしまう可能性がある。
実際にはスレッドの起動は最低優先度スレッドで行われるのでまず起きないことではあるし今のところ問題は起きていないのだが、起動準備ができていないのに起動する可能性は依然残っているので明示的にRaiseThreadを呼び出して起動するようにした。
明示的に呼び出さないと動かないとか仕様として気持ち悪いのだが全ての継承先クラスのコンストラクタの処理が完了した事を確認する方法がないので仕方がない。
地磁気センサを扱うには地磁気より強い機器自体の磁化成分を除去する必要がある。旧来は「機器を8の字に回して」キャリブレーションしてみたり、あるいは旭化成が特許を持ってるDOEみたいな方法もあったのだが、VRHMDをぐるぐる回してキャリブレーションするのはかなり面倒だしDOEは知的財産権的に微妙だし四点のサンプルを必要とする。
考案自体はだいぶ前にしていたのだが式が解けなかったのだが、先ほど式ではないが図で解けた。しかもDOEが四点のサンプルを必要とするのに対して新方式は二点でキャリブレーションを進められる。
開発環境にはXfce4を使っているのだが、この環境でGLXでキャプチャするとXfce4をコンポジット処理の「全画面オーバーレイウィンドウを直接表示する」を設定しておかないとキャプチャできず真っ黒になり、指定しておくと画面の更新が止まり、コンポジットを使わないとゴミが読み出される...という状況になっている。
まぁ要するにこの方法では無理って事だ。
KDEとかだとまた違う可能性もあるのでそのうち試すとしても、GLXによるキャプチャが使えないとするとあのスットロイX標準の方法でキャプチャするしかないのか...orz
--Xephryを取り込んだりして子コンテキストに描画させることはできるので不可能じゃないんだけど、手間かかりそうなので将来の課題だなー。本筋じゃないし。
forkしてexecするためのクラスを作ったのだが、ここで親プロセスではなく子プロセスが戻ってきてしまうように間違ったところ、共有ライブラリを読んでくれなくて死亡。
これはforkしてexecしてしまう場合には問題にならないが、プリエンプティブなスレッドが欲しい場合に問題になる。以前これについて調査したところ「forkする前になんか呼び出して予めライブラリを読ませとけ」というのが回答だったし他の回答はなかったのでこれがベストなんだろう。
メモリ空間をまるごと共有みたいな真似せずに必要ならmmapして共有しとけということかも知れぬ。
描画先が根窓ではない以上、キャプチャのためのGLXコンテキストが必要になる...というわけで作りかけだった部分を一通りちゃんと組んで安定してGLXコンテキストを扱えるようにしてみた...相変わらず値窓は真っ黒なわけだけどこれは後でXephyrの窓になるように修正するのでおk。
GLXコンテキストは何かマズイことをするとエラーを出すとかじゃなくてsegfaultで落ちるというセンシティブなブツなのだが、ラッパクラスがちゃんと動くことを確認できたのでこれで気軽に子コンテキストを扱えるようになるというもの。キャプチャに関してはあとはXephyrの起動と本命窓の特定ってところで、イベントの配信が巧く行けばアプリケーション側はVRHMDを外さずに開発できるようになる。
VRHMDの脱着は結構厄介な問題で、うちではDK1のスポンジはボロボロで、DK2のスポンジは切れた...と思ったら剥がれてただけなので補修しとこう。ともかくVRな開発で脱着を繰り返すとすごい勢いで痛むのだ。VRカバーってのもあるけど取れやすくて困るし...単純に面倒というのも小さくはないけどね。
wOCEの前身であるRWMはWMとして書かれたが途中でWMではなくディスプレイマネージャであることに気付いて方針転換した関係上、おそらくwOCEではもう使わない情報だろうが本当に探すのが大変なのでWMの作り方へのリンクをメモしておく。
現在本当に探しているのは「窓を最小化するときには何をすべきなのか」なのだが、またこの情報がない。すぐに出てくるXIconifyWindowはWMに対してアイコン化するというメッセージを送るだけなので実際の処理はWMがする。WMの好きにやっていいってことなんだろうけど。
そんなもんが必要なのはXephyrの窓を表示しておいても邪魔なだけというのが理由。どのみちwODMの画面が上になるのだが無意味に表示させてオーバーラップの処理しても仕方がないので。
だがXの処理では表示させない窓はUnmapすることになってて、しかしUnmapすると中身が一切合切なくなってしまうので使えないし、窓がIconizeされるときにはUnmapされない。なので窓がIconizeされる時に何してるのか調べる必要があるというわけ。
「アイコナイズした時の座標」みたいなXリソースがあったのでおそらくこれを使うのだろう。値が非常に大きいので要は最小化した時は画面外に移動しているようだ...結局これかぁ...。
X画面を直接キャプチャしようとするといろいろ問題があるので、X画面キャプチャのためにはXephyrやXnestを起動してその窓を探す必要がある。PIDはwODMからそれらを起動する時に得られるとしても、特定PIDの窓を探す方法がわからなかったので調査した。
StackOverflowの「How to get an X11 Window from a Process ID?」にあった。同様のエントリはもうひとつ。他の似たようなエントリはシェルスクリプトを使うものだったのでこの件とは無関係だ。
それによるとどうやら「_NET_WM_PID」ってラベルでAtomとして書き込まれているらしい。X11には窓に任意のデータを追加する機能があるのでXLibあたりがそれを使って書いといてくれているようだ。
間違い。_NET_WM_PIDとかの値はアプリケーションが自分で設定する必要がある。おそらく各種フレームワークはやってくれるだろうけど。
SightEnterイベントが連続して送られてくるのだが...調べてみると一番近いWidgetを探すメソッドが奥のWidgetに反応しちゃってて、奥をWidgetが通る度にLeave/Enterが発生しているという困った状況になってる。
...んだが原因判明。ある点に対応するWidgetを探すときに子要素を辿る方法が間違っていたのが原因。間違っていたというより完全に余計なことをしているので、他の何かと間違って入れたものと思われるorz
ともかくこれで長かった「サンプルアプリケーションを作る(1)」をclose。
フレーム時間の70%を経過してから描画処理が開始されるように設定してみたところ、目論見通り他の処理を高速化できた。最初の状態の10秒以上が0.5秒になると言ったところ。ほぼVSYNCを待つ環境と同様なのでこの方向で間違いはないだろう。
あとは描画負荷に合わせて待ち時間を調整したりフレームバッファの初期化なんかを待ち時間の前に入れて並列性を高めてみたりでこのあたりは完成とする。VSYNCに同期できないのは腹立たしいが現状では難しいので後回しにしとく。
glSwapBufferがVSYNCを待たない問題はまだ解決していない。これを解決しないと何してもクッソ遅いので避けて進むわけにはいかないし。VSYNCを待つような設定や処理にしても待つのは処理がVSYNCを跨ぐ場合だけで、そうでない時は何も待たないのでほとんど意味がない。これはAMDのドライバだけでなくIntelのドライバも同様だ。
描画スレッドを待たせるだけなら周期的に描画スレッドを寝かせればいい。VRでは描画タイミングが大事なのでVSYNCからの時間情報が欲しいところだがないなら仕方ないというところで、ひとまず周期の最初の半分以上は描画スレッドを寝かすことにする。pthreadはコオペレイティブなので他のスレッドが長引くと大きく時間が狂うわけだが...。
画面側のX環境をキャプチャしてVRHMDを脱がずに開発したいところだが、デスクトップ環境がルート窓を使ってなかったりしててなかなかうまく行かない。そこでXephyrとかXvfbとかXnestとかみたいな仮想画面を使ってルート窓からではなく「表側」からのキャプチャを試してみようと思う。
実のところルート窓をキャプチャするのは実画面のサイズに縛られたり、wODMの出力までキャプチャされちゃったり、Xdamageでキャプチャすると遅かったりでイマイチなわけだけど、仮想画面なら実画面を無視してサイズを設定できるしXephyrなら動画も楽々だし。
実装するにはXephyrの窓を探さなければならないわけだが。
グラボのドライバの中にはVSYNC待ちを無視してくれるものがあって、そういうドライバに当たると描画が忙しくてメッセージの転送や処理が回らなくなることがある。また逆に画像転送などのようにメッセージ処理に時間を取られて描画が疎かになったりする。なので描画とメッセージ処理を別のスレッドにした。スレッドの管理にはpthreadを使う。
メッセージ処理といってもテクスチャのアップデートなどがあるので描画スレッドとOpenGLのリソースは共有しなければならない。X環境ではこれはglXCreateContextでGLXコンテキストを作るときに元のGLXコンテキストを指定してやることでできる。なお、子コンテキストは親スレッドで作る。
で、子コンテキストを作ったらスレッドを起こす。スレッドで子コンテキストをglxMakeCurrentすればおしまい。glxMakeCurrentを調べればわかるがpthreadのスレッドはGLXコンテキストを切り替え対象にしててちゃんと切り替えてくれる。余計な心配は要らない。
ルート窓を取り込むためのGLXコンテキストを作って、スレッドでカレントにするとsegfaultで、X越しに設定するとBadMatchでおっ死ぬのだが...スレッドなのでBadMatchってのはないと思うのだが...と悩んでいたのだが、manに「drawable が None なのに ctx が NULL でない場合」にも起きるとの記述がある。この場合drawableはルート窓なので...。
もしやと思いルート窓用のコンテキストを先に作って、それを描画先窓のコンテキストで共有するようにしたらX越しに設定すると相変わらずBadMatchは起きるものの直接設定した時にはsegfaultは起きなくなった。
もっとも、キャプチャできていないんだけど(ダメジャン
template<typename T> struct IDArray{ unsigned used; IDArray::Node* array; unsigned size; class Node{ public: Node(IDArray& arr) : id(arr.used++), body(&arr){ //mallocしたりreallocしたりした領域を確保したりthisを所定位置に設定したり }; ~Node(){ //返却と配列の所定位置のクリア }; protected: const unsigned id; private: IDArray& body; }; };
と思ったら、今のコードはフレームバッファオブジェクト(FBO)を使っているのでフレームバッファを切り替えてキャプチャして戻して...という操作が必要っぽい。全体的な描画関連処理は以下のような感じ...。
FBOはVIEWクラスにがっちり隠蔽されてて触れないのだが、VSYNC待ちの時は生フレームバッファがbindされた状態でxDisplayモジュールに処理が回ってくる。このタイミングならデスクトップをキャプチャできるだろう。
引っ越し先。 見えるかな。