日記帳

日記です。

TestSprite.js

Rhino では JavaScript から任意の Java オブジェクトが扱えます. gcjコンパイルしてあるので sdl4gcj を使えば当然 SDL も扱えるわけです.新しくバインディングを書かなくても SDL が使える言語が増えるのって楽でいいですねぇ♪

ってことで SDL の testsprite.c の JavaScript 版.

importPackage(Packages.java.lang)
importPackage(Packages.sdl4gcj)
importPackage(Packages.sdl4gcj.video)
importPackage(Packages.sdl4gcj.event)
importPackage(Packages.sdl4gcj.input)

var random = new java.util.Random();

function Sprite(image){
	this.screen = Screen.getVideoSurface();
	this.rect = Rectangle();

	// init size
	this.image = image;
	this.movableW = this.screen.w - this.image.w;
	this.movableH = this.screen.h - this.image.h;
	this.rect.w = image.w;
	this.rect.h = image.w;

	// init location
	this.rect.x = random.nextInt(this.movableW);
	this.rect.y = random.nextInt(this.movableH);

	// init speed
	while(this.dx == 0 && this.dy == 0)
	{
		this.dx = random.nextInt(this.MAX_SPEED * 2 + 1) - this.MAX_SPEED;
		this.dy = random.nextInt(this.MAX_SPEED * 2 + 1) - this.MAX_SPEED;
	}
}

Sprite.prototype = {
	MAX_SPEED : 1,
	dx : 0,
	dy : 0,
	move : function()
	{
		this.rect.x += this.dx;
		if((this.rect.x < 0) || (this.rect.x >= this.movableW))
		{
			this.dx = -this.dx;
			this.rect.x += this.dx;
		}

		this.rect.y += this.dy;
		if((this.rect.y < 0) || (this.rect.y >= this.movableH))
		{
			this.dy = -this.dy;
			this.rect.y += this.dy;
		}
	},
	draw : function()
	{
		this.screen.blitSurface(this.image, this.rect.x, this.rect.y);
	}
}

var testSprite = 
{
	screenWidth : 640,
	screenHeight : 480,
	bitsPerPixel : 8,
	videoFlags : SDLConstants.SDL_SWSURFACE,
	numberOfSprite : 100,

	screen : null,
	icon : null,

	bgColor : null,
	sprites : null,
	rects : null,
	ticks : 0,
	frames : 0,

	run : function()
	{
		this.initScreen();
		this.initIcon();
		this.initSprites();
		this.printInfo();
		this.mainLoop();
		this.freeIcon();
	},
	getFPS : function()
	{
		return (this.frames * 1000)/this.ticks;
	},
	initScreen : function()
	{
		this.screen = Screen.setVideoMode(
			this.screenWidth, this.screenHeight,
			this.bitsPerPixel, this.videoFlags);
		this.bgColor = this.screen.mapRGB(Color.BLACK); 
	},
	initIcon : function()
	{
		// load icon image and change pixel format
		this.icon = Surface.loadBMP("icon.bmp");
		this.icon.setColorKey(SDLConstants.SDL_SRCCOLORKEY|SDLConstants.SDL_RLEACCEL);
		this.icon.displayFormat();

		// Run a sample blit to trigger blit acceleration 
		this.screen.blitSurface(this.icon);
		this.screen.fillRect(this.bgColor);
	},
	freeIcon :function()
	{
		this.icon.freeSurface();
		this.icon = null;
	},
	initSprites : function()
	{
		this.sprites = new Array(this.numberOfSprite);
		this.rects = java.lang.reflect.Array.newInstance(Rect, this.numberOfSprite);
		for (var i = 0;i < this.numberOfSprite;i++)
		{
			this.sprites[i] = new Sprite(this.icon);
			this.rects[i] = this.sprites[i].rect;
		}
	},
	updateSprites : function()
	{
		for (var i = 0;i < this.numberOfSprite;i++)
		{
			this.sprites[i].move();
			this.sprites[i].draw();
		}
	},
	updateScreen : function()
	{
		if ((this.screen.flags & this.screen.SDL_DOUBLEBUF) == this.screen.SDL_DOUBLEBUF)
			this.screen.flip();
		else
			this.screen.updateRects(this.rects);
	},
	mainLoop : function()
	{
		var done = false;
		var event = new EventManager();
		this.frames = 0;
		this.ticks = SDLSystem.getTicks();
		while (!done)
		{
			this.frames++;
			while(event.pollEvent() > 0)
			{
				switch (event.type)
				{
					case SDLConstants.SDL_KEYDOWN:
					case SDLConstants.SDL_QUIT:
						done = true;
						break;
					default:
						break;
				}
			}

			this.screen.fillRect(this.bgColor);
			this.updateSprites();
			this.updateScreen();
		}

		this.ticks = SDLSystem.getTicks() - this.ticks;
	},
	printInfo : function()
	{
		if (this.screen != null)
		{
			System.out.println("Screen is at " + this.screen.getPixelFormat().getBitsPerPixel() + " bits per pixel");
			var flags = this.screen.getFlags();
			if ((flags & SDLConstants.SDL_HWSURFACE) == SDLConstants.SDL_HWSURFACE)
				System.out.println("Screen is in video memory");
			else
				System.out.println("Screen is in system memory");

			if ((flags & SDLConstants.SDL_DOUBLEBUF) == SDLConstants.SDL_DOUBLEBUF)
				System.out.println("Screen has double-buffering enabled");
		}

		if (this.icon != null)
		{
			var iconFlags = this.icon.getFlags();
			if ((iconFlags & SDLConstants.SDL_HWSURFACE) == SDLConstants.SDL_HWSURFACE)
				System.out.println("Sprite is in video memory");
			else
				System.out.println("Sprite is in system memory");

			if ((iconFlags & SDLConstants.SDL_HWACCEL) == SDLConstants.SDL_HWACCEL)
				System.out.println("Sprite blit uses hardware acceleration");

			if ((iconFlags & SDLConstants.SDL_RLEACCEL) == SDLConstants.SDL_RLEACCEL)
				System.out.println("Sprite blit uses RLE acceleration");
		}
	}
}

