月別アーカイブ: 2014年6月

CPU 負荷が低い 新しい 3D API

昨年の AMD Mantle を皮切りに DirectX 12 が発表され、
つい先日 Apple から Metal も登場しました。
DirectX 11 以降停滞かつ安定していた状態から一変、
新しい GPU 向け API への流れが加速しつつあります。
どれも DirectX11, OpenGL とは互換性がない全く新しい API セットです。

これまでと趣が大きく異なっているのは CPU のための刷新だということ。
新しい描画機能への対応はなく GPU へのハードウエア追加も特に求められていません。
目的は CPU 負荷の軽減です。

API           Platform   Beta SDK   GPUs
-------------------------------------------------------------
Mantle        Windows    2014/5     RADEON GCN
Direct3D 12   Windows    ?          GCN,Fermi,Kepler,Maxwell
Metal         iOS 8      2014/6     PowerVR G6430

それだけ CPU の負荷の高さが問題になっていたことになります。

これまでは共通 API の互換性の代償として厚いドライバのレイヤーが存在し、
さらに性能向上により CPU が転送するコマンドの規模も大きくなりました。
Multi Core CPU が当たり前となっているにもかかわらず、
旧態依然とした OpenGL はスレッドによる最適化を阻んでいます。

ドライバのオーバーヘッドは、ゲーム専用機 (Game Console) と汎用機
PC/Smartphone の大きな違いの一つとなっていました。

●シンプルに

固定機能は段階的に減っていき、3D 描画に必要なアルゴリズムのほとんどが
アプリケーション側のソフトウエアで実装されるようになりました。
汎用化が進んで GPU の用途が広がると、既存の高度な機能やドライバの手厚い保護が
かえってプログラミングの自由を妨げることがあります。

GPU はもともと進化が早く、機能も性能も使われ方も短期間で変化してきました。
Desktop から Mobile に移っても同様です。
高度なレベルで統合された API は大きな変化についていくのが困難になります。
何でもやってくれる命令は便利ですが、ある程度使われ方が決まっていないと
仕様を決められないからです。
変更が頻繁に行われるほど設計はよりシンプルになり、依存を減らす方向に進みます。

Direct3D 10 では Shader の統合やリソースの Buffer 化といった改革が行われました。
実際に使ってみるとリソース管理は思ったより簡単ではない事に気が付きます。
Resource View には細かなフラグ設定が必要で、
慣れるまでは組み合わせの制限に悩まされました。
本当に自由に感じたのは Direct3D 11 の ComputeShader からです。
用途もデータの仕様もすべてプログラマが決められます。

Texture Atlas は複数のテクスチャを巨大なテクスチャに統合します。
中の配置をプログラマが自分で管理しなければなりませんが、
その代わりシェーダーは uv だけで好きなテクスチャを読み出すことが可能です。
もし仮に GPU のメモリ全部を巨大な 1 枚のテクスチャとみなすことができたら
uv はポインタとほとんど同じものになるでしょう。
現状リソース管理はプログラマに開放されていませんが、
Texture Atlas は制限を超えるための手法の一つとみなすことが出来ます。

OpenGL の Shader 命令は DirectX よりもかなり後にデザインされたものなので、
Uniform の配置やシェーダー同士のバインドも自動化されています。
OpenGL 3.x 以降はメモリ配置をプログラマが決められるようになり、
4.x 以降はシンボルのバインドも単純な番号指定に置き換わりつつあります。
Direct3D の低レベル命令に近づいています。

GPU は汎用性を増していますが、互換性を維持した積み重ねは
必要以上に複雑になってしまうことがあります。
新しい API では、CPU 負荷の低減と同時に
よりシンプルで使いやすい API への回帰も期待できます。

● Command Buffer

描画時に CPU が行っているのは Command Buffer の構築です。
いわば GPU (Command Processor) が実行するプログラムそのもので、
アプリケーションは毎フレーム動的にプログラムを生成していることになります。

