13 Java 2D

この章では、Java 2D APIで発生する可能性のあるもっとも一般的な問題のいくつかをトラブルシューティングするための情報とガイダンスを提供します。

この章の構成は、次のとおりです。

Java 2Dプロパティのサマリーについては、「Java 2Dのプロパティ」を参照してください。

一般的なパフォーマンス問題

レンダリング・パフォーマンスの低下には様々な原因が考えられます。次のトピックでは、アプリケーションのレンダリング・パフォーマンスの低下の原因を特定し、ソフトウェアのみのレンダリング・パフォーマンスを改善するためのアプローチを提案します。

このトピックでは、次の項目について説明します。

ハードウェア高速化レンダリング・プリミティブ

パフォーマンス問題の原因についての理解を深めるため、ハードウェア高速化の意味について考えてます。

一般に、ハードウェア高速化レンダリングは2つのカテゴリに分けられます。

  • 「高速化対象」の宛先へのハードウェア高速化レンダリング。ハードウェア高速化が可能なレンダリングの宛先の例として、VolatileImage、画面、およびBufferStrategyがあげられます。宛先が高速化対象である場合、表面に対するレンダリングはビデオ・ハードウェアによって実行される可能性があります。したがって、ユーザーがdrawRect呼出しを発行すると、Java 2Dはその呼出しを、ベースとなるネイティブAPI (GDI、DirectDraw、Direct3D、OpenGL、X11など)にリダイレクトし、そこでハードウェアを使用して操作が実行されます。

  • 高速化メモリー(ビデオ・メモリーまたはピックスマップ)へのイメージのキャッシング。これにより、それらのイメージを別の高速化対象表面に非常に高速でコピーできるようになります。これらのイメージは管理対象イメージと呼ばれます。

理想的には、高速化対象表面で実行される操作はすべて、ハードウェアで高速化されます。この場合、アプリケーションはプラットフォームによって提供されるメリットを最大限に享受できます。

残念ながら多くの場合、デフォルト・パイプラインはレンダリングにハードウェアを使用できません。その原因は、パイプラインの制限やベースとなるネイティブAPIにあります。たとえば、ほとんどのXサーバーは、アンチエイリアス・プリミティブのレンダリングやアルファ合成をサポートしません。

パフォーマンス問題の原因の1つは、実行される操作がハードウェア高速化されない場合にあります。宛先表面が高速化される場合でも、その一部のプリミティブが高速化されない可能性があります。

ハードウェア高速化が使用されていない場合を検出する方法を知ることが重要です。それを知れば、パフォーマンスの改善が容易になる可能性があります。

非高速化レンダリングの検出および回避のためのプリミティブ・トレース

高速化されないレンダリングを検出するには、Java 2Dプリミティブ・トレースを使用できます。

