Go to Previous Page Go to Contents Go to Java Page Go to Next Page
New Features of Java2 SDK, Standard Edition, v1.4
 
 

Buffer Strategy

 
 

ちらつき防止には Double Buffering

 
 

Java だと簡単なアニメーションであればすぐ作れるので、Applet などに使われている方も多いと思います。そんなとき、アニメーションのちらつきを防止するために Double Buffering がよく使われています。

Double Buffering は画像を表示するためのバッファを使って、描画行う方法です。画面を表示しているときに、バッファに描画を行い、描画が終わったらそれを表示させます。描画を行うことより、バッファのコピーの方が速いので、ちらつきを減らすことができるのです。

DoubleBuffering を行うには Image オブジェクトか BufferedImage オブジェクト (もちろん VolatileImage でも OK です) を生成して、そこに描画します。描画が終わったら Graphics クラスの drawImage メソッドで Image オブジェクトもしくは BufferedImage オブジェクトを描画するという方法になります。

でも、これって結構面倒ですよね。

また、Java2 SE, v1.4 で導入された VolatileImage クラスを使えば、次のようなこともできます。

VolatileImage オブジェクトは直接 DirectX の VRAM や X Window の pixmap に直接書き込みを行うことができます。というか、VRAM などを指しているポインタが、直接 VolatileImage を指すようになっています (ビデオポインタといいます)。そこで、2 枚の VolatileImage オブジェクトを用意します。ビデオポインタが一方の VolatileImage オブジェクトを指しているときに、もう一方の VolatileImage オブジェクトに描画を行います。

描画が終わったら、ポインタを変更して描画が終わった VolatileImage オブジェクトを指すようにします。ポインタが指さなくなった、もう一方の VolatileImage オブジェクトに今度は描画を行うようにします。

この場合、ポインタの指している先を変えるだけなので、バッファのコピーがない分、高速に画面の切り替えを行うことができます。これを Page Flipping といいます。

でも、やっぱりこれをやるのもかなり面倒ですね。

そこで、登場したのが BufferStrategy クラスです。BufferStrategy クラスは VolatileImage オブジェクトを使用して、Double Buffering や Page Flipping を簡単に実現するためのクラスです。

使い方も簡単なので、ぜひ活用してみてください。

 

 
 

まずは Frame で試してみる

 
 

java.awt.image.BufferStrategy オブジェクトを作成するには、java.awt.Window クラスの createBufferStrategy メソッドを使用します。このメソッドの引数は int 型で、バッファの枚数を指定します。2 だったら Double Buffering、3 のときが Page Plipping に相当します。それ以上の数も指定できます。

このメソッドは java.awt.Window クラスのメソッドなので、使えるのは java.awt.Frame、java.awt.Dialog、javax.swing.JWindow、javax.swing.JFrame、javax.swing.JDialog クラスなど java.awt.Window の派生クラスになります。

次のサンプルアプリケーションは複数のバッファを切り替えるだけのアプリケーションです。切り替えたバッファを区別できるように色と文字でバッファを表しています。

アプリケーションのソース BufferStrategyTest1.java

使用するときは、引数にバッファの枚数を指定してください。色を 13 色しか定義していないので、それ以下にしておいてください。

C:\>
java BufferStrategyTest1 3

切り替わっているのが、分かりましたか?

それでは、ソースを見ていきましょう。BufferStrategy を使用しているのはコンストラクタの中なので、その部分を抜粋したのを次に示します。

 1:         try{
 2:            frame.createBufferStrategy(bufferSize);
 3:            BufferStrategy bufferStrategy = frame.getBufferStrategy();
 4:            while(true){
 5:                for (int i = 0; i < bufferSize; i++) {
 6:                    Graphics g = bufferStrategy.getDrawGraphics();
 7:                    if(!bufferStrategy.contentsLost()){
 8:                        g.setColor(colors[i]);
 9:                        g.fillRect(0, 0, 400, 400);
10:                        g.setFont(new Font("Helvetica", Font.PLAIN, 64));
11:                        g.setColor(Color.black);
12:                        g.drawString("Buffer No." + i, 20, 220);
13:                        bufferStrategy.show();
14:                        g.dispose();
15:                    }
16:                    try {
17:                        Thread.sleep((int)1000L);
18:                    } catch (InterruptedException ex) {}
19:                }
20:            }
21:        }catch (IllegalStateException ex){
22:            ex.printStackTrace();
23:        }

