このトピックでは、Java 2Dハードウェア高速化レンダリング・プリミティブに関する一般的な問題、プリミティブ・トレースを検出して非高速化レンダリングを回避する方法について説明します。レンダリング・パフォーマンスの低下には様々な原因が考えられます。次のトピックでは、アプリケーションのレンダリング・パフォーマンスの低下の原因を特定し、ソフトウェアのみのレンダリング・パフォーマンスを改善するためのアプローチを提案します。
このトピックでは、次の項目について説明します。
パフォーマンス問題の原因についての理解を深めるため、ハードウェア高速化の意味について考えてます。
一般に、ハードウェア高速化レンダリングは2つのカテゴリに分けられます。
「高速化対象」の宛先へのハードウェア高速化レンダリング。ハードウェア高速化が可能なレンダリングの宛先の例として、VolatileImage
、画面、およびBufferStrategy
があげられます。ある宛先が高速化対象である場合、そのような表面に対するレンダリングはビデオ・ハードウェアによって実行される可能性があります。したがって、ユーザーがdrawRect
呼出しを発行すると、Java 2Dはその呼出しを、ベースとなるネイティブAPI (GDI、DirectDraw、Direct3D、OpenGL、X11など)にリダイレクトし、そこでハードウェアを使って操作が実行されます。
高速化メモリー(ビデオ・メモリーまたはピックスマップ)へのイメージのキャッシング。これにより、それらのイメージを別の高速化対象表面に非常に高速でコピーできるようになります。そのようなイメージは「管理対象イメージ」と呼ばれます。
理想的には、高速化対象表面に対して実行される操作はすべて、ハードウェアで高速化されます。この場合、アプリケーションはプラットフォームによって提供されるメリットを最大限に享受できます。
残念ながら多くの場合、デフォルト・パイプラインはレンダリングにハードウェアを使用できません。その原因は、パイプラインの制限やベースとなるネイティブAPIにあります。たとえば、ほとんどのXサーバーは、アンチエイリアス・プリミティブのレンダリングやアルファ合成をサポートしません。
パフォーマンス問題の原因の1つは、実行される操作がハードウェア高速化されない場合にあります。宛先表面が高速化される場合でも、その一部のプリミティブが高速化されない可能性があります。
ハードウェア高速化が使用されていない場合を検出する方法を知ることが重要です。それを知れば、パフォーマンスの改善が容易になる可能性があります。
高速化されないレンダリングを検出するには、Java 2Dプリミティブ・トレースを使用できます。
Java 2Dには組込みのプリミティブ・トレースが含まれています。「Java 2Dテクノロジのシステム・プロパティ
」のtraceプロパティの説明を参照してください。
アプリケーションの実行時に-Dsun.java2d.trace=count
を指定します。アプリケーションが終了すると、プリミティブのリストとそれらのカウントがコンソールに出力されます。
MaskBlit
プリミティブや任意のGeneral*
プリミティブが表示された場合、それは通常、レンダリングの一部がソフトウェア・ループによって処理されていることを意味します。Linux上で、半透明のBufferedImage
に対してdrawImage
を実行してVolatileImage
に書き込む場合の出力を、次に示します。
sun.java2d.loops.Blit$GeneralMaskBlit::Blit(IntArgb, SrcOverNoEa, "Integer BGR Pixmap")sun.java2d.loops.MaskBlit::MaskBlit(IntArgb, SrcOver, IntBgr)
デフォルト・パイプラインでの一般的な非高速化プリミティブと、トレース出力に示されるそれらの特徴をいくつかあげてみます。注意: このトレースのほとんどはLinuxで行われたものです。使用しているプラットフォームや構成によっては多少の違いが生じる可能性があります。
半透明イメージ(ColorModel.getTranslucency()
でTranslucency.TRANSLUCENT
が返されるイメージ)、またはAlphaCompositing
を使用したもの。サンプルのプリミティブ・トレース出力:
sun.java2d.loops.Blit$GeneralMaskBlit::Blit(IntArgb,SrcOverNoEa, "Integer BGR Pixmap")sun.java2d.loops.MaskBlit::MaskBlit(IntArgb, SrcOver, IntBgr)
アンチエイリアスの使用(アンチエイリアス・ヒントの設定による)。サンプルのプリミティブ・トレース出力:
sun.java2d.loops.MaskFill::MaskFill(AnyColor, Src, IntBgr)
アンチエイリアス・テキストのレンダリング(テキスト・アンチエイリアス・ヒントの設定)。サンプル出力は次のいずれかになります。
sun.java2d.loops.DrawGlyphListAA::DrawGlyphListAA(OpaqueColor, SrcNoEa, AnyInt)
sun.java2d.loops.DrawGlyphListLCD::DrawGlyphListLCD(AnyColor, SrcNoEa, IntBgr)
アルファ合成(半透明カラー(0xff
以外のアルファ値を含むカラー)でのレンダリング、Graphics2D.setComposite()
によるデフォルト以外のAlphaCompositing
モードの設定、のいずれかによる)。
sun.java2d.loops.Blit$GeneralMaskBlit::Blit(IntArgb, SrcOver, IntRgb)sun.java2d.loops.MaskBlit::MaskBlit(IntArgb, SrcOver, IntRgb)
本格的な変換(単なる移動ではない変換)。変換された不透明イメージのVolatileImage
へのレンダリング。
sun.java2d.loops.TransformHelper::TransformHelper(IntBgr, SrcNoEa, IntArgbPre)
回転されるラインのレンダリング。
sun.java2d.loops.DrawPath::DrawPath(AnyColor, SrcNoEa, AnyInt)
アプリケーションをトレース付きで実行し、不要な非高速化プリミティブを使用していないことを確認してください。
いくつかのレンダリング・パフォーマンスの低下の原因および可能な選択肢を次に示します。
高速化レンダリングと非高速化レンダリングの混在:
特定のパイプラインでの高速化表面へのレンダリング時に、アプリケーションによってレンダリングされるプリミティブの一部しか高速化できない状況では、スラッシングが発生する可能性があります。パイプラインで常にレンダリング・パフォーマンスの改善に向けた調整が試行されても、成功する可能性がおそらくほとんどないからです。
レンダリング・プリミティブの大部分が高速化されないことが事前にわかっている場合、BufferedImage
にレンダリングしてからバック・バッファや画面にコピーするか、あるいは前述のフラグのいずれかを使って非ハードウェア高速化パイプラインに切り替えたほうが得策です。
注: このアプローチを採用した場合、Java 2Dでのハードウェア高速化の使用状況が将来改善されても、そのメリットをアプリケーションで享受できなくなる可能性があります。 |
たとえば、リモートXサーバー・ケースでの使用頻度の高いアプリケーション内でアンチエイリアスやアルファ合成などが多用されると、パフォーマンスが極度に低下する可能性があります。これを避けるには、-Dsun.java2d.pmoffscreen=false
プロパティをJavaランタイムに渡すかプログラム内でSystem.setProperty()
APIを使って設定して、ピックスマップの使用を無効にします。注意: このプロパティは一度だけ読み込まれるので、GUI関連の操作の前に設定する必要があります。
最適でないレンダリング・プリミティブの使用:
目的の視覚効果を実現する際に、可能なかぎりもっとも単純なプリミティブを使用することをお薦めします。
たとえば、Graphics.drawLine()
を使用して新しいLine2D().draw()
は使用しません。結果は同じようにみえます。ただし、2番目の操作では、通常レンダリング・コストの非常に高い汎用形状としてレンダリングされるため、計算量が格段に多くなります。形状は、プリミティブ・トレース内で、アンチエイリアス設定や特定のパイプラインに応じてさまざまな方法で表示されますが、おそらく多数の*FillSpans
またはDrawPath
プリミティブとして表示されます。
複雑な属性のもう1つの例は、GradientPaint
です。これは、デフォルト以外の一部のパイプライン(OpenGLなど)ではハードウェア高速化される可能性がありますが、デフォルト・パイプラインではハードウェア高速化されません。したがって、パフォーマンスの問題が発生する場合にGradientPaint
の使用を制限できます。
ヒープに基づく出力先のBufferedImage:
BufferedImage
へのレンダリングでは、ほとんど常にソフトウェア・ループが使用されます。
一部のSPARCシステムでの例外は、特定のイメージング操作を高速化するためにVIS命令セットが使用される場合があることです。VIS命令セットを参照してください。
レンダリングが高速化される機会を残すには、BufferStrategy
またはVolatileImage
オブジェクトをレンダリング先として選択してください。
組込み高速化メカニズムの無効化:
Java 2Dは特定タイプのイメージを高速化しようとします。VolatileImage
などの高速化宛先へのコピーを高速化するため、イメージの内容がビデオ・メモリーにキャッシュされる可能性があります。これらのメカニズムは、意図せずにアプリケーションで無効化される場合があります。
getDataBuffer()によるピクセルへの直接アクセス:
アプリケーションがgetRaster().getDataBuffer()
APIを使ってBufferedImage
ピクセルにアクセスした場合、Java 2Dは、キャッシュ内のデータが最新であることを保証できないため、そのようなイメージの高速化の試みをすべて無効にします。
これを回避するには、getDataBuffer()
を呼び出さないでください。代わりに、BufferedImage.getRaster()
メソッドで取得可能なWriteableRaster
を操作します。
ピクセルを直接変更する必要がない場合には、イメージをビデオ・メモリー内に手動でキャッシュするためにイメージのキャッシュ・コピーをVolatileImage
内に保持し、元のイメージが更新された時点でそのキャッシュ・データを更新します。
すべてのコピーの前のスプライト・レンダリング:
アプリケーションがイメージを高速化表面(VolatileImage
、BufferStrategy
)にコピーする前にイメージに毎回レンダリングする場合、そのイメージは、高速化メモリーにキャッシュされるメリットを享受できません。その理由は、元のイメージが更新されるたびにキャッシュ・コピーを更新する必要があり、そのためにデフォルトのシステム・メモリー・ベースの表面だけが使用されて、高速化されないからです。
高速化メモリー・リソースの枯渇:
アプリケーションが多数のイメージを使用すると、使用可能な高速化メモリーが使い果たされる可能性があります。これが本当にアプリケーションのパフォーマンス問題の原因となっている場合、リソースを管理しなければいけない可能性があります。
次のAPIを使えば、使用可能な高速化メモリーの量を要求できます。GraphicsDevice.getAvailableAcceleratedMemory()
。
さらに次のAPIを使えば、イメージが高速化されているかどうかを判定できます。Image.getCapabilities()
。
アプリケーションがリソースを使い果たしていると判断した場合は、必要のないイメージを保持しないことにより、問題を処理できます。たとえば、ゲームが次のレベルに進んだら、以前のレベルに含まれるイメージをすべて解放します。また、イメージに関連付けられた高速化リソースをImage.flush()
APIで解放することもできます。
高速化優先度API Image.getAccelerationPriority()
およびsetAccelerationPriority()
を使用してイメージの高速化優先度を指定することもできます。少なくともバック・バッファを高速化することは妥当なので、これを最初に作成し、高速化優先度1 (デフォルト)を指定します。また、必要であれば、高速化優先度を0.0に設定して、特定のイメージが高速化されないようにすることもできます。
アプリケーションが(BufferedImage
へのレンダリングのみを行うかデフォルト・パイプラインを非高速化パイプラインに変更したために)ソフトウェア専用レンダリングに依存している場合、あるいはアプリケーションが混在されたレンダリングを行う場合でも、次に示す特定のアプローチによってパフォーマンスが改善します。
最適化されたサポートとイメージの種類または操作:
プラットフォーム全体のサイズ制約のために、あるイメージ形式を別の形式に変換するための最適化されたルーチンは、Java 2Dには限られた数しか含まれていません。最適化された直接ループが見つからない状況では、Java 2Dは中間イメージ形式(IntArgb
)を介して変換します。この場合、パフォーマンスが低下します。
Java 2Dプリミティブ・トレースを使用すればそのような状況を検出できます。
各drawImage
呼出しに対して2つのプリミティブが存在します。1つ目はイメージをソース形式から中間IntArgb
形式に変換し、2つ目は中間IntArgb
から変換先の形式に変換します。
そのような状況を回避する2つの方法を次に示します。
可能であれば別のイメージ形式を使用します。
イメージを、よりサポート・レベルの高い形式(INT_RGB
やINT_ARGB
など)の中間イメージに変換します。そうすれば、カスタム・イメージ形式からの変換が、コピーごとに発生する代わりに一度だけ発生するようになります。
透明と半透明:
できれば、完全な半透明(INT_ARGB
など)のイメージではなく、1ビット透明(BITMASK
)のイメージをスプライトとして使用することを検討してください。
完全なアルファを持つイメージの処理では、CPUの負荷がより高くなります。
1ビット透明イメージを取得するには、GraphicsConfiguration.createCompatibleImage(w,h, Transparency.BITMASK)
の呼出しを使用します。