2015年5月22日金曜日

Bufferに注意!

プログラムのことを探すとみんな「バッファーが・・・」とか「このBuffer」にはとかよく使うけど、
そもそもBufferってなんだ?
なんとなく読んでいて「あーメモリーのことを言っているんだね」とか
「何か保存する場所なんだ」って感じがしますね。

バッファーを辞書で探してみると(英語)
a person or thing that prevents incompatible or antagonistic people or things from coming into contact with or harming each other.
・・・・まったくわかりませんね。

日本語辞書でみると
1(鉄道車両などの)緩衝器[装置].
2緩衝物.
3緩衝国.
4【電子計算機】 バッファー.
・・・・これも意味わかりませんね。

まぁー特に大した意味もないので、何となくわかればいいと思います。
みんなそういう意味で使ってるんだって感じで。

で、「注意」とタイトルに書いたんですが、いったいどういうことなのかみてみましょう。
ここではバッファーを「メモリ領域」の意味で使おうと思います。
CとC++にはいろんなメモリコピーの関数(機能)があります。
memcpyとかstrcpyとか・・・それらを使う上で、バグったりしたことはありませんか?
プログラムが止まったり、落ちたり、変な動きをしたり、いろいろとあると思います。
それについて考えてみましょう。

多くのバグはまさにバッファー管理の誤りで起きます。例えば、初期化がされなかったり、保存できる領域を超えたりすることが代表的なものですが、この保存できるところを超えるとどうなるかを詳しくみてみましょう。

バッファーって保存する領域なのにこの領域を超えて保存しようとしたらどうなるのか?
どう思いますか?

実はOS側でこれは「ルール違反」だとみなして、プログラムを強制的に止めます。

たまにプログラムを書いて実行してみたら、すごい警告ポップアップが出て「デバッグ」「再実行」のボタンがあるのをみたことありますか?
この警告ですが、大体メモリルール違反で出る警告です。

じゃーなんでこれをルール違反だとみなすか?

例えば、メモリーにこんな感じでプログラムがあるとします。
Aプログラム「1|2|3|1|3|2」Bプログラム「3|1|2|1|2|3」
Aプログラムの一番後ろのマスから3マスデータを書き込むとします。

3|1|2とか書いたらどうなりますかね?

Aプログラムと全然関係のないBプログラムのデータが書き換えられますね?
それではBプログラムがバグったり、落ちたりする問題が起こります。
これじゃ、Bプログラムを作った人があまりにも可哀そうじゃないですか。
それで、こういうことを禁止にしたんです。
この問題はプログラム同士でも変数同士でも起きます。

理由もなく変数の値が変わったりしたら、この問題の可能性が高いです。

で、なんでこんなことが起きるのか?
変数にちゃんと代入したらいいだろうが!って思いますよねw
実は初心者のプログラムではあんまりこの問題が起きません。
でも初心者から抜け中級者になるころぐらいにこの問題が多く起きます。

例えば、

int a[20];
for(int i = 0; i <= 20; i++){
a[i] = 0;
}

このようなことが多くないでしょうか。で、一生懸命探して直すのに他でバグったりしますね。
実はこのようなバグとかエラーを直すことはすごくいい経験です。
まだ、勉強するときにできるだけ多くのエラー、バグ、クラッシュ、警告に合ってくださいw

変に聞こえるかもしれませんが、これもまた経験です。
プログラムは「かけた時間」ですべてが決まります。
かけた時間つまり多くのプログラムを組んでみると多くのエラーとバグと遭遇するでしょう。
それらを直してみることであなたは確実にレベルアップできます。

実際、会社では一人ですべてのプログラムを書く場合は多くありません。
他人が書いたものを使う場合も多いでしょう。あなたのプログラうムが完璧だとしても、ほかの人のプログラムが問題を起こす場合も多くあります。
その時、まさにこのような経験が力を発揮します。
覚えてください。バグ、エラー等を直すことは絶対無駄にはなりません。

