2012年01月24日

無駄の撲滅

こんにちは、ケイタです。
先週東京で初雪が観測されましたが、
昨晩は積もるほどの雪が降りました雪

そのおかげで今朝は雪化粧ぴかぴか(新しい)された東京の街を見ると言う珍しい体験が出来ましたわーい(嬉しい顔)
今は雪はすっかり止んでいますが、まだ路面は凍結しているところが多いので、
夜になって視界が悪くなり、気付かずに滑って転んだりしないように注意が必要そうですふらふら

さて、ゲーム機のCPUのスペックが上がるのと同様に、GPUの性能もどんどん上がってきていますが、
それでもゲームで表現したいグラフィックスに性能が及ばず、GPUの処理をオーバーしてしまう場合が多々ありますがく〜(落胆した顔)
そこで今回はGPUへの処理負荷軽減の基本的なテクニックを紹介しようと思います手(グー)

紹介するテクニックを実行しても、必ずGPUへの処理負荷が軽減されるとは限りません。予めご了承ください。

2010-10-14-0.png


1何も描画しないのが最速
早速身も蓋もありませんが、プログラムを書かなければバグは発生しないと同様に、
何も描画しなければGPUへの負荷はほぼ0となりますグッド(上向き矢印)

もちろんゲーム中に何も描画されなければゲームをプレイすることが出来なくなってしまうため、
必要最低限なものだけ描画するというのを念頭に置くのが大切ですパンチ

2カメラに写っていないものは描画しない
前述したように、ゲームの中で必要最低限なものだけ描画するのが大事なので、
カメラに写らないもののは描画の処理そのものをキャンセルするようにしましょう。
これは視錐台カリングを行うことで、大部分が解決するのではないでしょうか映画

3使わない描画設定は切る
機能がONになっているだけでGPUへの負荷が大きくなってしまう描画設定がいくつか存在します。
特にアルファブレンドなどはピクセルシェーダーに大きく負荷をかけるので、
うっかり不透明描画を行っているのにアルファブレンドがONになっていたということがないように注意しましょう。
この場合、描画結果は思った通りになっているのにGPUの負荷だけ高いので気付きにくい場合が多いですふらふら

しかし、頻繁に描画設定の切り替えを行うと、動作させるゲームのプラットフォームによっては
かえって処理負荷が高くなることもありますもうやだ〜(悲しい顔)
それを防ぐため、描画前に使うマテリアルごとにソートしておく仕組みや、描画設定のキャッシュ化がされていると尚いいのではないでしょうかるんるん

4無駄なピクセルシェーダーを動作させないようにする
ピクセルシェーダーとは、名前にピクセルと付いているように、描画するポリゴンの1ピクセルごとに動作するシェーダーです。
そのため、描画するピクセルシェーダーのプログラムが遅く、さらにその描画するピクセル数が多ければ多いほどGPUへの負荷は大きくなります。

そのピクセルシェーダーを最低限動作させるのに簡単な方法として、Zテストを利用する方法が挙げられますひらめき

例えば下の画像ではグリとブランが草原の上に立っていますが、画面の多くをブランが占めています。
このブランの後ろには草原が広がっていますが、その草原はブランに隠れて見えないため、
ピクセルシェーダーが動作したとしても無駄ということになりますふらふら

screenShotPC_0002.jpg

このような場合、草原を描画するピクセルシェーダーを動作させないためには、
草原より先にブランを描画してしまうのが最も簡単です。

ピクセルシェーダーが動作する条件として、描画しようとするピクセルの奥行き情報を比較し、
手前であると判断されたらピクセルシェーダーが動きます。
そのため、ブランを先に描画してしまえば隠れている草原のピクセルシェーダーは動作しなくなります。
(ただし、Z比較がONになっている場合に限ります)

つまり、カメラから見て手前にあるものから先に描画すると、無駄なピクセルシェーダーの動作を抑えられる場合が多いということになります。

2010-10-14-0.png


いかがでしたでしょうか?
GPUの性能が上がり、それにつれて求められるグラフィックス表現の幅も広がっていますグッド(上向き矢印)
しかし、その分GPUにかかる負荷も大きくなり続けています。
そのことを踏まえた上で、出来るだけGPUに処理負荷をかけないプログラムをあらかじめ書いておくと、
就職活動用の作品の制作終盤で処理落ちに苦しむことが少なくなるのではないかと思いますわーい(嬉しい顔)揺れるハート

それではまた手(パー)

posted by 管理人 at 23:20 | プログラミング

2012年01月19日

あのアレを退治

年末年始は、実家でのんびりと過ごしいい気分(温泉)、がっつりとご飯ファーストフードも頂きました。
先日、その成果をWii Fit にて実感。まずい、運動しないと・・・・あせあせ(飛び散る汗)
こんにちは、ハラです。

今回は、我々プログラマの大嫌いなアレ爆弾を退治してみようと思います。
アレといっても、カサカサと動く黒いヤツ・・・ではなく、バグですね。

ネタは、バッファオーバーラン(バッファオーバフロー)というものです。
これは、確保した領域の外側を上書きしてしまい、予期せぬ動作を引き起こしてしまう問題です。

まずは、1つ書いてみますメモ
void errorFunc1(int index, int value)
{
const int SIZE = 4;
int tgtArray[SIZE];
if( 0 <= index && index <= SIZE ) {
tgtArray[index] = value;
}
}
引数のindexが4の時だけ配列の領域外を上書きしてしまいます。
それ以外の値では正常に動作するので、問題が表面化し難いですね。
不等式の書き間違えは、無いようにしたいですひらめき

