高速描画?BufferStrategyを使ってみる

   2012/07/19

Javaには、非常にたくさんのクラスライブラリがあります。
いったいどれくらい覚えたらいいのか、いくつ理解すればプログラミングが
できるようになるのか不安になる人もいるようです。

毎度、管理人イガジーです。

知らないもの/ことは、あって当たり前。
必要に応じて、調べて、試して、使えばよいのです。
辞書に載ってる単語を全て覚えてから、英会話を始めようとするのが無謀なのと同じです。
少しでも覚えたら、まずは使ってみましょう。

という訳で、高速描画用の仕掛けらしいBufferStrategy というモノを使ってみました。
使い方としては
Frame または Canvas で、createBufferStrategy() して
getBufferStrategy() で得たBufferStrategy に対して描画し、
show()とすれば、表示される。という感じです。

※ ちなみに、イガジーが試した環境は Linux なので
Windows や MAC だと多少異なっているかもしれません。

最初にハマったのが、createBufferStrategy() しても例外が起きて
BufferStrategyを生成できないトラブルです。
どうやらFrameなり、Canvasを実際に表示(setVisble(true))して
画面上に窓が現れた状態にする必要があるようです。

BufferStrategy に対する描画は、getDrawGraphics()で得た
Graphics を使って行います。
show() した後は、BufferStrategy が切り替わるので Graphics を取り直す必要があります。

管理人イガジーが試したコードを掲載しておきます。

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.util.Timer;
import java.util.TimerTask;

public class BufStrCanvas {
	final int interval=100; //ms
	final int bww=30, bhh=30;
	final int bufw=204,bufh=181;
	final int speed=9;
	int vx=speed;
	int vy=2;
	int x,y;
	Frame f=new Frame("BufferStrategy Canvas");
	GCanvas cv;
	BufferStrategy bs=null;
	Timer tmr;

	BufStrCanvas() { // constructor
		f.addWindowListener(new WindowAdapter(){
			public void windowClosing(WindowEvent ev) {
				System.exit(0);
			}
		});
		cv=new GCanvas();
		x=100;
		y=80;
//		f.setIgnoreRepaint(true);
//		cv.setIgnoreRepaint(true);
		f.add(cv,BorderLayout.CENTER);

		Button btn=new Button("Stop");
		btn.addActionListener(new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent arg0) {
				tmr.cancel();
				if (cv.g!=null) cv.g.dispose();
				cv.g=null;
//				if (bs!=null) bs.dispose();
			}
		});
		f.add(btn,BorderLayout.SOUTH);
		f.setSize(300,300);
		f.setVisible(true);
		sleep(1000);
		try {
			cv.createBufferStrategy(2);
		}catch(IllegalStateException e){
			System.out.println("Fail createBufferStrategyh");
//			e.printStackTrace();
			System.exit(1);
		}
		bs=cv.getBufferStrategy(); // GCanvas
		tmr=new Timer();
		tmr.schedule(new MyTimer(), 1000, interval);
	}
	void sleep(long msec) {
		try {
			Thread.sleep(msec);
		} catch (InterruptedException e) {}
	}
	class GCanvas extends Canvas {
		private static final long serialVersionUID = 1L;
		Graphics g=null;
		void draw(){
			if (g!=null) g.dispose();
			bs.show();
			Toolkit.getDefaultToolkit().sync();
			g=bs.getDrawGraphics();
		}
/*
		@Override
		public void update(Graphics g0) {
			paint(g0);
		}
		@Override
		public void paint(Graphics g0){
			if (bs!=null) {
				if (g==null) bs.show();
			}
		}
*/
	}

	class MyTimer extends TimerTask {
		int busycount=0;
		boolean busy=false;

		public void run() {
			if (busy) {
				System.out.print("Busy");
				System.out.println(busycount++);
			}else{
				busy=true;
				cv.draw();  // GCanvas
				if (bs.contentsLost()) {
					tmr.cancel();
					bs.dispose();
					cv.g.dispose();
					System.exit(2);
				}
				cv.g.setColor(Color.BLACK);
//				cv.g.fillOval(x, y, bww, bhh);
				cv.g.fillRect(0,0,bufw+50,bufh+50);
				x+=vx;
				y+=vy;
				if ((x<=10)||(x>=bufw)) vx=-vx;
				if ((y<=10)||(y>=bufh)) vy=-vy;
				cv.g.setColor(Color.YELLOW);
				cv.g.fillOval(x, y, bww, bhh);
				busy=false;
			}
		}
	}

	public static void main(String[] args) {
		new BufStrCanvas();
	}
}

ざざっと作ってみたレベルなので、改良の余地は色々あると思います。
stopボタンをクリックしてタイマータスクを止めると、通常のpaint()では
描画されません(即ち、窓を隠して再表示させた時に画面が戻りません)。
その対処をしたい場合は、コメントにしている GCanvasのupdate()〜
paint()部分を活かしてください。

これが実際に速いのかどうか、(Linux上では)よくわかりません。
intervalを20(ms)などに小さくしても、処理は充分間に合っているようですが
実際の表示は間引かれているように見えます。
※ 追記
Toolkit.getDefaultToolkit().sync();を追加すると表示の間引きがなくなったような感じがします。

後日、Windowsなどでも試してみようと思っていますが、とり急ぎ、参考まで。
ご意見、ご質問などは、お気軽に メールフォーム からどうぞ。

この記事へのコメントはこちら

メールアドレスは公開されませんのでご安心ください。
また、* が付いている欄は必須項目となりますので、必ずご記入をお願いします。

内容に問題なければ、下記の「コメント送信」ボタンを押してください。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)