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

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

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