まぁー雑談交じりの内容だったのですが、すごく重要な部分です。次はメモリーについてもっと詳しく書いてみようと思います。

2015年5月20日水曜日

3D変換について

要点だけ抑えてみましょう。3D レンダリング(描画)とは3D っぽくみせることです。
例えば前後に動いている板があなたの前にあるとしましょう。

あなたはどう奥行き感じますか?

奥に行くと小さくなるように見えて、手前に来ると大きくなるように見えるでしょう。
パソコンの中の3D 表現とはまさにそういうことです。

とあるオブジェクトがカメラの前に近づくと大きくみせ、遠ざかると小さくみせるだけです。

え?じゃーどうやって?
この二つの絵をみてみましょう。実はすごくシンプルな考え方なんです。


 



真ん中のモンスターの大きさを変えるだけで、手前に来てるような感じがしますね。
(もちろん比較対象が少なすぎて遠近感があんまり感じられないんですけどw)
で?具体的にどんな変換があったのかみましょうか?

このキャラクターの3Dでの左上座標をみてみましょう。

左はXYZ -0.5, 0.5, 1.0
右はXYZ -0.5, 0.5, 5.0

Z座標が変わっていますね。
でも、これだけじゃよく仕組みがわかりませんね。では四角で囲ってみましょう。
で、それの画面上での左上の点の座標を比較してみましょう。

左のほうはX=243.00 Y=163.00で
右のほうはX=294.00 Y=214.00です。

どうでしょうか?なんかわかりますか?実は3D 描画というのは
「最終的に画面のどの位置にどのサイズで描画するか」を演算する処理を言います。
われわれが見ているパソコンのディスプレイは平面ですね。そこに奥行などありません。
ただ平いだけです。

つまり
3D座標→あらゆる変換用の数学式にかける→画面の2D座標
というシンプルな(変換用の数学式を誘導するのはかなり複雑です)ものです。
Openl GLes系はこの機能がついてませんが、DirectXは親切にすべての機能が揃えてあります。
これをネットで調べてみるとものすごい難しく説明します。(あらゆる数学の式で表現してあるので見るだけで嫌になりますw)

実は3D表現をするだけならその式を全部知る必要はありません。
最近のライブラリは優秀なので、あらかじめデバイスの中に
その機能を備えてあるものがほとんどです。

もちろん、高度なことをやろうとすれば、全部わかる必要はありますが、
「んなことじゃなくてとりあえずやってみたいんだ」って方は
今使っているデバイスでのやり方だけを調べてその関数を使うだけでOKだと思います。

例えば、DirectX9では

D3DXMATRIX World;
D3DXMATRIX Offset;
D3DXMATRIX View;
D3DXMATRIX Persp;
D3DXMatrixIdentity( &World ); //とりあえず、World行列をリセット

D3DXMatrixTranslation( &Offset, 描画したい位置);
D3DXMatrixMultiply( &World, &World, &Offset ); //描画したい位置をセット
D3DXMatrixLookAtLH(
                  &View,
                  &D3DXVECTOR3(カメラ位置),
                  &D3DXVECTOR3(0, 0, 0),
                  &D3DXVECTOR3(カメラが見ている方向)
                  );//カメラをセット
D3DXMatrixPerspectiveFovLH( &Persp, D3DXToRadian(視野角), 画面の横サイズ/画面の縦サイズ, 1.0f, 10000.0f);//視野角をセット

//描画デバイスにオブジェクトの描画位置を教える
描画デバイス->SetTransform(D3DTS_WORLD, &World);
//カメラ情報を教える。
描画デバイス->SetTransform(D3DTS_VIEW, &View);
//視野角情報を教える
描画デバイス->SetTransform(D3DTS_PROJECTION, &Persp);

描画式