ドライバはできるだけ GPU の性能を引き出すように作られているので、
無駄な Command を省くなどの最適化が行われます。
ハードウエア毎に構造が異なるので、GPU Native な形式への変換も必要になります。
GPU Command の生成と発行はそれなりにコストがかかります。

ステートの値が必要になるのは Draw のタイミングなので、
Command の生成は描画命令まで遅延します。
Draw 命令に負荷が集中して見えるのはそのためです。

各種 Buffer 化は Command 負荷軽減手段の一つとなります。
パイプラインステートだと描画のたびに Command Buffer に書き込まれますが、
Buffer なら予め転送しておいたリソースをアサインするだけで済むからです。

●ゲーム専用機 (Console)

ゲーム専用機では PC と事情が大きく異なります。

 ・互換性の枷がない
  ・ハードウエア互換性が不要 (ハードは単一)
  ・ソフトウエア互換性を持たない (SDK の上位互換性を持たない)
 ・必要に応じてより低レベルな最適化手段が用意されている
 ・ハード内部の情報がある程度公開されている

ゲーム専用機は数年サイクルでリフレッシュされ、互換性の確保には
専用ハードウエアまたはソフトウエアエミュレーションが用いられます。
そのためソフトウエア (SDK) 互換性があまり重要ではありません。

最初から GPU Native な形式を使用できることもあり、
CPU のオーバーヘッドも PC と比べるとかなり低くなっています。

必要に応じてより低レベルな最適化が可能なことも専用機の特徴です。

最近は少ないと思いますが、例えば GPU Command を直接操作できるなら
事前にモデルデータを GPU Native な Command 形式に変換しておくことが出来ます。
メモリに Buffer Data と Command Buffer をロードするだけで描画が可能となります。
動的な Command 生成と比べて CPU 負荷はほとんど生じません。
(ただしいくつかのトレードオフが発生するので必ずしも最善とはいえない)

ハードの内部構造がある程度公開されている点もプログラマの負担を減らしてくれます。
描画アルゴリズムの設計時に内部の仕組みがわかっていれば、
どの方法が効率が良いのかある程度判断できるからです。
あまり迷わなく済みます。

●互換性とこれから

・ゲーム専用機との差が小さくなる
・API の分裂

新しい API は今の CPU/GPU 性能と使われ方に合わせた再設計が行われます。
全体的な動作効率があがり、CPU オーバーヘッドの低減などパフォーマンス特性は
よりゲーム専用機に近づいていくものと考えられます。
その反面、現状ではプラットフォームごとに仕様が分断されており、
互換性においては新たな課題が残ります。

OpenGL ES 2.0 はモバイルからブラウザまでプラットフォームの枠を超えて
用いられており、統一された API として大きな意味を持っていました。
今後同じように OpenGL ES 3.0/3.1 が広く用いられるようになるのかといえば
必ずしもそうではないようです。
特に iOS の場合は Metal 対応デバイスと一致しているため、
性能や機能のために OpenGL ES 3.0 を選ぶメリットが無くなりました。

                                      ES2.0  ES3.0  Metal
----------------------------------------------------------
Apple A5/A6    PowerVR SGX543/554       Y      -      -
Apple A7       PowreVR G6430            Y      Y      Y

Android の ES 3.0 や Desktop の OpenGL 4.x と互換性を保つためには必要ですが、
性能や使いやすさを優先するなら Metal が選ばれる可能性が高まります。
用途に応じた使い分けが行われるでしょう。

とはいえ各種プラットフォームへの個別対応は大変です。
OpenGL なら Low Overhead Profile や Multi thread Extension のような、
プラットフォームを超えた新しい仕様が登場することを期待します。

関連ページ
3D Low overhead API

Android の新しい GPU BayTrail-T Intel HD Graphics

