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)