上のやつで描画されます。Open GL自体にはD3DXMatrixTranslationとか
D3DXMatrixMultiplyとかD3DXMatrixLookAtLHとかD3DXMatrixPerspectiveFovLHとか
こういう便利な関数がないのですが、使っているデバイス(例えばiOS, Android)の
デバイスライブラリでこの機能があると思いますので、似たような名前か似たような
機能がありましたらそれを上のものに置き換えたら3D描画ができます。
SetTransformとかはデバイス依存の関数なのでこれも変えないといけませんねw

2013年4月7日日曜日

自作OpenALライブラリー!

自作OpenALライブラリを作りました。

OpenALライブラリの構造
1. AL_Attacthment デバイスとコンテキスト生成。
2. AL_Sound スタティックサウンド再生。
3. AL_Stream ストリームサウンド再生。

そして、ロード部
LOAD_WAVEクラス

機能動作はこのような流れで行きます。

Load_Waveクラスはバッファ生成やデータの読み込みを手伝ってくれます、
Load_Wave::fmt_headerと Load_Wave::SOUND_DATAを使いデータバッファを確保し、サウンドデータ形式や実データを読みこみます。

このデータを
AL_Sound型やAL_Stream型に流し込むことによって再生されます。

AL_Streamはまだラッピングが足りなくてむき出しのところや荒いところがただありますが、このように構成されています。

ヘッダー

#pragma once
#include "LOAD_WAV.h"
#include "AL_Attacthment.h"

class AL_Stream
{
private:
 unsigned int m_Buffer_size;
 unsigned int m_Buffer_cnt;

 ALuint* m_Buffers;
 ALuint m_Source;
 LOAD_WAV::WAVEFILE m_file;
 LOAD_WAV::fmt_header m_format_data;
 LOAD_WAV::SOUND_DATA m_Sound_data;
 
protected:
 static void Set_SoundBuffer(ALuint buffer, LOAD_WAV::SOUND_DATA data, unsigned int data_size, const LOAD_WAV::fmt_header* format_data);

public:
 AL_Stream(void) : m_Buffer_cnt(0), m_Buffer_size(0), m_Buffers(0), m_Source(0), m_file(0){}
 ~AL_Stream(void){};

 void Create_SoundBuffer(unsigned int cnt, unsigned int size);
 void Release(void);
 void Rewind_file(void);

 void Play(void);
 void Set_Wave_file(LOAD_WAV::WAVEFILE file, const LOAD_WAV::fmt_header* fmt);
};




実装部
#include "AL_Stream.h"


void AL_Stream::Set_SoundBuffer(ALuint buffer, LOAD_WAV::SOUND_DATA data, unsigned int data_size, const LOAD_WAV::fmt_header* format_data)
{
 ALenum sound_format = NULL;

 if(format_data->m_Channels == 1){
  sound_format = AL_FORMAT_MONO8 + format_data->m_BitsPersample / 16;
 }else if(format_data->m_Channels == 2){
  sound_format = AL_FORMAT_STEREO8 + format_data->m_BitsPersample / 16;
 }

 alBufferData(buffer, sound_format, data, data_size, format_data->m_SamplerPersec);
}

void AL_Stream::Create_SoundBuffer(unsigned int cnt, unsigned int size)
{
 m_Buffer_cnt = cnt;
 m_Buffer_size = size;

 m_Buffers = (ALuint*)malloc(sizeof(ALuint)*m_Buffer_cnt);
 m_Sound_data = LOAD_WAV::Create_SoundBuffer(m_Buffer_size);

 alGenBuffers(m_Buffer_cnt, m_Buffers);
 alGenSources(1, &m_Source);

 
}

void AL_Stream::Release(void)
{
 alSourceStop(m_Source);
 LOAD_WAV::Release_SoundBuffer(m_Sound_data);
 alDeleteSources(1, &m_Source);
 alDeleteBuffers(m_Buffer_cnt, m_Buffers);
 
 if(m_Buffers != NULL){
  free(m_Buffers);
  m_Buffers = NULL;
 }
}