Bay Trail-T 搭載の Android 端末が発売されたので軽く調べてみました。
Android 端末に使われている GPU の種類に Intel HD Graphics が
新たに加わったことになります。

Qualcomm     Adreno
Imagination  PowerVR
NVIDIA       Tegra
ARM          Mali
Vivante      GC
Intel        HD Graphics   ← NEW

ASUS MeMO Pad 7 ME176

Adreno 320/330, Mali-T604, PowerVR G6430 (iOS) に続いて
入手可能な OpenGL ES 3.0 対応端末となっています。

対応している Extension は下記の通り。

// ASUS MeMO Pad 7 ME176 Android 4.4
// Atom Z3745 x86 RAM 1GB

GL_VENDOR: Intel
GL_RENDERER: Intel(R) HD Graphics for BayTrail
GL_VERSION: OpenGL ES 3.0 - Build eng.yunweiz.20140425.225700

GL_EXT_blend_minmax
GL_EXT_multi_draw_arrys
GL_SUN_multi_draw_arrys
GL_EXT_texture_filter_anisotropic
GL_EXT_texture_compression_s3tc
GL_EXT_texture_lod_bias
GL_EXT_color_buffer_float
GL_EXT_packed_float
GL_EXT_texture_rg
GL_INTEL_performance_queries
GL_EXT_texture_storage
GL_OES_EGL_image
GL_OES_framebuffer_object
GL_OES_depth24
GL_OES_stencil8
GL_OES_packed_depth_stencil
GL_OES_rgb8_rgba8
GL_ARM_rgba8
GL_OES_depth_texture
GL_EXT_color_buffer_half_float
GL_OES_vertex_half_float
GL_EXT_shadow_samplers
GL_OES_point_sprite
GL_OES_blend_subtract
GL_OES_blend_func_separate
GL_OES_blend_qeuation_separate
GL_OES_standard_derivatives
GL_OES_read_format
GL_OES_mapbuffer
GL_EXT_discard_framebuffer
GL_EXT_texture_format_BGRA8888
GL_OES_compressed_paletted_texture
GL_OES_ELG_image_external
GL_OES_compressed_ETC1_RGB8_texture
GL_OES_fixed_point
GL_OES_vertex_array_object
GL_OES_get_program_binary
GL_OES_texture_3D
GL_OES_texture_cube_map
GL_OES_fbo_render_mipmap
GL_OES_texture_float
GL_OES_texture_float_linear
GL_OES_texture_half_float
GL_OES_texture_half_float_linear
GL_OES_stencil_wrap
GL_OES_element_index_uint
GL_OES_texture_npot
GL_OES_texture_mirrored_repeat
GL_EXT_sRGB
GL_EXT_frag_depth
GL_APPLE_texture_max_level
GL_EXT_occlusion_query_boolean
GL_INTEL_timer_query
GL_ANGLE_texture_compression_dxt1
GL_ANGLE_texture_compression_dxt3
GL_ANGLE_texture_compression_dxt5
GL_EXT_texture_compression_dxt1
GL_OES_required_internalformat
GL_EXT_separate_shader_objects
GL_OES_surfaceless_context
GL_OES_EGL_sync
GL_EXT_robustness
GL_EXT_shader_texture_lod
GL_EXT_unpack_subimage
GL_EXT_read_format_bgra
GL_EXT_debug_marker
GL_KHR_blend_equation_advanced
GL_EXT_shader_integer_mix

対応している圧縮テクスチャフォーマットは ETC2/EAC, ETC1, DXT(S3TC) 。
DirectX11 世代の GPU なので機能面での心配はおそらく不要でしょう。

Android 向けには、BayTrail の他にも新 Atom (Silvermont core) SoC として
Z3400/Z3500 (Moorefield) が発表されています。
搭載 GPU は PowerVR G6400 となっており Intel HD Graphics ではありません。

Impress: Intel、22nmのスマートフォン向けAtom Z3400/3500を発表