SDLSystem.init(SDLSystem.SDL_INIT_VIDEO);
try
{
	testSprite.videoFlags = SDLConstants.SDL_SWSURFACE;
	testSprite.bitsPerPixel = 32;
	testSprite.run();
	System.out.println(testSprite.getFPS() + " frames per second");
}
catch (e)
{
	System.out.println(e);
}
finally
{
	System.out.println("SDLSystem.quit()");
	SDLSystem.quit();
}

実行環境は Linux + X で gcj は 3.3.2 です.640x480 32 bpp の Window モード SDL_SWSURFACE で測定.

まずはオリジナルの C言語版.

% ./testsprite
Screen is at 32 bits per pixel
Screen is in system memory
Sprite is in system memory
Sprite blit uses RLE acceleration
591.46 frames per second

sdl4gcj の TestSprite.java の場合.

% ./TestSprite
Screen is at 32 bits per pixel
Screen is in system memory
Sprite is in system memory
Sprite blit uses RLE acceleration
589.5686083353644 frames per second

TestSprite.js を gcjコンパイルした rhino で実行した場合.

% rhino TestSprite.js
Screen is at 32 bits per pixel
Screen is in system memory
Sprite is in system memory
Sprite blit uses RLE acceleration
134.16213416213415 frames per second
SDLSystem.quit()

とりあえず遅い.

ECMAScript のNumber 型が常に倍精度浮動小数点なのが効いているのでしょうか? いやそもそも ECMAScript はとっても動的な言語なので遅くならないはずがないですけども…

しかたがない(?)ので jsc + gcj でネイティブコードにコンパイルしてみます.

% jsc -o TestSprite.class TestSprite.js
% gcj -O2 -o TestSprite --main=TestSprite TestSprite.class -l-org-mozilla-classfile -l-org-mozilla-javascript
TestSprite.java: In class `TestSprite':
TestSprite.java: In method `TestSprite._c0(TestSprite,org.mozilla.javascript.Context,org.mozilla.javascript.Scriptable,org.mozilla.javascript.Scriptable,java.lang.Object[])':
TestSprite.java:208: 警告: unreachable bytecode from 1047 to before 1052

なんか警告が出たけど実行ファイルはできたので実行してみる.

% ./TestSprite
Screen is at 32 bits per pixel
Screen is in system memory
Sprite is in system memory
Sprite blit uses RLE acceleration
169.16916916916918 frames per second
SDLSystem.quit()

結構速くなってる! C 言語版や Java 版には遠く及びませんけど…

結果をまとめると以下のような感じ.

C言語 591.46
Java版(gcj版) 589.57
JavaScript版(インタープリタ) 134.16
JavaScript版(コンパイル) 169.17

性能は C 言語や Java に及ばないですけど rhino で開発して jsc + gcjコンパイルとかいろいろ遊べそうでいいですねぇ.