さらにもう1つメモ
void errorFunc2(int value)
{
char temp[16];
sprintf(temp, "value is %d\n", value);
}
こちらは、valueの値が大きい場合に問題が起きます。
1つ目と同じく、どんな値でも問題が起きるわけではないのが嫌らしい所ですもうやだ〜(悲しい顔)
尚、現在のVisual Studioなどでは、この問題の対処として、sprintf関数を使うと警告が出ます。

今度は、計算ミスが原因で起こるケースをメモ
void errorFunc3(void)
{
struct Data
{
char id;
int value;
};

const int SIZE = 4;
int bufferSize = (sizeof(char) + sizeof(int)) * SIZE;
char* pData = new char[bufferSize];
memset(pData, 0, sizeof(Data) * SIZE);

delete [] pData;
}
memset関数で、確保した領域を超えて上書きしています。
原因はアライメントが考慮されていないことですが、
サイズの計算を同じ式で行えば問題は起きません。
この件では些細かもしれませんが、大本の基準を1つにすると、色々なミスを未然に防げます。

最後に、読み込んだデータが不正だったために起こるケースですメモ
プログラムが長くなってしまうので直接値を設定していますが、ファイルから読み込むイメージでお願いします。
void errorFunc4(void)
{
struct Header { // ファイルのヘッダー
int totalSize;
int data1Count;
int data2Count;
};
struct Data { // ファイルのデータ
int data[8];
};

Header head; // 本来はファイルから読み込みます
head.totalSize = 224;
head.data1Count = 3;
head.data2Count = 5;

char* pData = new char[head.totalSize];
int data1Size = sizeof(Data) * head.data1Count;
int data2Size = sizeof(Data) * head.data2Count;
memset(pData, 1, data1Size); // こちらもファイルから読み込む代わりにmemsetしています
memset(pData + data1Size, 2, data2Size);

delete [] pData;
}
原因は、合計サイズとデータ個数に不整合があることで、確保した領域の外を上書きしてしまっています。
今回はプログラム上で値を入れているので、コードだけで判断出来ますが、
ファイルから読み込むように変えると、コードを読むだけでは判断が出来なくなります。

対処は、プログラム側にチェック用のコードを追加することでも可能ですが、
出来れば読み込むデータが常に正しいと保障できる環境を作りたいですね。


お恥ずかしいながら、2つ目のパターンを私は昨年やらかしてしまっていますふらふら
その時は、デバッグ版で発生せず、最適化版で問題が起きる形だったので、ちょっと困りました。

少しでも良い提出作品にするため、色々な要素を入れたくなると思います。
ただ、良い提出作品が完成したと思って会社に送っても、実際に見て貰う時にストップしたらかなり勿体ないです。
デバッグに関しても、ある程度意識してみて下さいねわーい(嬉しい顔)

posted by 管理人 at 12:27 | プログラミング

2012年01月16日

Static_Assert

こんにちは。
寒くて布団から出るのがつらい、イワモーです晴れ

あと2か月半程したら、初めての後輩が出来ます。
頼れる先輩になれる様、今以上に頑張っていかないと、と思う今日この頃ですグッド(上向き矢印)

さて、今回はVisualC++2010から追加された機能、
Static_Assertについてお話したいと思いますひらめき

Static_Assertを簡単に説明すると、
「定数にコンパイルの条件を追加する構文」
でしょうか。

VisualC++2010以前の機能では、
例えば定数に2の倍数以外の設定をして欲しくない場合


void Hoge()
{
try
{
if(MULTIPLES_OF_TWO%2 != 0)
{
throw "Surplus check error : MULTIPLES_OF_TWO need the multiple of 2!!";
}
}
catch(std::string errorLog)
{
std::cout << errorLog.c_str() << std::endl;
}
}