実際に 2014/5/8 に au から Z3580 を搭載した MeMO Pad 8 が発表されています。
発売自体は 8月とまだ先です。

Impress: LTE対応の8インチタブレット「ASUS MeMO Pad 8」

Tablet            SoC   core clock   display    GPU
---------------------------------------------------------------------
MeMO Pad 7 ME176  Z3745  4  1.86GHz  1280x800   Intel HD Graphics 4EU
MeMO Pad 8 ME181  Z3745  4  1.86GHz  1280x800   Intel HD Graphics 4EU
MeMO Pad 8 (au)   Z3580  4  2.33GHz  1920x1200  PowerVR G6430

Full HD モデルに使われているのは PowerVR G6430 の方です。
多くの Windows Tablet 同様に ME176/ME181 の画面サイズは 1280×800 なので、
純粋な GPU 性能では Intel HD Graphics (4EU) よりも PowerVR G6430 の方が
高いのではないかと思われます。

SoC core CPU-clock  GPU                   GPU-clock  fop   GFLOPS
-----------------------------------------------------------------
Z3745  4  1.86GHz   Intel HD Graphics 4EU   778MHz    64     49.8
Z3580  4  2.33GHz   PowerVR G6430           533MHz   256    136.4

Wikipedia Atom (system on chip)
Intel Atom Processor Z3745 (2M Cache, up to 1.86GHz)

Android でも x86 端末は珍しくなくなりました。
CPU 自体は x64 にも対応しています。

おそらく今後 Android も 64bit に対応すると思われますが、
既存の端末に対して 64bit 版が提供されるかどうかは不明です。
ここしばらくは、購入するタイミングが判断しづらく悩ましい状態となりそうです。

関連エントリ
BayTrail vs Kabini (Celeron J1900 vs Athlon 5350)
コンパイル時間の比較 BayTrail
Atom Bay Trail の浮動小数点演算能力

Objective-C の Object を C++ で扱う (smart pointer)

iOS/OSX の API の多くは Objective-C の Interface となっています。
他のプラットフォームとのコード共有を考えるならば、
アプリケーション側はできるだけ普通の C/C++ で書きたいと思うかもしれません。
この場合 System の API 呼び出し部分を分離して、何らかの Wrapper 経由で
アクセスすることになります。

Applicaton     *.cpp : C++
Library header *.h   : C++ / Objective-C++
Library source *.mm  : Objective-C++

Wrapper Library のヘッダは C++ と Objective-C++ で共有されることになるので、
Objective-C の Object をそのまま記述することが出来ません。
ヘッダとソースで実装を分離して隠蔽すべきなのでしょうが、
扱う Class や Object が増えて結構手間がかかっていました。

おそらく一番簡単で確実な方法は、iOS/OSX の場合だけアプリケーション側の
.cpp (.cc) を Objective-C++ とみなしてコンパイルすることでしょう。

ですが、どうしても純粋な C++ としてコンパイルしたかったので、
C++ で Objective-C の Object を所有できるようにしてみました。

// IDPointer.h
class IDPointer {
    void*  iPointer;
public:
    IDPointer() : iPointer( NULL )
    {
    }
    ~IDPointer()
    {
        Clear();
    }
    void Clear();
#if __OBJC__
    void SetID( id obj )
    {
        Clear();
        iPointer= (__bridge_retained void*)obj;
    }
    id GetID() const
    {
        return  (__bridge id)iPointer;
    }
#endif
};
// IDPointer.mm
#import  "IDPointer.h"

void IDPointer::Clear()
{
    id  obj= (__brdige_transfer id)iPointer;
    obj= NULL;
    iPointer= NULL;
}

一見うまく動作するように見えますが、dealloc のタイミングが意図したものに
ならないことがあります。

void idpointer_test()
{
    @autoreleasepool {
      TempClass*  t0= [[TempClass alloc] init];
      IDPointer  ptr;
      ptr.SetID( t0 );

      TempClass*  p0= ptr.GetID();

      t0= NULL;
      p0= NULL;
      ptr.Clear();
      // --- (1)
    }
    // --- (2)
}

