2018 仕事でやったこと改革したこと
2018年もいよいよ終わりが近づきました。
本年も大変お世話になりました!
今は来年引き続きのことで気は忙しいけど軽くまとめておこうと思います。
主に手がけたこと
某Android向けSDK開発
AndroidでC++とUnityなもの。xR領域のものです。
設計能力と実装能力とチームマネジメント能力が大いに鍛えられましたw
今年の大半はこれをやっていました。日本テレビさんのxR関連
真夏の音楽特番 「THE MUSIC DAY」のAR連動はテンションが上がりました。
日本中のものすごくたくさんの家庭に番組の進行と同期してアーティストがお邪魔しました。
また王子関係もありましたw
その他、会社のまとめページ https://www.systemfriend.co.jp/business/xr
働き方改革
今年は若干自分のチームが大きくなりました。
そうするとマネジメントっていうタスクがどうしても出てくるわけですが、自分の働き方の基本コンセプトとして下記を死守するために色々と効率を上げる工夫をしました。
- 18:00には家に帰って子どもをお風呂に入れる
- 興味のあることだけやりたいw
- 仕事時間の50%以上は開発をする
(営業・マネジメント系に費やす時間を限りなくゼロに近づけたい)
チームの目標設定
チームの一人一人に指示をするのは大変なので自分で考えて動けるようにチームの大きな目標を掲げました。
方向性に迷ったらそれを見て進むべき道を考えてねっていう感じなればマネジメント不要に近づけそう。
そしてそのドキュメントのURLをSlackのトピックに設定しいつでも参照できるようにしました。
ちなみにチームの目標はこれらです。
- 楽しいことだけやって生きられるようにしよう
(そうすればきっと他の人を楽しい気持ちにするものが作れるよ) - 最も価値があるのは”時間”なので自分の時間を作れるようにしよう
- 難しい問題を解決しよう
(プロとして同じことをやるにしても最も美しいコードで問題を解決しよう) - 新しい技術に関する知識を得よう
ワークフローと担当エリアの明確化
新しく入ってきた人に同じことを説明する時間を節約するためにぼくたちはどんな流れで仕事を継続的にしているかと、だれがどこをカバーしているかを可視化した資料をつくりました。
青字はメンバーのイニシャル。マッピングが多いほど望ましいw
そのうちマッピング数、各タスクでの活躍を評価なんかにもしてゆきたい感はあるけどまあおいおいですねw
要は時間ができたらこれを見て、次の動きを自分を拡張できる方向で考えて動いてねっていう意図で書きました。
これもSlackのトピックから常時アクセス可能になっています。
進捗確認の自動化
進捗どうですか?って聞いて回るだけでたくさんの時間を浪費してしまいますよね。
そこで平日の毎朝始業時間にチーム全員に今日やることを聞いてくれるbotをSlackに仕込みました。
大事なのは「今日やったこと」ではなく「今日これからやること」がイメージできているかという点だと思っているので未来のことを聞きます。
多分記入には1人1分もかかってないかなと思います。
ぼくの内容確認が1人1秒~5秒。
問題なければOKスタンプをつけるだけで終了です。
そしてもし方向性の調整や追加のお願いが必要だなと思ったらコメントで会話して手を付ける前に調整をお願いします。
ぼくは9:00には大抵会社にいないので電車の中でこれらが完了していることも大きなポイントかなと思っています。
日々だいたいのことが把握できているのでグループでのMTGは1週間に1回、10分以内で終わる感じです。
これらが功を奏しているのか、チームとしても社内でほとんど残業はしないけど最もパフォーマンスの高い存在になれているんじゃないかな。
まとめ
こんな感じで楽しく生きるための工夫を考えて試行してみるのは楽しいなと気づいた今年でもありましたw
来年も楽しくしたいな~。
来年もどうぞよろしくお願いします!
Unity2018.3でのHoloLensビルドのコード最適化設定 (MasterWithLTCGビルド)
Unity2018.3.0f2が正式版として落ちてきましたので早速HoloLensアプリのビルドを試してみました。
MasterWithLTCG ビルド設定
Unityからは普通にVisual StudioのIL2CPP のUWPプロジェクトを書き出すわけですが、VSでビルド設定をReleaseに変更しようとした際に見慣れぬ設定があることに気づきました。
この "MasterWithLTCG" という設定です。
Unityのリリースノートを調べると2018.3.0b9から導入されたもののようです。
そしてLTCGというのは "Link Time Code Generation" 「リンク時コード生成」の略ということがわかりました。
そしてLTCGとはなんぞやということはこちらに書かれていました。意外と古くからある機能なのですね。
Under The Hood: Link-time Code Generation | Microsoft Docs
こちらはオフィシャルなリファレンス。
/LTCG (Link-time Code Generation) | Microsoft Docs
そして "MasterWithLTCG" ビルド設定とはこのLTCGが有効とされるビルドということになります。
LTCGする意味
ここから先は調べてみてきっとそういうことだなと理解したことなので正確ではないかもしれませんw
そもそもHoloLensプロジェクトはIL2CPPのプロジェクトとして出力されていますからコードはIL(Intermediate Language)からC++コードに変換されているわけですが、Masterビルドでのビルドプロセスは下記となります。
これがMasterWithLTCGビルドでは下記になります。
- コンパイラ:各cppファイルがobjファイルに変換される。この時コードオプティマイズも行われる
- リンカ:各obj間の関数呼び出しをインライン化する等のオプティマイズを行う。
- リンカ:各objをマージしてexeファイルとする
なのでMasterWithLTCGビルドの方がリンク時間が長くなるはずです。
実際に計測してみました。
Master 1>Total compilation time: 88144 milliseconds. 1>Total link time: 16372 milliseconds. MasterWithLTCG 1>Total compilation time: 49604 milliseconds. 1>Total link time: 158050 milliseconds.
コンパイル時間が短くなっているのは謎ですがリンク時間は10倍程度長くなっていますので色々頑張っている様子が伺えます。
生成されたDLLファイルのサイズは下記の通り。 単純には言えませんがインライン展開によってマシン語レベルでコードが増えた結果かもしれません。
Master
MasterWithLTCG
まとめ
Unity2018.3でHoloLensプロジェクトを出力した際に現れる"MasterWithLTCG" ビルド設定の効能について書きました。
今回は比較的シンプルなアプリケーションでテストしたため実際に高速化の恩恵を体験することはできていないのですが、最終リリースをこの設定でビルドして少しでも高速化しておくことは意味がありそうだと思いました。
調査の経緯はこちらのツイートにもスレッド化されています。
Unity2018.3でHoloLensプロジェクト書き出すとMasterWithLTCGというConfigurationが現れたんだけどなんじゃろう pic.twitter.com/LJonfqomb7
— Satoshi Maemoto (@peugeot106s16) December 17, 2018
Intel RealSense D435i のDepth映像を確認してみた
Intelの新しいデプスカメラ D435i が届いたので開封してDepthカメラとしての動作確認をしましたので取り急ぎ共有します。
D435との外観比較
外観は全く一緒なので取り違えに注意です。
本体下部に型番が書いてあるのでそこで判断できます。
D435のDepth映像と赤外線の様子
赤外線パターンはくっきりとした小さな点です。 (ZOZOスーツだ!)
D435iのDepth映像と赤外線の様子
これは誤報かもしれません。ファームウェア更新後再度試したところD435同様の赤外線パターンが確認されました。
ステレオカメラなので赤外線は発していません。
赤外線カメラを当ててみればD435との違いがはっきりとわかります。
画質については面についてはD435が奇麗な印象。 ただ黒い髪の毛についてはD435が毛束の間がつぶれてしまうのに対しD435iはきちんと抜けていました。
まとめ
D435とD435iのDepthカメラとしての特性の違いを赤外線映像から探ってみました。 D435iのもうひとつの特徴、IMUについては動作を確認する方法がまだわかっていないのでもう少し探ってみたいと思います。
IMUデータの確認方法
IMUデータを確認するにはIntel RealSense Viewer 2.17.0 以上が必要です。現状 Pre Release状態になっています。
Release Intel® RealSense™ SDK 2.0 (build 2.17.0) · IntelRealSense/librealsense · GitHub
Android C++開発 [3] : 多次元配列をJNIでやり取りする
少し間が空きましたがまたモチベーションが出てきたので続きを書きます(笑)
先回はAndroid実機上で実行するInstrumentedTestでUIを表示しました。
今回はJava側で生成した多次元配列をC++側に渡し、C++側でバイナリデータをセットします。そしてそのデータをJava側に受け渡し、ビットマップとしてImageViewに表示します。
実際のユースケースとしては二つのカメラから取得した映像データを画面に表示するような時に利用できます。特殊ですねw
Activityの変更
先回作成したActivityに二つのImageViewを配置します。
ImageViewの名称はそれぞれimageView01, imageView02としておきます。
Java側InstrumentedTestの実装
InstrumentedTestでは後述するC++メソッドに二つのバイナリバッファを渡し、データを格納させます。
@Test public void imageRefreshTest() throws InterruptedException { int stride = 320; int lines = 240; Bitmap[] bitmaps = { Bitmap.createBitmap(stride, lines, Bitmap.Config.ARGB_8888), Bitmap.createBitmap(stride, lines, Bitmap.Config.ARGB_8888) }; int[][] buffers = { new int[stride * lines], new int[stride * lines] }; assertTrue(getBufferArray(buffers)); bitmaps[0].setPixels(buffers[0], 0, stride, 0, 0, stride, lines); bitmaps[1].setPixels(buffers[1], 0, stride, 0, 0, stride, lines); this.refreshImageOnUiThread(bitmaps); Thread.sleep(5000); }
下記の処理を行っています。
- UIに表示する2つのBitmapを生成
- C++に受け渡す2つバイナリバッファをもつ2次元配列 buffresを生成
- C++関数 getBufferArray()をコール
- バイナリバッファの値をBitmapにセットする
- UIスレッドでBitmapをImageViewに表示する
このコードからわかるようにバッファはJava側で生成されたものです。 このバッファをC++でアクセスしてみましょう。
C++ネイティブコードからJavaの多次元配列にアクセスする
C++側は下記のようなコードとなっています。
native-lib.cpp
extern "C" JNIEXPORT jboolean JNICALL Java_com_example_satoshi_1maemoto_myapplication_ExampleInstrumentedTest_getBufferArray(JNIEnv *env, jobject, jobjectArray array) { auto elementCount = env->GetArrayLength(array); if (elementCount == 0) { return false; } for (auto index = 0; index < elementCount; index++) { jintArray element = (jintArray)env->GetObjectArrayElement(array , index); auto buffer = env->GetIntArrayElements(element, nullptr); auto bufferCount = env->GetArrayLength(element); auto color = ((index % 2) == 0) ? 0x00000000 : 0x00FFFFFF; auto step = ((index % 2) == 0) ? 1 : -1; for (auto pixel = 0; pixel < bufferCount; pixel++) { buffer[pixel] = 0xFF000000 | color; color += step; } env->ReleaseIntArrayElements(element, buffer, 0); env->DeleteLocalRef(element); } return true; }
- env->GetArrayLengh()で配列の要素数を取得できる
- env->GetObjectArrayElement()でJava側2次元配列の1次元目の要素を取得する。このサンプルではint配列が格納されている要素[element]が取得できる。
- env->GetIntArrayElements()でさらに[element]に格納されているint配列[buffer]を取得する。
- bufferにデータを格納する
- 最後に参照を開放する
このようにC++側では配列の要素を取得して利用、使い終わったら参照を開放する、という手順でデータアクセスをします。
少し手間ですがJava側は非同期にGCが走ることがあるのでこのような使い方になるようです。
UIスレッドでBitmapを表示する
C++側からデータが取得できたのでUIに表示しましょう。
この処理も先回のテキスト表示と同様UIスレッドで処理を行わせる必要があります。
そのため下記のようなユーティリティ関数を用意しました。
private void refreshImageOnUiThread(Bitmap[] bitmaps) { class ShowImagesAction implements Runnable { private Bitmap[] bitmaps; public ShowImagesAction(Bitmap[] bitmaps) { this.bitmaps = bitmaps; } @Override public void run() { ImageView imageView01 = rule.getActivity().findViewById(R.id.imageView01); imageView01.setImageBitmap(this.bitmaps[0]); ImageView imageView02 = rule.getActivity().findViewById(R.id.imageView02); imageView02.setImageBitmap(this.bitmaps[1]); } } this.rule.getActivity().runOnUiThread(new ShowImagesAction(bitmaps)); }
実際にInstrumentedTestを実行すると下記のようにグラデーションのかかった2つの映像が表示されます。
まとめ
Andoid実機で動作するInstrumentedTestでC++側コードと多次元配列をやり取りする方法をお伝えしました。
プロジェクト一式は下記にアップしています。
Android C++開発 [2] : InstrumentedTestでUIを表示する
InstrumentedTestを使えばデバイス上でUnitTestができますが、状態や映像を表示したりするにはUIが表示できると便利です。
InstrumentedTestにActivity表示のルールを追加する
Activityのテストをするにはテストクラスに ActivityTestRule というルールを追加します。
つまりテスト実行時にこのActivityを表示するように、といったルールの定義です。
ExampleInstrumentedTest.java
@Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class, true, true);
この記述を行うとビルドエラーが発生します。
なぜならInstrumentedTest実行時にこのクラスを参照する設定がされていないからです。
build.gradle の dependencies 内、 androidTestImplementation の部分に参照を追加するとビルドエラーは解消します。
build.gradle
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:rules:0.4' #コレを追加 androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' }
ActivityにUI要素を追加
実施するテストに応じてActivityに必要なUI要素を追加します。
ここでは文字列を表示する TextView を "textMessage" という名前で追加しました。
テストクラスからUI要素にアクセスする
テストクラスからActivity内のUI要素にアクセスするには、先ほど追加したActivityTestRule からActivityを取得し操作します。
ただUI要素はUIスレッドから操作する必要があるので直接操作することはできず、runOnUiThread()メソッドにアクションを渡し、非同期で実行させる必要があるのですこし面倒でした。
このようなユーティリティメソッドを作成しました。
private void showMessageOnUiThread(String message) { class ShowTextAction implements Runnable { private String message; public ShowTextAction(String message) { this.message = message; } @Override public void run() { TextView messageText = rule.getActivity().findViewById(R.id.textMessage); messageText.setText(this.message); } } this.rule.getActivity().runOnUiThread(new ShowTextAction(message)); }
テストメソッドからの呼び出し
以上の準備を行えばテストメソッド内から画面に文字列を表示することができます。
応用すれば様々な操作を行うことができます。それだけではなくこのRuleの真価はUIの自動テストが行えるところにあります。
ボタンをプッシュするといった人が行うようなアクションもシミュレートさせることができます。
さて、テストメソッドでC++から取得した文字列を表示するためにはこのような実装を行いました。
ExampleInstrumentedTest.java
@Test public void stringFromJNITest() throws InterruptedException { String message = stringFromJNI(); this.showMessageOnUiThread(message); Thread.sleep(5000); assertEquals("Hello from C++ to InstrumentedTest", message); }
実行結果はこのようになります。
C++から取得したメッセージが表示されています。
まとめ
Andoid実機で動作するInstrumentedTestで画面を表示し、UI要素にアクセスする方法をお伝えしました。
プロジェクト一式は下記にアップしています。
Android C++開発 [1] : CMakeプロジェクトの作成
お久しぶりです!
この夏はものすごく忙しくてブログを書く時間がとれませんでした。
何をやっていたかというとAndroidで動作するライブラリをC++で作り、Unityラッパーも作成してUnity上でも利用できるようにする、という事をしていました。
C++でのAndroid開発はかなりレアな体験で、色々と学べたのでシェアします。
レアすぎて需要が無さそうだなあ(笑)
プロジェクトの作成
Android StudioでC++を扱うプロジェクトを作成するにはプロジェクト作成のウィザードの中で "Include C++ support" のチェックを付けます。
生成されるプロジェクトは下記のものを含みます。
名前 | 説明 |
---|---|
java | 画面のActivity等のJavaソースコード類 |
cpp | C++ソースコード |
build.gradle(Module: app) | ビルド設定 |
build.gradle(Module: app)について
Android のビルドシステムにはGradleが使われます。
C++のビルドで使われるCMakeとの関連付けはこのファイルの中で行われます。
"externalNativeBuild"の設定でC++ビルドのオプションとしてC++11を利用すること、CMakeのビルド設定が書かれているCMakeLists.txtへのパスが指定されています。
独自のカスタマイズを行う場合はこれらを編集します。
とりあえず実行
このまま特に変更をせずにアプリを起動するとC++側から"Hello from C++"という文字列を取得して画面に表示するものが起動します。
コードで何が行われているかを解説します。
native-lib.cpp
extern "C" JNIEXPORT jstring JNICALL Java_com_example_satoshi_1maemoto_myapplication_MainActivity_stringFromJNI( JNIEnv *env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); }
C++側ではマネージドのJavaのVM環境と連携をとるためJNIの規約に沿った名前のメソッドが用意されています。
規約としてはJavaのパッケージ名の "." を "_" に置き換え、引数でJavaVMへのポインタを受け取るというのがシンプルな説明です。
実際には様々なかたちのデータを引数でやり取りするため様々なテクニックがあります。
例えば配列の配列を受け取るとか、Java側のコールバックメソッドのアドレスを受け取りC++からJavaをコールバックするなど、、この辺りは次回以降扱いたいと思います。
JNIの規約で作ったC++関数は下記のようにJavaから呼び出すことができます。
MainActivity.java
public class MainActivity extends AppCompatActivity { // C++ライブラリ(native-lib.so)をロード static { System.loadLibrary("native-lib"); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // C++側の stringFromJNI()関数を呼び出し文字列を取得 TextView tv = (TextView) findViewById(R.id.sample_text); tv.setText(stringFromJNI()); } // C++関数は native 指定で宣言が必要 public native String stringFromJNI(); }
実機でのUnitTest
プロジェクトの何も手を加えない状態でUnitTestのモックも作成されています。
UnitTestには2種類あり、開発環境内で動作させる通常のUnitTestと、デバイス内で動作をさせるInstrumentedTestがあります。
やっぱり事件は現場で起こるのでできるだけ後者を行いたいものです。ビルド時間はかかってしまうのですが。
あらかじめ用意されているモックは特にC++側との連携はされていないものですが、もちろんC++関数のテストも可能です。 次回以降このInstrumentedTest内でUIを表示するにはどうすれば良いかなども扱いたいです。
ExampleInstrumentedTest.java
@RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() { // Context of the app under test. Context appContext = InstrumentationRegistry.getTargetContext(); assertEquals("com.example.satoshi_maemoto.myapplication", appContext.getPackageName()); } }
InstrumentedTestの実行は、クラスを右クリックしてRunまたはDebugを選択します。
まとめ
MRTKのAppBarで簡単視野追従フローティングメニューを作ってみた
HoloLensアプリを作っていると、ちょっとしたシンプルなメニューを呼び出して処理を選択したいということがよくあります。
そしてそれがあまり頻繁には使わない機能だったり、見栄えをスッキリさせたいという場合、使わないときはメニューを非表示にしておきたいですよね。
でもエアタップを他のアクションに割り当ててしまっている場合、HoloLensにはマウスの右クリックに相当するジェスチャーがないのでどのようにしてメニューを呼び出すか悩んでしまいますよね。
そこでこんなUIを作ってみました。
- 空間を数秒ホールドするとフローティングメニュー(AppBar)が現れる
- メニューは視線を移動しても視野内に追従して付いてくる
- 項目はフォーカス時、選択時に効果音を鳴らし操作感を上げる
このメニューを適用したプロジェクトはGitHubで公開しています。
エアタップはUnityちゃんの呼び出しに割り当ててあり、フローティングメニューでSpatialMapping表示有無の切り替え、BGMのミュート/ミュート解除を用意しています。
AppBarの表示、アクションの設定
メニューはAppBarを利用します。
AppBarを利用することで簡単にボタンが並んだメニューを作ることができます。
ボタンのアクションはInteractiveReceiverにリンクさせることで一つのコンポーネントで集中管理することができます。
- SquareButtonPrefab
指定先のPrefabでボタンの表示などをカスタマイズできます。今回はアイコンなし、テキストを中央表示にしたPrefabを指定しています - Use XXX
デフォルトで用意されている Remove Adjust Hide ボタンを非表示にします - Display Type
独立型のAppBarとします
- Type
Customとします - Name ボタン名、アクションを実際に処理するInteractiveReceiverのコードでアクション種別の識別に利用される識別子です
- Text
ボタンに表示されるテキスト - EventTarget
アクションを実際に処理するInteractiveReceiverの派生コンポーネントを指定します
AppBarの自動移動の設定
視線を移動した際にメニューが視野内に自動追従してくる機能はSolver関連のRadial Viewコンポーネントを利用することで簡単に実現できます。
カスタマイズポイントとしては、追従時に視野のどの程度の範囲内に留まるかの指定があります。
デフォルトではHoloLensの場合ほとんど視野に入ってこないので小さめにしています。
ただImmersive Headsetで見た場合はかなり狭い範囲に収まってしまって逆に不自然だったので双方で不自然でないギリギリの設定をしました。
ここはもう少し丁寧に作りこむ場合、プラットフォームによって設定値を変えた方が良いと思います。
- Max View Degrees
オブジェクトが留まる視野内の範囲を度数で指定します - Aspect V
オブジェクトが留まる範囲の縦横比。横1.0に対する縦の比率。
InteractiveReveiverでのアクション処理
ボタンのタップ処理は指定したInteractiveReceiverに通知され、ボタンのNameを識別子とすることで下記のように振り分け処理を行えます。
protected override void InputClicked(GameObject obj, InputClickedEventData eventData) { switch (obj.name) { case "Close": this.appBar.gameObject.SetActive(false); break; case "Mapping": this.IsDrawSpatialMappingWireframe = !this.IsDrawSpatialMappingWireframe; this.spatialMappingManager.SurfaceMaterial = this.IsDrawSpatialMappingWireframe ? this.spatialMappingMaterialWireframe : this.spatialMappingMaterialOcclusion; break; case "BGM": this.audioSource.volume = (audioSource.volume > 0f) ? 0f : 0.5f; break; default: base.InputClicked(obj, eventData); break; } }
Profileによるボタンの体裁や効果音の変更
Profileを編集することで一括してUIの体裁などを変更することができます。
MRTKにデフォルトで用意されているProfileを変更してしまうと、MRTKのアップデートなどでファイルを上書きした際に設定が戻ってしまうので、コピーを作成しそこに独自の設定を行うことをお勧めします。
また、下記画像で示しているProfileを参照しているHolographicButton Prefab も同様に直接変更せずコピーを作成して編集することをお勧めします。
ここでは独自のProfileを参照することと、アイコン表示をdisableとしています。
ボタンの体裁は AppButtonTextProfile にてテキストを中央揃えとしました。
効果音は AppButtonSoundsProfile にてクリック時の音とフォーカス移動時の音を独自のものに変更しています。
ホールドでのメニュー呼び出し
最後に空間ホールドでのメニュー呼び出しです。
これは、InputManagerにGlobalListnerにハンドラを追加することで実現します。
HoloLensUnityChan/GlobalInputHandler.cs at master · SystemFriend/HoloLensUnityChan · GitHub
public class GlobalInputHandler : MonoBehaviour, IHoldHandler, IInputClickHandler { void Start () { InputManager.Instance.AddGlobalListener(this.gameObject); } void IHoldHandler.OnHoldCompleted(HoldEventData eventData) { this.controller.appBar.gameObject.SetActive(true); }
まとめ
以上で簡単に視野追従型フローティングメニューをアプリに組み込むことができます。