58 votes

Problèmes de performance de Java2D

J'ai des problèmes de performance avec Java2D. Je connais le paramètre sun.java2d.opengl VM qui permet d'activer l'accélération 3D pour la 2D, mais même en l'utilisant, je rencontre des problèmes bizarres.

Voici les résultats des tests que j'ai effectués :

Dessiner une carte de 25x18 avec des tuiles de 32x32 pixels sur un JComponent
Image 1 = format .bmp, Image 2 = format .png

Sans -Dsun.java2d.opengl=true

120 FPS en utilisant l'image .BMP 1
13 FPS en utilisant l'image .PNG 2

Avec -Dsun.java2d.opengl=true

12 FPS en utilisant l'image .BMP 1
700 FPS en utilisant l'image .PNG 2

Sans accélération, je suppose qu'une sorte de transformation a lieu à chaque fois que je fais un drawImage() dans le logiciel, et que cela réduit considérablement le nombre de FPS dans le cas du .PNG. Mais pourquoi, avec l'accélération, les résultats changeraient-ils (et le PNG est en fait incroyablement plus rapide) ? ! C'est de la folie !

L'image .BMP 1 est convertie en un type d'image de TYPE_INT_RGB. L'image .PNG 2 est convertie en un type d'image de TYPE_CUSTOM. Afin d'obtenir une vitesse constante avec et sans accélération opengl, je dois créer une nouvelle BufferedImage avec un type d'image TYPE_INT_ARGB, et dessiner l'image 1 ou l'image 2 dans cette nouvelle image.

Voici les résultats obtenus avec cette méthode :

Sans -Dsun.java2d.opengl=true

120 FPS en utilisant l'image .BMP 1
120 FPS en utilisant l'image .PNG 2

Avec -Dsun.java2d.opengl=true

700 FPS en utilisant l'image .BMP 1
700 FPS en utilisant l'image .PNG 2

Ma vraie question est la suivante : puis-je supposer que TYPE_INT_ARGB sera le type d'image natif pour tous les systèmes et toutes les plateformes ? Je suppose que cette valeur pourrait être différente. Existe-t-il un moyen pour moi d'obtenir la valeur native afin de pouvoir toujours créer de nouvelles BufferedImages pour des performances maximales ?

Merci d'avance...

67voto

Consty Points 785

Je pense avoir trouvé une solution en effectuant des recherches et en assemblant des morceaux à partir de trop nombreuses recherches sur Google.

Le voici, commentaires et tout :

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; 
}

Dans mon post précédent, GraphicsConfiguration était ce qui contenait les informations nécessaires pour créer des images optimisées sur un système. Cela semble fonctionner assez bien, mais j'aurais pensé que Java le ferait automatiquement pour vous. Évidemment vous ne pouvez pas être trop à l'aise avec Java :) Je suppose que j'ai fini par répondre à ma propre question. Enfin, j'espère que cela aidera certains d'entre vous que j'ai vus essayer d'utiliser Java pour des jeux en 2D.

6 votes

C'est absolument génial. Il permet à mon code de s'exécuter beaucoup plus rapidement. Merci beaucoup.

0 votes

Je m'excuse pour le grand retard dans le marquage de cette réponse.

0 votes

MERCI BEAUCOUP !

5voto

Alex Byrth Points 751

Bon, c'est un vieux post mais je voudrais partager mes découvertes sur le dessin direct avec Swing/AWT, sans BufferedImage.

Certains types de dessins, comme la 3D, sont mieux réalisés lorsque l'on peint directement sur un écran. int[] tampon. Une fois les images faites, vous pouvez utiliser un ImageProducer exemple, comme MemoryImageSource pour produire des images. Je suppose que vous savez comment réaliser vos dessins directement, sans l'aide de 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

Notez que nous avons besoin d'une instance unique de MemoryImageSource et Image . Ne créez pas une nouvelle image ou un nouveau ImageProducer pour chaque cadre, sauf si vous avez redimensionné votre JPanel. Voir init() méthode ci-dessus.

Dans un fil de rendu, demandez à un repeindre() . Sur la balançoire, repeindre() appellera la fonction surchargée paintComponent() où il appelle votre render() et demandez ensuite à votre imageProducer de mettre à jour l'image. L'image étant faite, dessinez-la avec Graphics.drawImage() .

Pour avoir une image compatible, utilisez le bon ColorModel lorsque vous créez votre Image . J'utilise 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();
}

2voto

Nick Stinemates Points 5642

D'après ce dont je me souviens lorsque j'envisageais de faire de la programmation graphique en Java, les bibliothèques intégrées sont lentes. On m'a conseillé sur GameDev.Net que toute personne faisant quelque chose de sérieux devrait utiliser quelque chose comme jogl

6 votes

Etant donné que la plus grande pénalité de performance est l'appel à drawImage(), je considère que 450 appels avec 700 FPS est plutôt bon. Il faudrait que j'écrive la même chose en jogl pour voir s'il y a une différence significative, mais de grands progrès ont été réalisés avec Java2D dans les versions récentes de Java.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X