上の例は (1) ではなく (2) のタイミングで TempClass が dealloc されます。
原因は GetID() が id を返しているためで、ARC により autoreleasepool への
登録が行われているようです。
weak なポインタを返す場合、所有者の生存期間をコンパイラが保証できないため
autoreleasepool に委ねているものと思われます。
実際には autoreleasepool に入って欲しくないケースもあります。

IDPointer& operator=( const IDPointer& src )
{
    SetID( src.GetID() );
    return  *this;
}

例えば上のコードは SetID() が即座に retain しているため、
生存期間を考慮する必要がないのですが autoreleasepool に入ります。

void* のまま受け取ってから受け取り側が __bridge cast するか、または
__attribute__((ns_returns_retained)) をつけると autoreleasepool に
含まれないことがわかりました。

// IDPointer.h
class IDPointer {
    void*  iPointer;
public:
    IDPointer() : iPointer( NULL )
    {
    }
    ~IDPointer()
    {
        Clear();
    }
    IDPointer( const IDPointer& src );
    IDPointer& operator=( const IDPointer& src );
    void Clear();
#if __OBJC__
    explicit IDPointer( id obj )
    {
        iPointer= (__bridge_retained void*)obj;
    }
    void SetID( id obj )
    {
        Clear();
        iPointer= (__bridge_retained void*)obj;
    }
    __attribute__((ns_returns_retained)) id GetID() const
    {
        return  (__bridge id)iPointer;
    }
    IDPointer& operator=( id obj )
    {
        SetID( obj );
	return  *this;
    }
#endif
};
// IDPointer.mm
#import  "IDPointer.h"

IDPointer::IDPointer( const IDPointer& src )
{
    iPointer= (__bridge_retained void*)src.GetID();
}
IDPointer& IDPointer::operator=( const IDPointer& src )
{
    SetID( src.GetID() );
    return  *this;
}
void IDPointer::Clear()
{
    id  obj= (__brdige_transfer id)iPointer;
    obj= NULL;
    iPointer= NULL;
}

↑の場合は GetID() しても autoreleasepool に入ることがありません。

これで C++ 側でも比較的安全に Objective-C Object の所有や受け渡しを
行うことが出来ます。
Objective-C の Object の instance は ARC で管理されていますが、
C++ からは smart pointer (shared_ptr) として見えることになります。

使用例

// SampleAPI.h
class PlayerData : public IDPointer {
};
class Player {
    IDPointer  PlayerInstance;
public:
    void Init();
    void CreateData( PlayerData& data );
    void Play( PlayerData& data );
};
// SampleAPI.mm
void Player::Init()
{
    PlayerInstance= [MYPlayer newPlayer];
}

void Player::CreateData( PlayerData& data )
{
    data.SetID( [[MYPlayerData alloc] init] );
}

void Player::Play( PlayerData& data )
{
    id player= PlayerInstance.GetID();
    [player stop];
    [player playWithData:data.GetID()];
}

Chromecast で C++ アプリを走らせる。Emscripten のゲームを動かす

Chromecast 上でも Emscripten のプログラムが動きました。

chromecast_chiraks.jpg

少々ややこしいのですが、PC のブラウザで走らせた結果を、
Tab のミラーリング機能で TV に表示しているわけではないです。

Emscripten で作った JavaScript コードを、Custom Receiver として登録することで
Chromecast 上で走らせています。

速度は 10~15fps 前後。
Android 端末よりも遅いので、
Chromecast の CPU はあまり動作クロックが高くないようです。

● Chromecast とは

ほぼストリーム専用に特化したメディアプレイヤーです。

 ・Google Chromecast

端末の画面を TV にミラーリング表示するような、リモートモニタアダプタとは異なっています。

