Java2D性能问题

59

我在使用Java2D时遇到了性能问题。虽然我知道可以通过设置sun.java2d.opengl VM参数来启用2D的3D加速,但即使这样也会出现一些奇怪的问题。

以下是我运行的测试结果:

绘制一个25x18像素大小,32x32像素大小的瓷砖地图到JComponent上
图像1 = .bmp格式,图像2 = .png格式

没有使用-Dsun.java2d.opengl=true参数

使用.bmp图像1:120帧每秒
使用.png图像2:13帧每秒

使用-Dsun.java2d.opengl=true参数

使用.bmp图像1:12帧每秒
使用.png图像2:700帧每秒

如果没有加速,我认为每次使用drawImage()方法时都会进行某种转换,从而导致.png图像的FPS大大降低。然而,为什么有了加速后,结果会反过来(.png图像实际上表现得更快)?太疯狂了!

图像1被转换为TYPE_INT_RGB类型的图像。图像2被转换为TYPE_CUSTOM类型的图像。为了在有和没有OpenGL加速的情况下获得一致的速度,我必须创建一个新的BufferedImage,并将图像1或图像2绘制到这个新图像中。

以下是运行结果:

没有使用-Dsun.java2d.opengl=true

使用.bmp图像1:120帧每秒
使用.png图像2:120帧每秒

使用-Dsun.java2d.opengl=true参数

使用.bmp图像1:700帧每秒
使用.png图像2:700帧每秒

我的真正问题是,我可以假设TYPE_INT_ARGB将是所有系统和平台的本地图像类型吗?我认为这个值可能会有所不同。有没有办法让我获得本地值,以便我始终可以为最大性能创建新的BufferedImage?

提前感谢...

3个回答

71

通过研究并从太多的谷歌搜索中汇集碎片,我认为我找到了一个解决方案。

这就是它,包括所有评论:

private BufferedImage toCompatibleImage(BufferedImage image)
{
    // obtain the current system graphical settings
    GraphicsConfiguration gfxConfig = GraphicsEnvironment.
        getLocalGraphicsEnvironment().getDefaultScreenDevice().
        getDefaultConfiguration();

    /*
     * if image is already compatible and optimized for current system 
     * settings, simply return it
     */
    if (image.getColorModel().equals(gfxConfig.getColorModel()))
        return image;

    // image is not optimized, so create a new image that is
    BufferedImage newImage = gfxConfig.createCompatibleImage(
            image.getWidth(), image.getHeight(), image.getTransparency());

    // get the graphics context of the new image to draw the old image on
    Graphics2D g2d = newImage.createGraphics();

    // actually draw the image and dispose of context no longer needed
    g2d.drawImage(image, 0, 0, null);
    g2d.dispose();

    // return the new optimized image
    return newImage; 
}

在我的上一篇文章中,GraphicsConfiguration是包含创建系统优化图像所需信息的对象。它似乎很有效,但我本以为Java会自动处理这个问题。显然,你不能过于依赖Java。:) 我猜最终我自己回答了我的问题。希望这可以帮助到一些试图使用Java制作2D游戏的人。


6
太棒了!这让我的代码运行速度快了很多。非常感谢你。 - Dennis
我对标记此回答的耽搁表示歉意。 - Consty
非常感谢你! - user3965604

4

虽然这是一篇旧文章,但我想分享一下关于使用Swing/AWT进行直接绘制而不使用BufferedImage的发现。