アプリケーションの実行時に-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を使用して設定して、ピックスマップの使用を無効にします。デフォルトのXrenderパイプラインは、リモートのX11でもアンチエイリアス処理されたテキストおよび標準合成モードをサポートしているため、アプリケーションによっては、ピックスマップの方が依然として高性能な場合があります。これはテストして検証する必要があります。

    ノート:

    このプロパティは1回のみ読み込まれるので、GUI関連の操作の前に設定する必要があります。
  • 最適でないレンダリング・プリミティブ:

    目的の視覚効果を実現する際に、可能なかぎりもっとも単純なプリミティブを使用することをお薦めします。

    たとえば、Graphics.drawLine()を使用して新しいLine2D().draw()は使用しません。結果は同じようにみえます。ただし、2番目の操作では、通常レンダリング・コストの非常に高い汎用形状としてレンダリングされるため、計算量が格段に多くなります。形状は、プリミティブ・トレース内で、アンチエイリアス設定や特定のパイプラインに応じてさまざまな方法で表示されますが、おそらく多数の*FillSpansまたはDrawPathプリミティブとして表示されます。

    複雑な属性のもう1つの例は、GradientPaintです。これは、デフォルト以外の一部のパイプライン(OpenGLなど)ではハードウェア高速化される可能性がありますが、デフォルト・パイプラインではハードウェア高速化されません。したがって、パフォーマンスの問題が発生する場合にGradientPaintの使用を制限できます。

  • ヒープに基づく出力先のBufferedImage:

    BufferedImageへのレンダリングでは、ほとんど常にソフトウェア・ループが使用されます。

    レンダリングが高速化される機会を残すには、BufferStrategyまたはVolatileImageオブジェクトをレンダリング先として選択してください。

  • 組込み高速化メカニズムの無効化:

    Java 2Dは特定タイプのイメージを高速化しようとします。VolatileImagesなどの高速化宛先へのコピーを高速化するため、イメージの内容がビデオ・メモリーにキャッシュされる可能性があります。これらのメカニズムは、意図せずにアプリケーションで無効化される場合があります。

  • getDataBuffer()によるピクセルへの直接アクセス:

    アプリケーションがgetRaster().getDataBuffer() APIを使用してBufferedImageピクセルにアクセスした場合、Java 2Dは、キャッシュ内のデータが最新であることを保証できないため、このタイプのイメージの高速化の試みをすべて無効にします。

    これを回避するには、getDataBuffer()を呼び出さないでください。代わりに、BufferedImage.getRaster()メソッドで取得可能なWriteableRasterを操作します。

    ピクセルを直接変更する必要がない場合には、イメージをビデオ・メモリー内に手動でキャッシュするためにイメージのキャッシュ・コピーをVolatileImage内に保持し、元のイメージが更新された時点でそのキャッシュ・データを更新します。

  • すべてのコピーの前のスプライト・レンダリング:

    アプリケーションがイメージを高速化表面(VolatileImageBufferStrategy)にコピーする前にイメージにレンダリングする場合、そのイメージは、高速化メモリーにキャッシュされるメリットを享受できません。その理由は、元のイメージが更新されるたびにキャッシュ・コピーを更新する必要があり、そのためにデフォルトのシステムメモリーベースの表面だけが使用されて、高速化されないからです。

  • 高速化メモリー・リソースの枯渇:

    アプリケーションが多数のイメージを使用すると、使用可能な高速化メモリーが使い果たされる可能性があります。これがアプリケーションのパフォーマンス問題の原因となっている場合、リソースを管理しなければいけない可能性があります。

    次のAPIを使えば、使用可能な高速化メモリーの量を要求できます。GraphicsDevice.getAvailableAcceleratedMemory()

    さらに次のAPIを使えば、イメージが高速化されているかどうかを判定できます。Image.getCapabilities()

    アプリケーションがリソースを使い果たしていると判断した場合は、必要のないイメージを保持しないことにより、問題を処理できます。たとえば、ゲームが次のレベルに進んだら、以前のレベルに含まれるイメージをすべて解放します。また、イメージに関連付けられた高速化リソースをImage.flush() APIで解放することもできます。

    高速化優先度API Image.getAccelerationPriority()およびsetAccelerationPriority()を使用してイメージの高速化優先度を指定することもできます。少なくともバック・バッファを高速化することは妥当なので、これを最初に作成し、高速化優先度1 (デフォルト)を指定します。また、必要であれば、高速化優先度を0.0に設定して、特定のイメージが高速化されないようにすることもできます。

ソフトウェアのみのレンダリングのパフォーマンスの改善