インターネットへの接続機能を持っており、基本的には Chromecast は
メディアプレイヤーとして単独で動作します。
ただし本体にリモコンやコントローラを繋ぐことはできず、UI を持っていません。
必ず PC や Android/iOS 等の他の端末経由で操作を行うことになります。

一般のメディアプレイヤー BOX / スティック との違いは下記の通り

・ストリーム向けなのでストレージを必要としない。SD カードスロットもない
・リモコンを持たない。USB/Bluetooth/IR 等の周辺機器をつなぐことができない

ストレージ含めて周辺機器も不要にしたことでハードウエアはシンプルです。
UI もいらないため Android を搭載したメディアプレイヤーよりも性能を必要とせず
コストダウンを可能にしているようです。

● ハードウエア

 ・iFixit Chromecast Teardown
 ・ARM Marvell SoCs

分解記事より Marvell 88DE3005-A1、↑ARM Marvell SoCs より Cortex-A9。
↓Amazon の Fire TV の比較表でも Single core の RAM 512MB と記載されています。

 ・Amazon Fire TV

動作クロックや GPU は不明。
同様のコンセプトだった Nexus Q と比べてもかなり控えめなスペックとなっています。

● Chromecast のアプリケーション

Chromecast は Web Browser (Chrome) が内蔵されており、
Web Application を走らせるプラットフォームとして機能しています。
ただし自由に Web page を表示させることはできず、
登録した Web Application (Receiver App) しか開けないように制限されています。

また Chromecast 本体だけでは何も操作できないので、
PC/Android/iOS 側にも操作用のアプリケーションが必要になります。

(1) Receiver Apps : Chromecast 上で走る Web Application (HTML+JS)
(2) Sender Apps : 外部から操作するためのアプリ (PC Chrome/Android/iOS Apps)

基本的には (1) と (2) の組み合わせで出来ています。
他にも Web Application として (1) を設置するための Web サーバーが要ります。

● PC の Tab ミラーリング

PC の Chrome ブラウザに拡張機能を追加すると、ブラウザの表示内容を
Chromecast に送信できるようになります。
アイコンから「このタブをキャスト」ボタンを選択。

CPU 負荷が一定分増加することからも、動画として Web Page の内容を送信しているようです。
PC がストリームサーバーを兼ねていることになります。

任意の Web を制限なく表示できますが、Chromecast が直接ネットにアクセスして
レンダリングを行っているわけではありません。

● リモコンだけのアプリ (Default Receiver)

メディアプレイヤーなどのよく使われる Receiver (1) は、デフォルトのものが用意されています。
そのためアプリケーションによっては、(2) のリモコン側を作るだけで機能を実現することができます。
この場合登録は不要。

● Chromecast の上で動くアプリ (Custom Receiver)

Chromecast 上で走る (1) の Receiver アプリを作るには登録が必要になります。

 1. Receiver / Sender アプリを作成
 2. Receiver を Web サーバーに転送する
 3. Developer Console で 2. の URL を登録して ID を受け取る
 4. 開発に使う Chromecast のシリアルを登録して Developer mode にする
   (Remote Debug できるようになる)

以下テスト実行までの手順

◎ Receiver アプリ URL の登録

1. Developer Console に登録&ログイン (5ドル必要)
 ・Google Cast SDK Developer Console
2. [ADD NEW APPLICATION] で Custom Receiver を選択
3. 名前と Receiver App を設置した URL の登録
   例 http://~/receiver_chiraks.html
4. Application ID が発行されるので、リモコン側 (2) のアプリケーションに ID を書き込む

◎ Chromecast Device の登録

1. Developer Console の [ADD NEW DEVICE] でシリアルナンバーを登録する。
 ・本体の裏面上部の文字列、または箱のシールにある SN の文字列
2. Device 登録には時間がかかるので 15分待つ。
3. “Ready For Testing” になったら完了

◎ Developer mode の確認