void AL_Stream::Rewind_file(void)
{
 fseek(m_file, m_format_data.m_Data_Position, SEEK_SET);
}


void AL_Stream::Play(void)
{
 ALint buffer_processed = 0;
 ALint source_stats = 0;
 alGetSourcei(m_Source, AL_BUFFERS_PROCESSED, &buffer_processed);
 alGetSourcei(m_Source, AL_SOURCE_STATE, &source_stats);

 if(source_stats == AL_STOPPED || source_stats == AL_INITIAL){
  alSourcePlay(m_Source);
 }

 while (buffer_processed > 0){
  ALuint unqued_buffer = NULL;
  alSourceUnqueueBuffers(m_Source, 1, &unqued_buffer);
  LOAD_WAV::Get_SoundBuffer(m_file, m_Sound_data, m_Buffer_size);
  AL_Stream::Set_SoundBuffer(unqued_buffer, m_Sound_data, m_Buffer_size, &m_format_data);
  alSourceQueueBuffers(m_Source, 1, &unqued_buffer);
  buffer_processed--;
 }
}

void AL_Stream::Set_Wave_file(LOAD_WAV::WAVEFILE file, const LOAD_WAV::fmt_header* fmt)
{
 m_file = file;
 memcpy(&m_format_data, fmt, sizeof(LOAD_WAV::fmt_header));

 for(unsigned int i = 0; i < m_Buffer_cnt; i ++){
  LOAD_WAV::Get_SoundBuffer(m_file, m_Sound_data, m_Buffer_size);
  AL_Stream::Set_SoundBuffer(m_Buffers[i], m_Sound_data, m_Buffer_size, &m_format_data);
 }

 alSourceQueueBuffers(m_Source, m_Buffer_cnt, m_Buffers);
}


ストリーム再生で意外とてこずる方が多かったので、そのような方たちに参考になれるよう全ソース公開しますw
これが参考になればいいですけどww

かなり荒いソースだし、コピペーだけじゃ使えないですけど、読む方によっては参考になれると思います。


マルチスレッドとパフォーマンス改善の関係性


ここ数日間スレッドを使って遊んでみました。

マルチスレッド化がパフォーマンス改善にどれくらい影響をするのか気になったりして、過去にはWin32 APIのスレッド機能を使ってテストしてみたりしたんですが、
今回はboostをインストールしたので、boost::threadを使い遊んでみました。

多分、皆さんが一番気になることって、マルチスレッドってパフォーマンス改善に繋がるの?そして、どれくらい改善できる?というところだと思います。
その答えは改善できる可能性が高いということです。そして、改善の程度はスレッドに与える仕事によります。

なぜ、可能性という言葉を使ったのか。それは改善できるかもしれないし、改善できないかもしれないということなので使わせてもらいました。
正直なところ、マルチスレッド化ということがパフォーマンス改善に直接繋がるわけではない気がします。
あくまでも、Aという処理をしている間プログラムを止まらせたくないというところでマルチスレッドは使われるわけです。
今やデュアルコアなんか当たり前でクアッドコアやオクタコアまで市販されています。

このコアを分ける流れは昔の単一コアの処理スピードを上げることが限界に達することによってできたのです。各コアの処理スピードは少し落として、そのコアたちを複数個乗せたほうが処理スピードを上げられるという概念でマルチコアCPUが作られ始めたわけです。


ちなみに、昔単一コアの処理スピードが3.0Ghzを超えたとき、CPUを限界までこき使う技術「ハイパースレッド」(仮想マルチコア化技術)が開発されたりしたこともありました。w

マルチコアということが当たり前になり、大量の仕事を分けて処理した方が早いという概念もさらに加速化されることになります。

当たり前な考え方ですが、これを現実にたとえてみるとマルチスレッドとパフォーマンスの関係がさらに理解しやすくなります。

例えば、
手紙に切手を貼る仕事が与えられてこれをこなしていくと考えてみましょう。
5000枚が超える量を二人でわけるとき