この様にゲーム中で動的に定数の条件を判別する必要があります。
実際にプログラムを動作させるまで、MULTIPLES_OF_TWOは2の倍数か判定できません。
更に、2の倍数以外だった場合の例外処理等も必要になってきます。
(もちろん、人間による目視確認は出来ますがふらふら

しかし、VisualC++2010から利用できる「Static_Assert」を利用すると、
ゲームの動作中ではなく、コンパイル時に判定する事が出来ますぴかぴか(新しい)


static const int MULTIPLES_OF_TWO = 1001;

void Hoge()
{
static_assert(MULTIPLES_OF_TWO%2 == 0,
"static assert error :MULTIPLES_OF_TWO need the multiple of 2!!");
//... 以下で適当な処理
}


この場合、そもそもコンパイルエラーが発生し、プログラムを動作させる事が出来ません。

実際にプログラムを動かす必要が無く、2の倍数以外だった時の処理を書かないで済むので、
エラー処理の手間が省けますぴかぴか(新しい)

しかも、static_assertはスコープに関わらずに記述する事が出来ます目


static const int MULTIPLES_OF_TWO = 1001;
//グローバルスコープに記述する事も可能
static_assert(MULTIPLES_OF_TWO%2 == 0,
"static assert error :MULTIPLES_OF_TWO need the multiple of 2!!");

void Hoge()
{
//... MULTIPLES_OF_TWOのエラーはコンパイル時に検出できるので、
//  エラーを気にせずHogeのすべき処理に専念できる
}


この様に非常にすっきりとした、見やすいコードにする事ができます手(グー)

今回は static const int が2の倍数かを検出する例を取りましたが、
コンパイラが事前に判別出来るデータはこれ以外でも自由に利用する事が出来ます。

また、今回はコンパイルすれば、エラーの「出る」コードを.cppデータとして添付しました。

添付したプログラムにはもう一つ、
static_assertとsizeofを利用した簡単な利用例を記述しましたので、
興味がある方はご覧下さい。

main.cpp

※・VisualStudio2010以前ではstatic_assertがサポートされていないので、正常に動作しません。
※・コード中にあるMULTIPLES_OF_TWOを2の倍数にし、
  LargeClass内で定義されているMAX_DUMMY_SIZEを1000以下にするとコンパイルが通ります。

いかがでしたでしょうか?
VisualC++2010では、他にもラムダ式やauto変数、decltype型指定子など、
使い方次第では強力な機能が追加されています。
特にラムダ式とauto変数は、柔軟なプログラムの設計が可能となる機能だと思うので、
まだ触った事の無い方は、是非一度、触れてみてはと思います手(チョキ)

それでは、また手(パー)

posted by 管理人 at 14:36 | プログラミング

2012年01月05日

GDI+でBMP/PNG/JPEGの読み込みを簡単に

こんにちは、イワサキです !!
新年明けましておめでとうございます。
今年も技術デモをなるべく多く公開していきたいと思います。よろしくお願いします! ぴかぴか(新しい)

さて、2012年に入って学生の皆さんも就職活動本番に突入してく時期になっていますが
今回はゲーム就職作品に役立てられそうなプログラムをひとつ紹介してみたいと思います。


3D作品にDirectXを使用しているとテクスチャの読み込みに対しては特に不便を感じることなく簡単に読み込むことができます。
DirectXにはD3DX拡張ライブラリにテクスチャロード用の関数が予め用意されているため、
様々な画像形式をテクスチャとしてロードすることができます。
しかしながら、場合によってはOpenGLなどの別のプラットフォームで制作することもあったりすると途端にその恩恵にあずかることができなくなってしまいます。

BMP形式は比較的簡単なフォーマットですので自作してもさほど苦労はしないと思います。
PNGやJPEG形式などはその形式独自の圧縮アルゴリズムがあり、自力で展開して利用するのは非常に骨の折れる実装になってしまいます。
もちろん実際にはそんなことにならないようにオープンソースライブラリでlibpnglibjpegのように展開を肩代わりしてくれるものが提供されています。
これらを活用することで比較的簡単に画像を読み込むことができるようになります。

libpng
http://www.libpng.org/pub/png/libpng.html
 →動作にはzlib ( http://www.zlib.net/ )が必要になります。

libjpeg
http://libjpeg.sourceforge.net/


2012-01-05-1.png
【図】それぞれの形式専用の展開ライブラリを適用して読み込み

オープンソース系のライブラリはlibpngのようにライセンス的にも緩く簡単に活用できるものが存在しています。
応用できるところには適切に導入していくことで制作の労力を大幅に軽減できます。

まだプログラミングを始めて経験が浅い時には、このようなライブラリを組み込む事自体にも難易度が高く感じたりすることがあると思います。
もっと敷居が低い方法で簡単に実現できる方法があれば嬉しいですよね。

Windows環境限定になりますが、WindowsXP以降には「GDI+」というグラフィックAPIが拡張新設されています。
そのなかに画像ファイルの読み込みをサポートするモジュールも存在しており、最初から簡単に利用できるようになっています。

GDI+を使用して画像ファイルを読み込む場合は同じロード処理で複数の画像形式に対応することができます。

2012-01-05-2.png
【図】GDI+では様々な画像形式を共通のAPIで扱うことができる

2010-10-14-0.png


早速ですが、ソースコードを御覧ください。

【ヘッダ部】

//---------------------------------------------------------------------------
// 最低限必要なプラットフォームが WindowsXPであることを指定します。
// ※ GDI+ APIを利用するため、WindowsXP以降が必要になります。
//---------------------------------------------------------------------------
#define WINVER 0x0501 // 0x0501 = WindowsXP
#define _WIN32_WINNT 0x0501

#include <windows.h>
//---- GDI+ヘッダ関連
#include <gdiplus.h>
#pragma comment (lib, "gdiplus.lib") // リンク時に必要なライブラリ


【実装部】

bool LoadImage(const char fileName[])
{
// 文字コードをワイド文字列に変換
// 【注意】本来はこの箇所は文字列バッファ長の考慮の他に文字列終端コードを処理するよりセキュアな対応が好ましいです。
wchar_t path[ MAX_PATH ];
size_t pathLength = 0;

if( mbstowcs_s( &pathLength, // [out] 変換された文字数
&path[0], // [out] 変換されたワイド文字列を格納するバッファのアドレス(変換先)
MAX_PATH, // [in] 出力側のバッファのサイズ(単位:文字数)
fileName, // [in] マルチバイト文字列のアドレス(変換元)
_TRUNCATE ) != 0 ) { // [in] 出力先に格納するワイド文字の最大数
return false;
}

// GDI+オブジェクト(画像展開に必要)
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;

//---- GDI+の初期設定
if( Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL) != Gdiplus::Ok ) {
return false;
}

//-------------------------------------------------------------
// 画像の読み込み
//-------------------------------------------------------------
bool result = false;
int width = 0; // 画像の幅
int height = 0; // 〃 高さ

//--- 画像ファイルを開く
// 【対応画像形式】 BMP, JPEG, PNG, GIF, TIFF, WMF, EMF
Gdiplus::Bitmap* pBitmap = Gdiplus::Bitmap::FromFile(path);
if( pBitmap && pBitmap->GetLastStatus() == Gdiplus::Ok ) {
//---- 画像サイズ分の領域確保
width = pBitmap->GetWidth();
height = pBitmap->GetHeight();

// ■ロードする画像のメモリの解像度を変更/設定(この位置に任意に記述して下さい)
if( /* createBuffer(width, height)*/ ) {
result = true;
}
}

//---- 画像イメージの読み込み
if( result == true ) {
for( int y=0; y<height; y++ ) {
for( int x=0; x<width; x++ ) {
Gdiplus::Color srcColor;
pBitmap->GetPixel(x, y, &srcColor);

unsigned char r = srcColor.GetR();
unsigned char g = srcColor.GetG();
unsigned char b = srcColor.GetB();
unsigned char a = srcColor.GetA();

// ■ピクセルへの描画(この位置に任意に記述して下さい)
// setPixel(x, y, r, g, b, a);
}
}
}

delete pBitmap;

//---- GDI+の解放
Gdiplus::GdiplusShutdown(gdiplusToken);

return result;
}


2010-10-14-0.png


いかがでしょうか?わーい(嬉しい顔)
わずかこれだけのプログラムでBMP形式だけでなく、PNG形式やJPEG形式まで読み込むことができます。
上記の方法ではパレットを持ったデータも全てフルカラー形式に展開されてしまいますのでご注意下さい。
アルファ成分もロード可能ですので、テクスチャのロードに活用出来ると思います。ぴかぴか(新しい)ぴかぴか(新しい)ぴかぴか(新しい)

ソースコード内で■記号をつけた部分にメモリ確保とデータ書き込みの処理を追加して使うことになります。
クラス実装に拡張したりして活用していただけたら幸いです。

この方法はWindows環境のみでしか利用できませんので、他の環境の場合はlibpng/libjpegといったオープンソースライブラリで
対応する必要があります。




ヘキサドライブでは先月12月から新卒採用スタートしています。
教育には師弟制度があり、先輩と共に知識や能力を高めあっていく環境づくりをしています晴れ
一緒にゲーム作品を作っていきませんか?採用情報詳細は下記情報を御覧ください。

◎ 株式会社ヘキサドライブ 採用情報 ◎
http://hexadrive.jp/index.php?id=5

学生の皆さんに会えるのを楽しみにしていますぴかぴか(新しい)是非ご応募くださいね。 手(パー)


posted by 管理人 at 23:55 | プログラミング

2011年12月21日

JavaScriptとクラスとクロージャー

当面はJavaScriptに関するネタでblogを書こうと決めているgood sun晴れこと山口です。
今回は前回と同じく素材が用意出来なかった為、
第一回で書いたスクリプトの構文について説明していきたいと思います。
素材を用意する時間がなくてFINAL FANTASY XIII-2 とPSVitaは遊ぶ時間があるのが不思議ですわーい(嬉しい顔)

このJavaScriptシリーズ第一回では何気なく記述をしていますが、
JavaScriptは俗に言うオブジェクト指向な言語なので、
C++等で使われている"クラス"のようなものが書けます。

function Node(){

}
Node.prototype.update = function(){

}

この辺等がそうですね。
一見すると関数を定義しているだけのようですが、
JavaScriptでのクラスのようなものはこの関数の定義を利用して作成します。

C++での

Class MyClass{
public:
MyClass(value):myValue{
}
~MyClass(){
}
int addValue(value){
return myValue + value;
}
protected:
private:
int myValue;
};
...
MyClass* pMyClass = new MyClass(3);
int v = pMyClass->addValue(5);

といった記述は

JavaScriptでは

function MyClass(value){
this.myValue = value;
}
MyClass.prototype.addValue = function(value){
return this.myValue + value;
}
val myClass = new MyClass(3);
val v = myClass.addValue(8);

といった感じに書けます。
如何でしょうか、結構好感の持てる書式だと思います。

最後にJavaScriptで気をつけなければいけないthisからはじまるクロージャー入門をしたいと思います。
第一回で記述した以下の処理に注目して下さい。

Frame.prototype.run = function(lastTime){

var _this = this;
setTimeout(function(){_this.run(nowTime);}, nextTime);
}

Frameクラスのrun関数は
最後に実行した時間を引数にとって、
一定時間後に再度自分自身のrun関数を呼び出す事を続ける処理です。

setTimeout関数に渡す第一引数ですが、
C,C++で言うところの関数ポインタのようなものになります。
関数内に無名関数定義を書けるのはとてもいい感じです。
この無名関数内の処理ですが、
自分自身のrun関数を呼び出したいので、
this.run(nowTime);
と記述しても良さそうではありますが
thisという予約語が関数を持つオブジェクトのインスタンスを挿すキーワードなので、
このthisは登録された無名関数を呼び出す関数を持つオブジェクトのインスタンスを挿してしまい、
Frameオブジェクトのインスタンスを指してくれません。
正しくFrameオブジェクトのインスタンスthisを使う為に、
直前で_this変数にthisを代入して_this経由でFrame.prototypeのrun関数を呼び出しています。

この関数の引数にない変数を堂々と使えるのは、
クロージャーという仕組みらしいです。
関数内関数とそれを取り巻く変数との紐付けみたいな感じでしょうか。
よく見ると例としてあげた関数の引数にとっているnowTimeも引数に無い変数ですね。

これを利用するとJavaScriptでもprivate変数パスワードのようなものを作成する事が出来ます。

function MyClass(){
var _this = this;
function privateActivate(){
var value;
_this.__proto__.getFunc = function(){return value;}
_this.__proto__.setFunc = function(argv){value = argv;}
}
privateActivate();
}
var myClass = new MyClass;
myClass.setFunc(3);
myClass.getFunc() + 5;

なんとも不思議な感じです。
普段使わない言語を使うと頭の体操になっていい感じです。
来年こそは素材を準備してスマートフォン用のダイナミックなページ作成に挑戦しますパンチ

posted by 管理人 at 20:37 | プログラミング

2011年11月14日

続JavaScriptで行く

MacBook Airを使っていたら無性に欲しくなったのでiPod touchも買ってしまいました。
だんだんとApple製品に染まっていっているgood sun晴れこと山口です。

iPod touchと言えばiPhoneから電話と3G通信やGPS機能を取り除いた程度で、
iOSを搭載して様々なアプリケーションを実行する事が出来る上、
wifi環境があればインターネットも出来てしまうというリッチ有料なデバイスです。
なんとJavaScriptでiPhone等の特徴的な操作方法であるタッチ処理手(パー)が出来るということで、
今回はそんなお話を…と思ったのですが、
その為の素材の用意が間に合いませんでしたので、
原点とも言えるJavaScript+HTMLのお話をしたいと思います。

HTMLと聞くとホームページを作ったことがある人ならば、
<html>〜</html>といったタグを使ったページ作りを思い浮かべるでしょうか。
今見ているこのブログも内部的にはそのように作られています。

しかし、なんとJavaScriptを使うとほとんどのタグを書かずにHTMLを作成する事が出来てしまいます。
<html><body><script>
のタグだけあれば様々な要素を持ったwebページを作ることが出来ます。
というのもタグを使った要素の記述以外にスクリプトから動的な要素の生成が行えるのです。

例えばテーブルはHTMLを使った場合次のように書けますが、

<html>
<body>
<table>
<tr>
<td>これはHTMLタグでの</td>
<td>テーブルの</td>
<td>サンプルです</td>
</tr>
</table>
</body>
</html>

JavaScriptを利用すると

<html>
<body></body>
<script type="text/javascript" charset="UTF-8">
var td0 = new document.createElement("td");
var td1 = new document.createElement("td");
var td2 = new document.createElement("td");
var tr = new document.createElement("tr");
var table = new document.createElement("table");
td0.innerText = "これはJavaScriptでの";
td1.innerText = "テーブルの";
td2.innerText = "テストです";
tr.appendChild(td0);
tr.appendChild(td1);
tr.appendChild(td2);
table.appendChild(tr);
document.body.appendChild(table);
</script>
</html>

こんな感じで書くことが出来ます。
如何でしょう?
各要素を作成してそれぞれを子要素として格納していきます。
プログラマには合理的な作り方だと感じられるのではないでしょうか。

私が初めてこの書き方を知ったときはとてもワクワクしましたわーい(嬉しい顔)
最初から要素を変数として取得しておけるので、
細かい制御がしやすくなってちょっと複雑な仕組みも簡単に作れそうだなぁと。
こうした新しい発見から来るはワクワクは新しい発想に繋がり易いので大切にしていきたいです。

では今回はこの辺で。
タッチ処理手(パー)も機会があれば是非お披露目したいです。


posted by 管理人 at 20:28 | プログラミング

2011年10月07日

JavaScript始めました

まず始めに前アップルCEOのスティーブ・ジョブズ氏の訃報について、
哀悼の意を表し、心からご冥福をお祈りいたします。
PC業界に限らずゲーム業界等様々な業界に大きな影響を与えた偉大な方でした。

そんなアップル製品のMacBook Airを先日購入したgood sunこと山口です。

今回はそんなMac購入のきっかけにもなったお話です。
突然ですがJavaScriptご存知ですか?
Android開発で有名なJava言語とは全くの別物のブラウザ等で動くスクリプト言語です。
ブラウザで動作するので、OSを気にすることなくプログラムを記述することが出来ます。
その為持ち運びやすかったり、寝転びながら操作できるMacBook Airは最適な環境だったりします。
この寝転んで...がしたくてMacを買いました。

さて、なぜJavaScriptなのかですが、百聞は一見に如かず。
今回は簡単に最近噂のHTML5のcanvasの紹介を兼ねてJavaScriptのサンプルプログラムを作って見ました。
今回のサンプルはランダムな色の玉が跳ねるだけなのですがcanvasには画像も貼れます。
canvasを使うことでブラウザ内に自由に描画を行うことが出来ますし、
タイマを使って描画タイミングを制御することで、アクション性の高いゲームにも向いた処理が行えます。
実はこのcanvasは3D描画を扱うことも出来たりします。
魅力がたっぷり詰まった環境についてまだまだ色々語りたいのですが、今回はこの辺で。


今回のサンプルは
MacOSX LionでSafariとGoogle Chrome
WindowsのInternet Explorer9,Firefox,Google Chrome,Operaで動作確認を行なっております。
恐らくcanvasが使えるブラウザ全てで動作するのではないでしょうか?
残念ながらInternet Explorerは9より前のバージョンは動作対象外となります。

↓今回のサンプルコードです。ダウンロードはこちら

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>javascriptサンプル</title>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
<script type="text/javascript" charset="UTF-8">
//! スクリーンサイズ
Screen = {
width:320,
height:240
};
//! ノードクラス
function Node(){
// パラメータはランダムで決定
this.posx = Math.floor(Math.random() * Screen.width);
this.posy = Math.floor(Math.random() * Screen.height);
this.dirx = Math.ceil((Math.random() - 0.5) * 3);
this.diry = Math.ceil((Math.random() - 0.5) * 3);
this.color = "#"+((Math.floor(Math.random()*255) << 16) | (Math.floor(Math.random()*255) << 8) | Math.floor(Math.random()*255)).toString(16);
this.rad = Math.ceil(Math.random() * 15);
this.isFill = Math.random() > 0.5 ? true : false;
}
//! 更新関数
Node.prototype.update = function(){
this.posx += this.dirx;
this.posy += this.diry;
if((this.posx > Screen.width) || (this.posx < 0)){
this.posx -= this.dirx;
this.dirx = -this.dirx;
}
if((this.posy > Screen.height) || (this.posy < 0)){
this.posy -= this.diry;
this.diry = -this.diry;
}
}
//! 描画関数
Node.prototype.draw = function(context2d){
context2d.beginPath();
context2d.arc(this.posx, this.posy, this.rad, 0, 360, false);
if(this.isFill == true){
context2d.fillStyle = this.color;
context2d.fill();
}else{
context2d.strokeStyle = this.color;
context2d.lineWidth = 2;
context2d.stroke();
}
context2d.closePath();
}
//! フレーム管理クラス
function Frame(nodeCount){
// クリアカラーとキャンバスサイズ
this.clearColor = "#404040";
// コンテキストの準備
this.canvas = window.document.getElementById("canvas");
this.context2d = this.canvas.getContext('2d');
this.canvas.width = Screen.width;
this.canvas.height = Screen.height;
// クリアカラーでクリアする
this.context2d.fillStyle = this.clearColor;
this.context2d.fillRect(0, 0, Screen.width, Screen.height);
// ノードの準備
this.nodes = new Array(nodeCount);
for(var i = 0; i < nodeCount; i++){
this.nodes[i] = new Node();
}
}
//! 実行
Frame.prototype.run = function(lastTime){
// クリア
this.context2d.fillStyle = this.clearColor;
this.context2d.fillRect(0, 0, Screen.width, Screen.height);
//! 全てのノードを更新して
for(var i = 0; i < this.nodes.length; i++){
this.nodes[i].update();
this.nodes[i].draw(this.context2d);
}
//! 更新を行う
var nowTime = new Date();
// 20FPS
var nextTime = 50 - (nowTime.getTime() - lastTime.getTime());
if(nextTime <= 0){
nextTime = 1;
}
var _this = this;
setTimeout(function(){_this.run(nowTime);}, nextTime);
}
var frame = new Frame(100);
frame.run(new Date());
</script>
</html>


posted by 管理人 at 20:56 | プログラミング

2011年09月28日

AVXを使ってみました!

こんにちは!
そろそろ職場の空気を掴みはじめてきた、新人のイワモーです晴れ

さて、前回のブログでは新しいPCを買った事をお話ししたと思いますが、
その中でAVXを勉強中という事を書きました。

今回は早速、そのAVXについて少しお話してみようかと思いますぴかぴか(新しい)

まず簡単に説明すると・・・
AVXとは、拡張SIMD命令セットの事で、従来のSSE命令では最大128bitまでの演算幅だったのが、
AVXでは256bitまで拡張されており、一回の命令で最大8つの数値を演算させる事が可能になったSIMD命令。
そして、SSEの拡張を重ねた結果、非常に煩雑になってしまったSIMD命令への解決へのアプローチ

という感じでしょうか。
厳密には更に説明が必要だったりしますが、詳細についてはAVXのリファレンス等を読んで頂ければと思います目

インテルAVXリファレンス(大き目のPDFデータなので、注意してください)
http://download.intel.com/jp/software/AVE/319433-006JA.pdf


では、実際にはどんな感じなのかexclamation&question
AVXを用いた簡単なコードの流れを少し書いてみましたぴかぴか(新しい)

今回扱うデータはこの様な形にしました。
ゲーム等ではよく使いそうな、よくあるVector4データです。

struct VERTEX_DATA
{
float x;
float y;
float z;
float w;
};

VERTEX_DATA *pDataBuffer0; //データバッファ
VERTEX_DATA *pDataBuffer1; //計算値のバッファ

static const int MAX_VERTEX = 10000000; //データの大きさ

まずAVXを使うにあたり注意する所は、
アラインメントを32byteで定義してメモリを確保しなければならない所です目
AVXでは、基本的に一つのデータにつき、32byteのアラインメントを必要としています。


pDataBuffer0 = (VERTEX_DATA*)_aligned_malloc( sizeof(VERTEX_DATA) * MAX_VERTEX, 32);
pDataBuffer1 = (VERTEX_DATA*)_aligned_malloc( sizeof(VERTEX_DATA) * MAX_VERTEX, 32);

普段_aligned_mallocはあまり使わないアロケート方法ですが、
第2引き数でアラインメント幅を入力する事が出来ます。
これでメモリを32byteアラインメントで確保しました手(チョキ)

もちろん、構造体の定義の方で__declspec(align(32))の様にアラインメントを定義してしまっても問題は無いと思います。
その場合は、普通のnew演算子でメモリをアロケートしても大丈夫でするんるん


次にデータの入力ですが、これは普通のプログラムと同じ様に代入できます。


pDataBuffer0[i].x = 100;
pDataBuffer0[i].y = 100;
pDataBuffer0[i].z = 100;
pDataBuffer0[i].w = 100;

pDataBuffer1[i].x = 100;
pDataBuffer1[i].y = 100;
pDataBuffer1[i].z = 100;
pDataBuffer1[i].w = 100;


データ入力が終わったら後はAVXのイントリンシック命令を使って計算させるだけですexclamation
__m256というAVX用のデータ型に値をロードし、計算させ、結果を元に戻す、という手順ですぴかぴか(新しい)


//MAX_VERTEXは、2の倍数と今回は仮定
//また_mm256_load_psはfloat8個分のデータ転送を行えるので、VERTEX_DATA2個分に相当する。
//よって、ループカウンタを2でインクリメント

__m256 srcIntrin;
__m256 destIntrin;

for(int i=0; i<MAX_VERTEX; i+=2)
{
    //値を__m256の変数にロードする
    srcIntrin = _mm256_load_ps((float*)&pDataBuffer0[i]);
    destIntrin = _mm256_load_ps((float*)&pDataBuffer1[i]);
    //ロードが終わったら、計算命令にデータを入れ(この命令は掛け算)結果を受け取り
    srcIntrin = _mm256_mul_ps(srcIntrin,destIntrin);
    //計算結果を元の場所に戻す
    _mm256_store_ps((float*)&pDataBuffer0[i],srcIntrin);
}

これで、無事にAVXを使って計算させる事に成功しましたexclamation
上の例では、_mm256_mul_psを利用し、掛け算を行っています。

最後に忘れてはならないのが、確保したメモリの解放ですふらふら
_aligned_mallocを使っている場合は_aligned_freeでメモリを解放してあげましょう。


_aligned_free(pDataBuffer0);
_aligned_free(pDataBuffer1);

もしも構造体を__declspec(align(32))で定義し、newで確保している場合は、
そのままdelete命令で解放してしまいましょうるんるん
以上が基本的な流れですパンチ


また、今回はコンパイルすれば動作するコードを .CPPデータとして添付ました。

main.cpp

※ ・CPPデータのコンパイルは、VisualStudio2010が必要となります
  ・データの実行については、OSのバージョンがWindows7SP1である必要があります。
  ・AVXに対応しているCPUが必要となります。


自分の環境では実際にAVXを用いたパターンと、通常の計算方法をリリースビルドで比べてみると、
割り算等のコストが高めの演算では6倍近い速度で計算を行わせる事ができました。
しかし、足し算や引き算等のコストが低めの計算では3割程早くなる程度でしたもうやだ〜(悲しい顔)

使いこなせれば動作速度を向上させるツールに十分なると思いますが、
アラインメントに気を遣うのでメンテナンスしずらい点、新しい命令が故のバグ、
対応環境の少なさ、効果を発揮できる場面の見極め等々、壁はとても大きいです。
それに加えて、AVXは従来のSSE命令が混在しているプログラムだと最大限のパフォーマンスが得られないという欠点もあります。バッド(下向き矢印)

ですが、実際に動作速度を向上出来た時の感動も大きいですexclamation×2
まだまだSIMD最適化に片足を突っ込んだばかりですが、有志で開いている勉強会等に積極的に参加していき、少しずつ理解を深めていきたいと思いますexclamation

それではまた手(パー)

posted by 管理人 at 14:54 | プログラミング

2011年08月31日

ドングル3

お久しぶりです。
先日激辛料理レストランで撃沈しましたふらふら
沈みゆく太陽good sun晴れこと山口です。
今日も元気です。

今回でドングルシリーズ最後となります。
もはやドングルそのものとは関係の薄い内容となっておりますが、
そもそもドングルとはプログラムの実行制限により、
プログラムライセンス保持者を守るパンチものです。

どんなに頑張ってハードウェアキーを使って、
安全にライセンスキーをやり取りしても、
実際に実行するプログラム側を攻撃されてしまうこともあるのです。

今回はこの実行するプログラムを守る方法の一つをご紹介します。
実行コードを隠すです。
簡単なサンプルコードを以下に掲載します。
Windows7(Intel Core i7CPU) VisualStudio2010にて動作確認しています。


#include<stdio.h>
#include<windows.h>

// ただの加算関数
int funcAdd(int a, int b)
{
return a + b;
}

// 上の関数のx86バイナリコード
unsigned char codeAdd[] =
{
// _a$ = 8
// _b$ = 12
0x55, // push ebp
0x8b, 0xec, // mov ebp, esp
0x81, 0xec, 0xc0,0x00, 0x00, 0x00, // sub esp, 192
0x53, // push ebx
0x56, // push esi
0x57, // push edi
0x8d, 0xbd, 0x40, 0xff, 0xff, 0xff, // lea edi, [ebp - 192]
0xb9, 0x30, 0x00, 0x00, 0x00, // mov ecx, 48
0xb8, 0xcc, 0xcc, 0xcc, 0xcc, // mov eax, -858993460
0xf3, 0xab, // rep stosd
0x8b, 0x45, 0x08, // mov eax, _a$[ebp]
0x03, 0x45, 0x0c, // add eax, _b$[ebp]
0x5f, // pop edi
0x5e, // pop esi
0x5b, // pop ebx
0x8b, 0xe5, // mov esp, edp
0x5d, // pop edp
0xc3 // ret 0
};

typedef int(*funcType)(int a, int b);
int main()
{
// 通常関数のコール結果
printf("func %d\n", funcAdd(5, 10));

// 少し前のwindowsから実行属性の無い領域を実行させられないプロテクトが入っている為
// 実行属性付の領域を確保する
void* code = VirtualAlloc( NULL, sizeof(codeAdd), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if(code != NULL){
memcpy(code, codeAdd, sizeof(codeAdd));
DWORD oldProteted;
VirtualProtect( code, sizeof(codeAdd), PAGE_EXECUTE, &oldProteted );
// このような感じでデータ領域のコードを実行属性の領域に移してきて実行させる事が出来る
funcType func = (funcType)code;
printf("code %d\n", func(5, 10));
}
return 0;
}


funcAddという関数はプログラムコードを逆アセンブラした際に、
プログラム領域に配置されますが、
codeAddというデータはデータ領域に配置されます。
ある程度の規模のプログラムになるとちょっと見ただけでは、
このcodeAdd部分を探すのは難しくなります。
さらにこれを応用すると実行コードを暗号化したり
別ファイルからリソースの如く読み込んだり出来ます。
より目につきにくく読みにくくすることが出来るようになります。

ちなみにx86バイナリコードは
構成プロパティのC/C++の出力ファイルから
アセンブリの出力で
"アセンブリコードとコンピュータ語コード(/FAc)"を選択するとビルド後に出力されるものを利用しています。

とは言えこのあたりの防衛はいつかは破られると言われているものです。
頻繁にバージョンアップを行って、最新版を生み出し続ける事が最大の対策かもしれないですね。

今回でドングルシリーズを終了して、
次回は最近興味を持っているHTML5的なお話か最近購入した3Dマウスのお話が出来たらいいなと思います。
ではまた手(パー)


posted by 管理人 at 19:41 | プログラミング

2011年07月27日

ドングル2

お久しぶりです。
珍しく前回からネタが続く、good sun晴れこと山口です。

USBをハードウェアキーとして利用するの続きをやってみたいと思います。
今回注目するのはライセンスキー発行に繋がる部分です。

ライセンスキーの流れは普通はこんな感じでしょうか。
まずユーザーはハードウェアキー情報をライセンス管理者に送信して、
管理者はその情報を基にライセンスキーを発行してユーザーに渡します。
アプリケーションはライセンスキー情報とハードウェアキー情報を照合して、
対になるデータであることを確認したらアプリケーションを正常起動します。

今回はハードウェアキーの情報を管理者に安全に届けるmail toための方法を考えたいと思います。
公開鍵と秘密鍵を使った暗号化パスワードというのがあります。
公開鍵で暗号化されたデータは秘密鍵だけで復号化出来るというものです。
この仕組を利用して
アプリケーションには公開鍵情報を埋め込み、
この鍵を利用してハードウェアキー情報を暗号化して、
管理者のもつ秘密鍵で復号化してライセンスキーを作成するという流れが出来そうです。

ではどのようにこの暗号化、復号化を行うかですが...
今回もAPIを利用したコードをネットで調べてC++で動作するようにしてみました。
Windows7にてVS2010のコンソールアプリとして動作確認しています。

#include <windows.h>
#include <wincrypt.h>
#include <memory>
#include <stdio.h>

void printCode(const char* str, const BYTE* code, DWORD size)
{
printf("%s from>>\n", str);
for(DWORD i = 0; i < size; i++){
printf("%c", code[i]);
}
printf("\n<<to\n");
}

int main(void)
{
// RSA用にコンテキストを有効化します
HCRYPTPROV cryptprov;
CryptAcquireContext(&cryptprov, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, 0 );
// 鍵の自動生成
HCRYPTKEY genKey;
// 第三引数の上位16bitが0の場合はデフォルトのキー長が使われる
CryptGenKey(cryptprov, CALG_RSA_KEYX, CRYPT_EXPORTABLE, &genKey);
// 秘密鍵です
DWORD privateKeySize;
CryptExportKey(genKey, 0, PRIVATEKEYBLOB, 0, NULL, &privateKeySize);
std::auto_ptr<BYTE> privateKey(new BYTE[privateKeySize]);
CryptExportKey(genKey, 0, PRIVATEKEYBLOB, 0, privateKey.get(), &privateKeySize);
// 公開鍵です
DWORD publicKeySize;
CryptExportKey(genKey, 0, PUBLICKEYBLOB, 0, NULL, &publicKeySize);
std::auto_ptr<BYTE> publicKey(new BYTE[publicKeySize]);
CryptExportKey(genKey, 0, PUBLICKEYBLOB, 0, publicKey.get(), &publicKeySize);

// 作成された鍵で暗号化と復号化が出来るか試してみる
// キーをインポートしたと想定、公開鍵を読み込んで改めてキーを作成する
HCRYPTKEY pubImportKey;
if(!CryptImportKey(cryptprov, publicKey.get(), publicKeySize, 0, 0, &pubImportKey)){
return 0;
}
// まずは暗号化
const char* originalCode = "hello world";
DWORD originalSize = strlen(originalCode);
DWORD encryptSize = originalSize;
// 一度暗号化データのサイズを調べる
CryptEncrypt(pubImportKey, 0, true, 0, (BYTE*)originalCode, &encryptSize, 0);
std::auto_ptr<BYTE> encryptBuffer(new BYTE[encryptSize]);
memcpy(encryptBuffer.get(), originalCode, originalSize);
printCode("オリジナルコード", encryptBuffer.get(), originalSize);
// 暗号化する
if(!CryptEncrypt(pubImportKey, 0, true, 0, encryptBuffer.get(), &originalSize, encryptSize)){
return 0;
}
printCode("暗号化したコード", encryptBuffer.get(), encryptSize);

// 続いて復号化
// キーをインポートしたと想定、秘密鍵を読み込んで改めてキーを作成する
HCRYPTKEY prvImportKey;
if(!CryptImportKey(cryptprov, privateKey.get(), privateKeySize, 0, 0, &prvImportKey)){
return 0;
}
if(!CryptDecrypt(prvImportKey, 0, true, 0, encryptBuffer.get(), &encryptSize)){
return 0;
}
printCode("復号化したコード", encryptBuffer.get(), encryptSize);
// 本来は最初に鍵を作ったら何処かにとって置かないと毎回違う鍵が作られるので要注意です

CryptDestroyKey(prvImportKey);
CryptDestroyKey(pubImportKey);
CryptDestroyKey(genKey);
CryptReleaseContext(cryptprov, 0);

return 0;

}

APIを使えば簡単に暗号化も出来てしまいますね。
本来はこの暗号化の仕組み自体を作ると言うのも面白いのですが、
使えるものを上手く使うことは時間節約にも繋がります。
ここで浮いた時間を本来のアプリケーションにつぎ込むとより良いものになると思いますパンチ
次回も続く予定ですが、少し違った方向性で行きたいと思います。
ではまた手(パー)

posted by 管理人 at 21:20 | プログラミング