まず、2 行目で createBufferStrategy メソッドをコールしています。引数の bufferSize はバッファの枚数を表しています。createBufferStrategy メソッドは 2 種類の例外を投げます。一方が引数が 1 以下の時に投げられる IllegalArgumentException、もう一方が Window が表示できないときに投げられる IllegalStateException です。どちらも、RuntimeException なので、例外処理は記述しなくてもいいのですが、とりあえず IllegalStateException だけ書いておきました(といっても、例外のスタックトレースを表示させてるだけですが)。

createBufferStrategy メソッドは内部で BufferStrategy オブジェクトを作るだけなので、実際にこれを取り出すのは getBufferStrategy メソッドを使用します (3 行目)。

次からが BufferStrategy オブジェクトを使用して、バッファに描画を行う部分になります。

まず、6 行目で行っているように、Graphics オブジェクトを getDrawGraphics メソッドを使用して、BufferStrategy オブジェクトから取得します。

Graphics オブジェクトを取得した後、描画する前に BufferStrategy クラスの contentsLost メソッドを使ってバッファが消失していないかどうかを調べておきます。これは、BufferStrategy クラスがバッファに VolatileImage を使用するため、Contents Lost がおこりえるためです。詳しくは VolatileImage の説明を参考にしてください。

Contents Lost がおこっていなければ、後は普通のグラフィックの描画と同じになります。

描画が終わったら、バッファを切り替えて表示させます (13 行目)。これには BufferStrategy クラスの show メソッドを使用します。

そして、最後にバッファを切り替えてしまったので、不要になった Graphics オブジェクトを廃棄します (14 行目)。

結局、次のような手順になります。

  1. createBufferStrategy メソッドで BufferStrategy オブジェクトの生成
  2. getBufferStrategy メソッドを使用して BufferStrategy オブジェクトの取得
  3. getDrawGraphics メソッドで Graphics オブジェクトを得る
  4. contentsLost メソッドでバッファがロストしていないかを調べる。
  5. 描画
  6. show メソッドを使用することで、バッファの切り替えを行い、表示する
  7. Graphics オブジェクトを廃棄する

一度、BufferStrategy オブジェクトが得られれば、1 と 2 の処理はいらないので、3 から 6 を繰り返すようになります。

今、描画しているのが何枚目のバッファか、などは気にしなくても複数バッファを切り替えて使えるようになります。また、BufferStrategy クラスは内部で持っているバッファは VolatileImage なので高速に描画を行うことができます。

 

 
 

フルスクリーンでも試してみよう

 
 

もちろん、フルスクリーンでも BufferStrategy クラスは使用できます。使い方もまったく同じです。というか、フルスクリーンで使うことを前提に作られたようです。このため、BufferStrategy クラスのチュートリアルは FullScreen のチュートリアルの一章で扱われています。

BufferStrategyTest1 のフルスクリーン版が BufferStrategyTest2 になります。

アプリケーションのソース BufferStrategyTest2.java

フレームをフルスクリーンにした以外はなにも変わっていません。

 

 
 

普通のコンポーネントでも使ってみたい

 
 

今までの例で、BufferStrategy クラスが Window クラスの派生クラスで使えることが分かりました。ゲームみたいに画面全体で行うようなアプリケーションであればいいのですが、普通のボタンなどのコンポーネントを組み合わせたアプリケーションの一部にアニメーションを入れるなどの用途には使えません。

ところが、Window クラスの createBufferStrategy の JavaDoc には

Overrides:
  createBufferStrategy in class Component

と記述されています。ということは、Component の派生クラスでも使えるかるということになりますが.... 実際には、Component クラスの createBufferStrategy メソッドは public ではないので使用することができません。残念。

でも、Component の派生クラスで、Window クラスのようにオーバーライドして使えるようになったものはないのでしょうか。Java の API を探してみたら、1 つだけありました。それは java.awt.Canvas クラスです。残念ながら Swing のコンポーネントにはありませんでした。

というわけで、Canvas クラスだけは BufferStrategy クラスを使った Double Buffering や Page Filipping が行えます。

ただし、いろいろ試してみたところ、次の 2 点の制約がありました。

  • フルスクリーンでは使えない
  • アプレットでも使えない

アプレットで使えないのは結構残念ですね。ただし、これは J2SDK のベータで試したので、もしかすると正式版では変わるかもしれません。

といっていてもしょうがないので、サンプルを作ってみました。前回の FullScreen の回のおまけで作成した FullScreenField をCanvas を使用して書き直してみました。Ball クラスの方はそのまま前回のものを使用しました。