Aというメインの人が手紙をとるとき2枚ずつとり、サブのBに分けてあげるとことにすると、この仕事の効率は極端にわるくなります。

逆にAという人は「一人でやったほうが早いわ」と言うでしょう。

では、最初から2500枚ずつ分けると、AとBの負担も減りつつ仕事も速くなる可能性があります。

ここでも「可能性」という言葉が出てきました。
なぜなら、Aが仕事を先に終わらせてもBが終わらないとBを待つしかありません。
(Bが極端に切手をはることが苦手という前提ですがw)

このようなことから考えてみると

適度な人数に適度な仕事をわけてやることを前提にマルチスレッドというものはその力を発揮できるということになります。

では、さらにこのマルチスレッドを有効に使う方法は何があるか考えてみましょう。
これも理解しやすくするために現実に例えていきたいと思います。

飯屋があるとします。
この飯屋の店長は料理も早く、そしてうまい。
かつ食器洗いやそのた雑務もはやくこなせる人です。

この店は店長一人ですべてをやればいいのか?
その答えは「仕事の量による」ということになります。

1.客数が少ない場合だと、一人でこなしたほうが良いに決まっている。
2.客数が多い場合はアルバイトを雇って使ったほうがいい。

ということはすぐ思いつきます。

では、2の場合
バイトくんに料理をやらせるとどうなるか?
バイト君がなにか調理師の資格をもってない限り、飯の味は悪くなり、店の評判はおちるでしょう。

つまり、仕事というものは優先順位というものがあるということです。
飯屋というものはご飯の味が第一で、この能力に優れている人間がやらなければいけません。

このことから順位をつけてみると・・・
1.      料理
2.      食器洗い、その他の雑務
(順位付けが適当ですが、気にしないでくださいw)

つまり、一番大事なことは店長がやり、その他の雑務はバイトに任せたほうが効率的には良いはずです。

これをゲームプログラムに当てはめてみましょう。
例えば、
1.ゲームのルールの処理
2.敵がいる場合AIの処理
3.ビジュアルエフェクト等・・・
の順だったら、
メインスレッドは1を処理します。
2は時と場合によって分担しましょう。
3は動きだけの雑な処理なので完全にサブスレッドに預けます。

これをメインスレッドで全部こなすと
どうでもいい3の処理が少し遅延しただけで、1や2の処理が遅延する恐れがあります。
ゲームになんの影響もないオブジェクトの処理で処理落ちをおこすわけなんです。

このようなことは避けたい!

3のような仕事をメインの処理をする前に別スレッドに預けて大事なことはメインでやることで、処理遅延は避けられます。

つまり、マルチスレッドというものは

そのプログラムの設計やシステムに密接にかかわるもので単にマルチスレッド化して良いものではありません。

2013年3月29日金曜日

マージソートのパフォーマンス

void main(void)
{
 //システム時間を計る計測器(クロック数で計算)
 B_Counter* sys_counter = new B_Counter();
 
 //配列を用意する
 int x[MAX_DATA];
 for(int i = 0; i < MAX_DATA; i ++){
  x[i] = (int)rand();
 } 

 //カウンター開始
 sys_counter->Counter_Start();
 
 //ソート開始
 merge_sort(x, 0, MAX_DATA-1);
 
 //カウンター終了
 sys_counter->Counter_End();
 
 //時間出力
 printf("%f \n", sys_counter->Get_Time());


 int c[MAX_DATA];

 for(int i = 0; i < MAX_DATA; i ++){
  c[i] = (int)rand();
 }

 sys_counter->Counter_Start();

 //一番単純なソートシステム
 for(int i = 0; i < MAX_DATA - 1; i ++){
  for(int a = i+1; a < MAX_DATA; a ++){
   if(c[i] > c[a]){
    int data = c[i];
    c[i] = c[a];
    c[a] = data;
   }
  }
 }
 sys_counter->Counter_End();

 printf("\n");
 
 printf("%f \n", sys_counter->Get_Time());
}