某些类型的绘图,例如3D,最好直接绘制到一个int[]缓冲区中。完成图像后,可以使用一个ImageProducer实例(如MemoryImageSource)来生成图像。我假设您知道如何直接进行绘图,而无需使用Graphics/Graphics2的帮助。

    /**
* How to use MemoryImageSource to render images on JPanel
* Example by A.Borges (2015)
*/
public class MyCanvas extends JPanel implements Runnable {

public int pixel[];
public int width;
public int height;
private Image imageBuffer;   
private MemoryImageSource mImageProducer;   
private ColorModel cm;    
private Thread thread;


public MyCanvas() {
    super(true);
    thread = new Thread(this, "MyCanvas Thread");
}

/**
 * Call it after been visible and after resizes.
 */
public void init(){        
    cm = getCompatibleColorModel();
    width = getWidth();
    height = getHeight();
    int screenSize = width * height;
    if(pixel == null || pixel.length < screenSize){
        pixel = new int[screenSize];
    }        
    mImageProducer =  new MemoryImageSource(width, height, cm, pixel,0, width);
    mImageProducer.setAnimated(true);
    mImageProducer.setFullBufferUpdates(true);  
    imageBuffer = Toolkit.getDefaultToolkit().createImage(mImageProducer);        
    if(thread.isInterrupted() || !thread.isAlive()){
        thread.start();
    }
}
/**
* Do your draws in here !!
* pixel is your canvas!
*/
public /* abstract */ void render(){
    // rubisch draw
    int[] p = pixel; // this avoid crash when resizing
    if(p.length != width * height) return;        
    for(int x=0; x < width; x++){
        for(int y=0; y<height; y++){
            int color =  (((x + i) % 255) & 0xFF) << 16; //red
                color |= (((y + j) % 255) & 0xFF) <<  8; //green
                color |= (((y/2 + x/2 - j) % 255) & 0xFF) ;   //blue         
            p[ x + y * width] = color;
        }
    }        
    i += 1;
    j += 1;          
}    
private int i=1,j=256;

@Override
public void run() {
    while (true) {
        // request a JPanel re-drawing
        repaint();                                  
        try {Thread.sleep(5);} catch (InterruptedException e) {}
    }
}

@Override
public void paintComponent(Graphics g) {
    super.paintComponent(g);
    // perform draws on pixels
    render();
    // ask ImageProducer to update image
    mImageProducer.newPixels();            
    // draw it on panel          
    g.drawImage(this.imageBuffer, 0, 0, this);  
}

/**
 * Overrides ImageObserver.imageUpdate.
 * Always return true, assuming that imageBuffer is ready to go when called
 */
@Override
public boolean imageUpdate(Image image, int a, int b, int c, int d, int e) {
    return true;
}
}// end class

请注意,我们需要唯一的MemoryImageSourceImage实例。除非您已调整JPanel的大小,否则不要为每个帧创建新的Image或新的ImageProducer。请参见上面的init()方法。
在渲染线程中,请求repaint()。在Swing上,repaint()将调用重写的paintComponent(),其中它调用您的render()方法,然后要求您的imageProducer更新图像。完成Image后,使用Graphics.drawImage()绘制它。
为了拥有兼容的Image,请在创建Image时使用适当的ColorModel。我使用GraphicsConfiguration.getColorModel()
/**
 * Get Best Color model available for current screen.
 * @return color model
 */
protected static ColorModel getCompatibleColorModel(){        
    GraphicsConfiguration gfx_config = GraphicsEnvironment.
            getLocalGraphicsEnvironment().getDefaultScreenDevice().
            getDefaultConfiguration();        
    return gfx_config.getColorModel();
}

只是好奇一些人为什么会对正确的回答进行负评... 这个问题涉及到 (i) Java2D;(ii) 在绘制图像时的高性能;(iii) 渲染来自其他地方的图像(OpenGL、本地渲染器、软件渲染等)。那么为什么要负评呢? - Alex Byrth

2

据我所记,当我考虑在Java中进行图形编程时,内置库的速度很慢。在GameDev.Net上,有人建议说任何认真做事的人都必须使用像jogl这样的东西。


6
由于最大的性能损失实际上是调用drawImage(),所以我认为450个调用且帧率为700FPS还算不错。我需要用jogl编写相同的内容来查看是否存在显着差异,但在Java的最新版本中,Java2D已经取得了巨大进展。 - Consty

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接