This topic describes generic performance issues related to Java 2D hardware accelerated rendering primitives, how to detect primitive tracing and avoid non-accelerated rendering. There could be many causes for poor rendering performance. The following topics identify the cause for your applications poor rendering performance and suggests some approaches to improve performance of software-only rendering.
This topic contains the following subsections:
In order to better understand what could be causing performance problems, take a look at what hardware acceleration means.
In general, hardware accelerated rendering could be divided into two categories.
Hardware-accelerated rendering to an "accelerated" destination. Examples of rendering destinations which can be hardware-accelerated are VolatileImage
, screen, and BufferStrategy
. If a destination is accelerated, rendering which goes to such surface may be performed by video hardware. So if you issue a drawRect
call, Java 2D redirects this call to the underlying native API (such as GDI, DirectDraw, Direct3D or OpenGL, or X11), which performs the operation using hardware.
Caching images in accelerated memory (Video memory or pixmaps) so that they can be copied very fast to another accelerated surface. Such images are known as "managed images."
Ideally, all operations performed to an accelerated surface are hardware-accelerated. In this case the application takes the full advantage that is offered by the platform.
Unfortunately in many cases the default pipelines are not able to use the hardware for rendering. This can happen due to the pipeline limitations, or the underlying native API. For example, most X servers do not support rendering antialiased primitives, or alpha compositing.
One cause of performance issues is when operations performed are not hardware-accelerated. Even in cases when a destination surface is accelerated, some primitives may not be.
It is important to know how to detect the cases when hardware acceleration is not being used. Knowing this may help in improving performance.
To detect a non-accelerated rendering, you can use Java 2D primitive tracing.
Java 2D has built-in primitive tracing. See the description of the trace
property at System Properties for Java 2D Technology.
Run your application with -Dsun.java2d.trace=count
. When the application exits, a list of primitives and their counts is printed to the console.
Any time you see a MaskBlit
or any of the General*
primitives, it typically means that some of your rendering is going through software loops. Here is the output from performing drawImage
on a translucent BufferedImage
to a VolatileImage
on Linux:
sun.java2d.loops.Blit$GeneralMaskBlit::Blit(IntArgb, SrcOverNoEa, "Integer BGR Pixmap")sun.java2d.loops.MaskBlit::MaskBlit(IntArgb, SrcOver, IntBgr)
Here are some of the common non-accelerated primitives in the default pipelines, and their signatures in the tracing output. Note: Most of this tracing was taken on Linux; you may see some differences depending on your platform and configuration.
Translucent images (Images with ColorModel.getTranslucency()
returns Translucency.TRANSLUCENT
), or with AlphaCompositing
. Sample primitive tracing output:
sun.java2d.loops.Blit$GeneralMaskBlit::Blit(IntArgb,SrcOverNoEa, "Integer BGR Pixmap")sun.java2d.loops.MaskBlit::MaskBlit(IntArgb, SrcOver, IntBgr)
Use of antialiasing (by setting the antialiasing hint). Sample primitive tracing output:
sun.java2d.loops.MaskFill::MaskFill(AnyColor, Src, IntBgr)
Rendering antialiased text (setting the text antialising hint). Sample output can be one of the following:
sun.java2d.loops.DrawGlyphListAA::DrawGlyphListAA(OpaqueColor, SrcNoEa, AnyInt)
sun.java2d.loops.DrawGlyphListLCD::DrawGlyphListLCD(AnyColor, SrcNoEa, IntBgr)
Alpha compositing, either by rendering with translucent Color (a Color with alpha value which is not 0xff
), or by setting a non-default AlphaCompositing
mode with Graphics2D.setComposite()
:
sun.java2d.loops.Blit$GeneralMaskBlit::Blit(IntArgb, SrcOver, IntRgb)sun.java2d.loops.MaskBlit::MaskBlit(IntArgb, SrcOver, IntRgb)
Non-trivial transforms (if the transform is more than only translation). Rendering a transformed opaque image to a VolatileImage
:
sun.java2d.loops.TransformHelper::TransformHelper(IntBgr, SrcNoEa, IntArgbPre)
Rendering a rotated line:
sun.java2d.loops.DrawPath::DrawPath(AnyColor, SrcNoEa, AnyInt)
Run your application with the tracing and make sure you do not use unaccelerated primitives unless they are needed.
Some of the possible causes of poor rendering performance and possible alternatives are described as follows:
Mixing accelerated and non-accelerated rendering:
A situation when only part of the primitives rendered by an application could be accelerated by the particular pipeline when rendering to an accelerated surface can cause thrashing, because the pipelines will be constantly trying to adjust for better rendering performance but with possibly little success.
If it is known beforehand that most of the rendering primitives will not be accelerated, it could be better to either render to a BufferedImage
and then copy it to the back-buffer or the screen, or switch to a non-hardware accelerated pipeline using one of the flags discussed above.
Note: This approach may limit your application's ability to take advantage of future improvements in Java 2D's use of hardware acceleration. |
For example, if your application is often used in remote X server cases, but it heavily uses antialiasing, alpha compositing, and so forth, the performance can be severely degraded. To avoid this, disable the use of Pixmaps by setting the -Dsun.java2d.pmoffscreen=false
property either by passing it to the Java runtime, or by setting it programmatically using the System.setProperty()
API. Note: This property must be set prior to any GUI-related operations because it is read only once.
Non-optimal rendering primitives:
It is preferable to use the simplest primitive possible to achieve the desired visual effect.
For example, use Graphics.drawLine()
instead of new Line2D().draw()
. The result looks the same. However, the second operation is much more computationally-intensive because it is rendered as a generic shape, which is typically much more expensive to render. Shapes show up in different ways in the primitive tracing, depending on antialiasing settings and the specific pipeline, but most likely they will show up as many *FillSpans
or DrawPath
primitives.
Another example of complicated attributes is GradientPaint
. Although it may be hardware accelerated by some of the non-default pipelines (such as OpenGL), it is not hardware accelerated by the default pipelines. Therefore, you can restrict the use of GradientPaint
if it causes performance problems.
Heap-based destination surface BufferedImage:
Rendering to a BufferedImage
almost always uses software loops.
An exception on some SPARC systems is that the VIS instruction set may be used for accelerating certain imaging operations. See VIS Instruction Set.
To ensure that the rendering has the opportunity of being hardware accelerated, choose a BufferStrategy
or a VolatileImage
object as the rendering destination.
Defeat built-in acceleration mechanism:
Java 2D attempts to accelerate certain types of images. Contents of images may be cached in video memory for faster copying to accelerated destinations such as VolatileImages
. These mechanisms can be unknowingly defeated by the application.
Get direct access to pixels with getDataBuffer():
If an application gets access to BufferedImage
pixels by using the getRaster().getDataBuffer()
API, Java 2D will not be able to guarantee that the data in the cache is up to date, so it will disable any acceleration attempts of such image.
To avoid this, do not call getDataBuffer()
. Instead, work with WriteableRaster
, which can be obtained with the BufferedImage.getRaster()
method.
If you do need to modify the pixels directly, you can manually cache your image in video memory by maintaining the cached copy of your image in a VolatileImage
, and updating the cached data when the original image is touched.
Render to a sprite before every copy:
If an application renders to an image every time before copying it to an accelerated surface (VolatileImage
, BufferStrategy
), the image cannot take advantage of being cached in accelerated memory. This is because the cached copy has to be updated every time the original image is updated, and therefore only the default system-memory based surface is used, and this means no acceleration.
Exhausted accelerated memory resources:
If the application uses many images, it may exhaust the available accelerated memory. If this is indeed the cause of performance issues for your application, you might need to handle the resources.
The following API can be used to request the amount of available accelerated memory: GraphicsDevice.getAvailableAcceleratedMemory()
.
In addition, the following API can be used to determine if your image is being accelerated: Image.getCapabilities()
.
If you determined that your application is exhausting the resources, you can handle the problem by not holding images you no longer need. For example, if your game advanced to the next level, release all images from the previous levels. You can also release accelerated resources associated with an image by using the Image.flush()
API.
You can also use the acceleration priority API Image.getAccelerationPriority()
and setAccelerationPriority()
to specify the acceleration priority for your images. It is a good idea to make sure that at least your back-buffer is accelerated, so create it first, and with acceleration priority of 1 (default). You can also prohibit certain images from being accelerated if needed by setting the acceleration priority to 0.0.
If your application relies on software-only rendering (by only rendering to a BufferedImage
, or changing the default pipeline to an unaccelerated one), or even if it does mixed rendering, then the following are certain approaches to improving performance:
Image types or operations with optimized support:
Due to overall platform size constraints, Java 2D has a limited number of optimized routines for converting from one image format to another. In situations where an optimized direct loop can not be found, Java 2D will do the conversion through an intermediate image format (IntArgb
). This results in performance degradation.
Java 2D primitive tracing can be used for detecting such situations.
For each drawImage
call there will be two primitives: the first one converting the image from the source format to an intermediate IntArgb
format and the second one converting from intermediate IntArgb
to the destination format.
Here are two ways to avoid such situations:
Use a different image format if possible.
Convert your image to an intermediate image of one of the better-supported formats, such as INT_RGB
or INT_ARGB
. In this way the conversion from the custom image format will happen only once instead of on every copy.
Transparency vs translucency:
Consider using 1-bit transparent (BITMASK
) images for your sprites as opposed to images with full translucency (such as INT_ARGB
) if possible.
Processing images with full alpha is more CPU-intensive.
You can get a 1-bit transparent image using a call to GraphicsConfiguration.createCompatibleImage(w,h, Transparency.BITMASK)
.