1. Chromecast の電源を一旦落とす。
2. Chromecast を再び起動して PC 上の Chrome ブラウザから下記 URL を開く
  http://ChromecastのIPアドレス:9222
3. もし開けない場合は、再び電源を落として時間をおいてからやりなおす。

“Inspectable WebContents” と表示されたら成功で、
Console の log を確認したり Remote Debug できるようになります。

◎ アプリの実行

Sender を起動して登録した Chromecast Device に接続

とりあえず Chromecast のようなデバイスでも
Emscripten でプログラムを作れることがわかりました。

関連エントリ
Emscripten C++/OpenGL ES 2.0 (9) C関数呼び出しと FileSystem
Emscripten C++ のアプリをブラウザで動かす (8) iOS でも動く
Emscripten C++/OpenGL ES 2.0 (7) Emscripten の OpenGL API と WebGL
Emscripten C++/OpenGL ES 2.0 (6) Chrome の速度と IE11/Safari
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (5)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (4)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (3)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (2)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (1)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす 一覧

Emscripten C++/OpenGL ES 2.0 (9) C関数呼び出しと FileSystem

● JavaScript から C関数を呼び出す

ccall() を使います。
(C/C++言語から JavaScript Lib を呼び出す方法は こちら)

#include  
#include  

extern "C" void myfunc01()
{
    printf( "MYFUNC01\n" );
}

int main()
{
    EM_ASM(
        // C言語関数の呼び出し
        ccall( 'myfunc01', 'v' );
    );
    return  0;
}

C/C++側から参照がない関数はオプティマイザが削除してしまうので
そのままだと上のプログラムはエラーになります。
コンパイル時に下記のオプションが必要です。

-s EXPORTED_FUNCTIONS="['_main','_malloc','_myfunc01','_myfunc02']

EXPORTED_FUNCTIONS で個別に関数名を指定するか、または

-s EXPORT_ALL=1

を指定します。

C言語の symbol なので内部表現では先頭に ‘_’ が付きます。
C++ 内で定義する場合は extern “C” が必要な点に注意。

● 引数の指定と cwrap

ccall 2番目の引数 ‘v’ は戻り値の型を意味しています。
例えば文字列を返す関数の場合 ‘string’ を与えます。

#include  
#include  

extern "C" const char* myfunc01()
{
    return  "MYFUNC_01";
}

int main()
{
    EM_ASM(
        // C言語関数の呼び出し
	console.log( ccall( 'myfunc01', 'string' ) );
    );
    return  0;
}

‘string’ は自動的にオブジェクトに変換されます。

引数を渡す場合も型の情報が必要です。

#include  
#include  

extern "C" const char* myfunc02( int a, float b, const char* c )
{
    printf( "%d %f %s\n", a, b, c );
    return  "MYFUNC_02";
}

int main()
{
    EM_ASM(
        // C言語関数の呼び出し
	console.log( ccall( 'myfunc02', 'string', ['number','number','string'], 12, 34.5, 'abcdefg' ) );
    );
    return  0;
}

文字列引数はポインタに変換するために、一旦内部の仮想メモリ空間に
展開する必要があります。
JavaScript から呼び出した場合、この領域として stack が用いられています。

戻り値   引数        C言語
---------------------------------------------
string   string      const char*
number   number      int/float 他 (int64以外)
v        --          void

頻繁に呼び出す場合は cwrap() で関数に変換できます。

var myfunc02= cwrap( 'myfunc02', 'string', ['number','number','string'] );

var ret_str= myfunc02( 345, 67.8, 'hijklmn' );

● IDBFS ファイルシステム

ブラウザのローカルストレージに保存可能なファイルシステムです。
任意の場所に mount 可能で、path + filename がそのまま key になります。
通常は仮想 FS に対して読み書きを行うので IDB との同期が必要です。