アプリケーションのソース BallField.java
Ball.java

まず、クラスの定義ですが、BallField クラスは Canvas クラスの派生クラスにしました。

public class BallField extends Canvas {
 
    private BufferStrategy bufferStrategy;
    private int DOUBLE_BUFFER = 2;
    private int PAGE_FLIP = 3;

プロパティとして BufferStrategy オブジェクトである bufferStrategy があります。また、定数の DOUBLE_BUFFER と PAGE_FLIP も定義しました。その他は、前回の FullScreenField クラスと同様に Ball クラスの配列や、アニメーションを行うための Timer オブジェクト、コンポーネントの幅と高さがあります。

bufferStrategy はコンストラクタで取得しています。

 1:    public BallField(){
 2:        initFrame();
 3: 
 4:        try{
 5:            createBufferStrategy(DOUBLE_BUFFER);
 6://          createBufferStrategy(PAGE_FLIP);
 7:            bufferStrategy = getBufferStrategy();
 8:        }catch(IllegalStateException ex){
 9:            ex.printStackTrace();
10:            System.exit(0);
11:        }
12: 
13:        balls = new java.util.ArrayList();
14:        balls.add(new Ball(width, height));
15:
16:        timer = new java.util.Timer();
17:        timer.schedule(new java.util.TimerTask(){
18:            public void run(){
19:                draw();
20:            }
21:        }, 1000, 60);
22:    }

2 行目の initFrame で BallField オブジェクトを表示するフレームを生成して、設定を行っています。このとき、フレームの下部に 3 つのボタンを配置しました。それぞれ、「ボールの増加」、「ボールの進行方向変更」、「終了」です。

5 行目の createBufferStrategy で BufferStrategy オブジェクトを生成しています。このままでは Double Buffering になります。Page Flipping を行いたいときは、5 行目をコメントアウトして、6 行目のコメント記号を削除すれば OK です。

生成した BufferStrategy オブジェクトを取得しているのが、7 行目の getBufferStrategy メソッドです。

createBufferStrategy は前述したように IllegalStateException を発生しますので、例外処理を 8 から 11 行に記述してあります。

13 行目からは FullScreenField と同様です。13, 14 行目でボールの配列を生成して、はじめは 1 つだけ Ball オブジェクトを入れておきます。

15 から 21 行はアニメーションのためのタイマの生成と設定を行っています。これで、60 ms ごとに draw メソッドをコールするようになります。

それでは、次に描画を行う draw メソッドを説明しましょう。

 1:    public void draw(){
 2:        for(int i = 0 ; i < balls.size() ; i++){
 3:            ((Ball)balls.get(i)).move();
 4:        }
 5: 
 6:        Graphics2D g = (Graphics2D)bufferStrategy.getDrawGraphics();
 7:        if(!bufferStrategy.contentsLost()){
 8:            clear(g);
 9:            for(int i = 0 ; i < balls.size() ; i++){
10:                ((Ball)balls.get(i)).drawShadow(g);
11:            }
12:            
13:            for(int i = 0 ; i < balls.size() ; i++){
14:                ((Ball)balls.get(i)).draw(g);
15:            }
16:            bufferStrategy.show();
17:            g.dispose();
18:        }
19:    }

2 行目から 4 行目ではボールの移動を起こっています。実際の描画はこの後に行われます。

BufferStrategy クラスの使い方は Frame クラスでも Canvas クラスでも変わりません。

まず、6 行目で Graphcis オブジェクト (ここでは Graphics2D にキャストしていますが) を取得し、contentsLost メソッドでバッファが消失したかを調べ (7 行目)、描画します (8 から 15 行目)。描画が終われば、show メソッドで表示を行い (16 行目)、Graphics オブジェクトを廃棄します。

できあがった BallField はこんな感じです。ウィンドウの下部にあるボタンと Canvas が同居していて、かつ BufferStrategy が使用できているのが分かると思います。

Ball

 
 

最後に

 
 

BufferStrategy を使えば、自前でイメージのバッファを管理する必要がないので、アニメーションを行うにはとても便利です。でも、java.awt.Window のサブクラスか、java.awt.Canvas しか使えないのがちょっと困り者ですね。

Swing のコンポーネントベースのアプリケーションでもアニメーションを使うことはあると思うんですけど...

それでも、ゲームなどにはかなり役立ちそうですね。

今回使用したサンプルはここからダウンロードできます。

参考 URL

(Jul. 2001)

 
 
Go to Previous Page Go to Contents Go to Java Page Go to Next Page