アプリケーションが(BufferedImageへのレンダリングのみを行うかデフォルト・パイプラインを非高速化パイプラインに変更したために)ソフトウェア専用レンダリングに依存している場合、あるいはアプリケーションが混在されたレンダリングを行う場合でも、次に示す特定のアプローチによってパフォーマンスが改善します。

  1. 最適化されたサポートとイメージの種類または操作:

    プラットフォーム全体のサイズ制約のために、あるイメージ形式を別の形式に変換するための最適化されたルーチンは、Java 2Dには限られた数しか含まれていません。最適化された直接ループが見つからない状況では、Java 2Dは中間イメージ形式(IntArgb)を介して変換します。この場合、パフォーマンスが低下します。

    Java 2Dプリミティブ・トレースを使用すればそのような状況を検出できます。

    drawImage呼出しに対して2つのプリミティブが存在します。1つ目はイメージをソース形式から中間IntArgb形式に変換し、2つ目は中間IntArgbから変換先の形式に変換します。

    そのような状況を回避する2つの方法を次に示します。

    • 可能であれば別のイメージ形式を使用します。

    • イメージを、よりサポート・レベルの高い形式(INT_RGBINT_ARGBなど)の中間イメージに変換します。そうすれば、カスタム・イメージ形式からの変換が、コピーごとに発生する代わりに一度だけ発生するようになります。

  2. 透明と半透明:

    できれば、完全な半透明(INT_ARGBなど)のイメージではなく、1ビット透明(BITMASK)のイメージをスプライトとして使用することを検討してください。

    完全なアルファを持つイメージの処理では、CPUの負荷がより高くなります。

    1ビット透明イメージを取得するには、GraphicsConfiguration.createCompatibleImage(w,h, Transparency.BITMASK)の呼出しを使用します。

テキスト関連の問題

この項では、テキストのレンダリングに関連して発生する可能性がある問題とクラッシュ、およびそのような問題を克服するためのヒントについて説明します。

この項には次のサブセクションが含まれます:

フォント・ロードのトレース

-Dsun.java2d.debugfonts=trueプロパティを設定すると、Java 2Dによってロードされたフォントに関する情報が生成されます。Java 2Dが検出したフォントを確認し、使用するフォントを推測し、拒否するフォントに関する情報を確認できます。このプロパティによって、次のような出力が生成されます:

INFO: Registered file C:\WINDOWS\Fonts\WINGDING.TTF as font ** TrueType Font: Family=Wingdings
 Name=Wingdings style=0 fileName=C:\WINDOWS\Fonts\WINGDING.TTF rank=2
Aug 16, 2006 10:59:06 PM sun.font.FontManager initialiseDeferredFont
INFO: Opening deferred font file SYMBOL.TTF
Aug 16, 2006 10:59:06 PM sun.font.FontManager addToFontList
INFO: Add to Family Symbol, Font Symbol rank=2
Aug 16, 2006 10:59:06 PM sun.font.FontManager registerFontFile
INFO: Registered file C:\WINDOWS\Fonts\SYMBOL.TTF as font ** TrueType Font: Family=Symbol
 Name=Symbol style=0 fileName=C:\WINDOWS\Fonts\SYMBOL.TTF rank=2
Aug 16, 2006 10:59:06 PM sun.font.FontManager findFont2D
INFO: Search for font: Dialog
Aug 16, 2006 10:59:06 PM sun.font.FontManager initialiseDeferredFont
INFO: Opening deferred font file ARIALBD.TTF
Aug 16, 2006 10:59:06 PM sun.font.FontManager addToFontList
INFO: Add to Family Arial, Font Arial Bold rank=2
Aug 16, 2006 10:59:06 PM sun.font.FontManager registerFontFile
INFO: Registered file C:\WINDOWS\Fonts\ARIALBD.TTF as font ** TrueType Font: Family=Arial
 Name=Arial Bold style=1 fileName=C:\WINDOWS\Fonts\ARIALBD.TTF rank=2
Aug 16, 2006 10:59:06 PM sun.font.FontManager initialiseDeferredFont
INFO: Opening deferred font file WINGDING.TTF
Aug 16, 2006 10:59:06 PM sun.font.FontManager initialiseDeferredFont
INFO: Opening deferred font file SYMBOL.TTF
Aug 16, 2006 10:59:06 PM sun.font.FontManager findFont2D
INFO: Search for font: Dialog
Aug 16, 2006 10:59:06 PM sun.font.FontManager initialiseDeferredFont
INFO: Opening deferred font file ARIAL.TTF
Aug 16, 2006 10:59:06 PM sun.font.FontManager addToFontList
INFO: Add to Family Arial, Font Arial rank=2
Aug 16, 2006 10:59:06 PM sun.font.FontManager registerFontFile
INFO: Registered file C:\WINDOWS\Fonts\ARIAL.TTF as font ** TrueType Font: Family=Arial
 Name=Arial style=0 fileName=C:\WINDOWS\Fonts\ARIAL.TTF rank=2