FS.syncfs( true, callback );     読み込み IDB → Virtual FS
FS.syncfs( false, callback );    書き込み Virtual FS → IDB
void save_func()
{
    FILE*  fp= fopen( "/save/data.txt", "w" );
    fputs( "ABCDEFG", fp );
    fclose( fp );
    EM_ASM(
	// 書き込み (false は省略できる)
        FS.syncfs( false, function(err){ } );
    );
}

// EXPORTED_FUNCTIONS に '_load_func' を加える必要あり
extern "C" void load_func()
{
    // 読み込みが完了したらアクセスできる
    EM_ASM(
        var  text= FS.readFile( '/save/data.txt', {flags:'r',encoding:'utf8'} );
        console.log( text );
    );
}

static void ems_loop()
{
    static int  count= 0;
    if( ++count == 60 * 3 ){
        save_func();
    }
}

int main()
{
    EM_ASM(
        FS.mkdir( '/save' );
        FS.mount( IDBFS, [], '/save' );

        // 読み込み
        FS.syncfs( true, function(err){
            ccall( 'load_func', 'v' );
        });
    );

    emscripten_set_main_loop( ems_loop, 60, true );
    return  0;
}

callback を使うので mail_loop が必要です。
いろいろ試していますが通常の C関数での読み込みがうまく行っていません。
JavaScript からは可能でした。

● NODEFS

ブラウザのように制限が無いため、node command の場合は NODEFS を
使って直接ローカルファイルにアクセスできます。

EM_ASM(
        FS.mkdir( '/save' );
        FS.mount( NODEFS, {root:'.'}, '/save' );
    );

mount 時の root option が実際の File System の Path 指定になります。
syncfs は不要。

● SOCKFS

read, write, open, close 等の標準 API で socket にアクセスできるようにするため、
Socket API は FileSystem の一種として実装されているようです。
実装上の内部的なものであって特に使用する必要はありません。

● 独自ファイルシステムを作る

MountType は FileSystem Object なので、
自分で FileSystem を作ることも可能です。

// chafs.js
mergeInto( LibraryManager.library, {
    chafs_init: function(){
        Module.CHAFS= {
            createNode: function( parent, name ){
                var node= FS.createNode( parent, name, 511 ); // 0777
                node.node_ops= {
                    lookup: function( parent, name ){
                        return  Module.CHAFS.createNode( parent, name );
                    },
                };
                node.stream_ops= {
                    open: function( stream ){
                    },
                    close: function( stream ){
                    },
                    read: function( stream, buffer, offset, length, position ){
                        // buffer を 'A' で埋めるだけ
                        for( var i= 0 ; i< length ; i++ ){
                            buffer[offset + i]= 65;
                        }
                        return  length;
                    },
                };
                return  node;
            },
            mount: function( mount ){
                return  Module.CHAFS.createNode( null, '/' );
            },
        };
    },
});

chafs_init() で FileSystem を作成しています。

// main.cpp
#include  
#include  

extern "C" void chafs_init();

static void load_func()
{
    FILE*   fp= fopen( "/save/data.txt", "r" );
    char    buf[128];
    size_t  size= fread( buf, 1, 10, fp );
    buf[size]= '\0';
    fclose( fp );
    printf( "%s\n", buf );
}

int main()
{
    chafs_init();  // 作成

    EM_ASM(
        FS.mkdir( '/save' );
        FS.mount( Module.CHAFS, [], '/save' );
    );
    load_func();
    return  0;
}

この例では CHAFS をマウントした '/save' 以下どのファイルを読み出しても、
すべて 'A' で埋められています。
ただし getattr を定義していないのでファイルサイズを取ることはできません。

関連エントリ
Emscripten C++ のアプリをブラウザで動かす (8) iOS でも動く
Emscripten C++/OpenGL ES 2.0 (7) Emscripten の OpenGL API と WebGL
Emscripten C++/OpenGL ES 2.0 (6) Chrome の速度と IE11/Safari
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (5)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (4)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (3)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (2)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (1)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす 一覧