上記のようなソースでソートアルゴリズムをテストしてみました。

使っているアルゴリズムは二つで
1.マージソート。
2.アルゴリズムでも何でもない単純比較ソート。

その結果は・・・

・配列を20個用意した場合
マージソート : 0.008979
単純ソート : 0.004489

20個ソートしているのに何でここまで時間がかかるんだよって疑問を持ってる方もいらっしゃると思いますが、
そこんとこはおいといて時間をご覧ください。

なぜかマージソートのほうが時間がかかっています。
この時点では・・・マージソートって効率悪いなーって思ってましたが、配列の個数を増やしてみたら・・・

・配列を200個用意した場合
マージソート : 0.085299
単純ソート : 0.300150

200個ソートした時点でかなりの差が見られました。
明らかにマージソートのほうが速いですね。

・配列を2万個用意した場合
マージソート : 12.141984
単純ソート : 3484.902047

この時点ではもう相手にならないですねw
マージソートの特徴かもしれませんが、ソートする量によって時間も比例しますが、
大量のデータのソートに適してるという結果がみられました!

かなり有意義なスピード比べでしたねーw(知らなかったのは自分だけかもしれませんがw)

2013年3月26日火曜日

ビルボードとソートとパーティクル

パーティクル処理は処理的なところはさほど難しいものではない。
でも、いろんな問題が起きる。

その中で一番大変なところはビルボードの前後関係をみてソートを行わないといけないところだと思う。

例えば、ソートを行わないと



.......なぞの状態になる。
αブレンドというのは描画バッファのデータを元に演算を行う処理なので、描画を行うとき後ろのデータがないとこうなるのである。
さらにデップステストを行っているので、隠れているオブジェクトは切り取られてしまってビルボードの形が見えてしまっている。

要はデップステストをきると塗りつぶしになるので、デップステストを切ってみた。


.............確かにかくかくはなくなってはいるものの、前後関係が完全におかしい絵になってしまった。
なので、結局ソートをすることになった。
ソートを行って、描画してみると


......確かに前後関係も正しくなっているし、ビルボードの形も見えなくはなっているけど、カメラを少し動かしたりするとちらつく問題がまだあった。

描画位置(ワールド座標)でのソートなので、傾いたりすると頂点同士の重なり合いでちらつく問題が発生するのである。
最終的にはソートを行った後、デップステストを切って描画することにした。


綺麗な描画になっているのがわかる。
前後関係の問題もちらつく問題もなくなり、求めた形として描画されるようになった。

実際の動画です



2013年3月9日土曜日

ソース表示テスト!

#pragma once
#include 
#include 

class LOAD_WAV
{
private:
 typedef unsigned char u_char;
 typedef unsigned int u_int;
 typedef unsigned short u_short;

 struct fmt_header{
  u_short m_FormatID;
  u_short m_Channels;
  u_int m_SamplerPersec;
  u_int m_BytesPersec;
  u_short m_Blocksize;
  u_short m_BitsPersample;
 };

 struct chunk_data{
  u_char m_Chunk_tag[4];
  u_int m_Chunk_size;

 };

public:
 
 LOAD_WAV(void);
 ~LOAD_WAV(void);

 class Wav_Data{
  friend class LOAD_WAV;
 private:
  fmt_header format_data;
  void* pSound_data;
  size_t Data_size;
 public:

  const void* Get_SoundData(void) const
  {
   return pSound_data;
  }

  size_t Get_SoundData_Size(void) const
  {
   return Data_size;
  }

  const fmt_header* Get_FormatData(void) const
  {
   return &format_data;
  }
 };

 static void Load_Wave(const char* file_name, Wav_Data* pOut_data);
 static void Release_Wave(Wav_Data* pOut_data);
};


Waveファイルの読み込みクラスのヘッダーファイルですー
ソースコード表示チェックなんです。

(でも、ヘッダーだけなんですけどなんか恥ずかしいですねw)