Aug 16, 2006 10:59:06 PM sun.font.FontManager initialiseDeferredFont
INFO: Opening deferred font file WINGDING.TTF
Aug 16, 2006 10:59:06 PM sun.font.FontManager initialiseDeferredFont
INFO: Opening deferred font file SYMBOL.TTF

テキストの外観の違い

Javaアプリケーションとネイティブ・アプリケーションのテキスト表示の違いは、使用されるフォント・ラスタライザが異なることが原因である可能性があります。Javaアプリケーションは通常、ネイティブ・アプリケーションが使用するのと同じ、オペレーティング・システムのフォント・ラスタライザを使用します。ただし、Javaアプリケーションでは、オペレーティング・システムにインストールまたは含まれている、フォント・レンダリング用ソフトウェア・ライブラリであるFreeTypeが使用される場合があります。たとえば、FreeTypeは、Windowsではグレースケール・テキストに、macOSではmacOSフォント・ラスタライザでサポートされていないType 1フォントに使用される場合があります。ただし、Windowsには複数のフォント・ラスタライザがあることに注意してください。その結果、ネイティブ・アプリケーションによってテキスト・レンダリングが異なる場合があります。さらに、すべてのプラットフォームでプラットフォームのラスタライズにある程度の構成可能性があり、テキストの外観にわずかな違いが生じる可能性があります。

その他の相違の原因としては次のようなものが挙げられます:

  • サブピクセル位置決めが使用されているかどうか。これはグリフの正確な位置決めに影響します。「java.awt.RenderingHints.KEY_FRACTIONALMETRICS」を参照してください。
  • Linuxでは、Swingは異なるアンチエイリアシング設定を選択する場合があります。

この動作には、考えられる理由がいくつかあります。

  • リモートX11接続経由のアンチエイリアスは、パフォーマンス上の理由により、デフォルトでは有効になりません。
  • 埋込みビットマップを使用するCJKフォントは、サブピクセル・テキストの代わりにビットマップを使ってレンダリングされる可能性があります。

  • サポートされていないデスクトップの中には、そのフォント・スムージング設定が適切に報告されないものがあります。たとえば、KDEはサポートされていませんが、たいていは動作するはずです。しかし、なんらかの問題によってJDKはその設定を検出できないようです。

Java言語のフォントのサイズは常に72 dpiで表現されます。ネイティブOSでは別の画面dpiを使用できるため、調整が必要になります。対応するJavaフォント・サイズを計算するには、Toolkit.getScreenResolution()を72で割り、その結果にネイティブ・フォントのサイズを掛けます。

WindowsルックアンドフィールやLinuxオペレーティング・システム用のGTKルックアンドフィールなど、すべてのネイティブSwingルックアンドフィールで、Swingコンポーネントによりこの調整が自動的に実行されます。

Windows以外のオペレーティング・システムでは通常、Type1フォントではなくTrueTypeフォントの使用をお薦めします。フォント・タイプを知る最も簡単な方法はファイル拡張子を調べることです。pfaおよびpfbという拡張子はType1フォント、ttfttcおよびtteという拡張子はTrueTypeフォントを表します。

フォント・メトリックス

テキストの境界が予想していたものと異なることに気づいた場合、境界の適切な計算方法を使用していることを確認してください。たとえば、FontMetricsから得られる高さは特定のテキストに固有のものではなく、stringWidthは論理的な有効幅を示し、と同じものではありません。詳細は、Java 2D FAQのフォントおよびテキストに関する質問を参照してください。