0

I need to plot animated frames in which each pixel is calculated on the fly as the result of an algorithm. Full screen animations may thus require many millions of operations per frame. I would like to achieve the highest refresh rate possible, preferably 20 to 30+ frames per second, if possible.

Can someone show me how to design/write a highly optimized architecture for extremely fast frame refreshment in java?

This needs to be platform independent, so I cannot take advantage of hardware acceleration. The code will be executed on each individual user's computer, NOT on a central server. Of course, I am separately approaching this from the standpoint of simplifying the algorithms for generating the pixel values within-each-frame, but this question is about architecture for high speed frame-by-frame refreshment between-frames, independent of the algorithm used to generate pixel values within each frame. For example, in answers to this posting, I am looking for methods such as using:

  • BufferedImage,
  • double-buffering,
  • multi-threading,
  • accelerated off-screen images,
  • other between-frames methods, etc.

I wrote some sample code below to simulate the problem. At full screen on my notebook computer, the code below individually refreshes 1,300,000+ pixels per frame with unique values. This takes 500 milliseconds per frame on a machine with four processors and 8 gigabytes of memory. I suspect that I am not using BufferedImage correctly below, and I would really like to learn about other between-frame, architecture-level techniques for optimizing the code below, independent of the algorithms I will end up using to calculate pixel values within-each-frame. Your code samples and links to articles would be much appreciated.

How can I improve the code below, from an frame-by-frame (between-frames) architecture standpoint, not from a within-frame standpoint?

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;

public class TestBuffer {
    private static void createAndShowUI() {
        TestPanel fastGraphicsPanel = new TestPanel();
        JFrame frame = new JFrame("This Needs A Faster Architecture!");
        frame.getContentPane().add(fastGraphicsPanel);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setPreferredSize(new Dimension(800,600));
        frame.setResizable(true);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
    public static void main(String[] args) {java.awt.EventQueue.invokeLater(new Runnable() {  
    public void run() {createAndShowUI();}});}
}

@SuppressWarnings("serial")
class TestPanel extends JPanel {
    int w, h;
    private static int WIDTH = 700;
    private static int HEIGHT = 500;
    private static final Color BACKGROUND_COLOR = Color.white;
    private BufferedImage bImg;
    private Color color = Color.black;

    public TestPanel() {
        bImg =  new BufferedImage(WIDTH, HEIGHT,BufferedImage.TYPE_INT_RGB);
        Graphics g = bImg.getGraphics();
        g.setColor(BACKGROUND_COLOR);
        g.fillRect(0, 0, WIDTH, HEIGHT);

        Timer myTimer = new Timer(10, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if(w!=0&&h!=0){
                    if(WIDTH!=w&&HEIGHT!=h){
                        WIDTH = w; HEIGHT = h;
                        bImg =  new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
                    }
                }
                repaint();
            }
        });
        myTimer.setInitialDelay(0);
        myTimer.setRepeats(true);
        myTimer.setCoalesce(true);
        myTimer.start();
        g.dispose();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        w = getWidth();
        h = getHeight();
//      System.out.println("w, h are: "+w+", "+h);
        long startTime = System.currentTimeMillis();
        g.drawImage(bImg, 0, 0, null);
        long endDrawImageTime = System.currentTimeMillis();
        Graphics2D g2 = (Graphics2D) g;
        drawRandomScreen(g2);
        long endDrawScreenTime = System.currentTimeMillis();
        long stopTime = System.currentTimeMillis();
        long drawImageTime = endDrawImageTime - startTime;
        long drawScreenTime = endDrawScreenTime - endDrawImageTime;
        long elapsedTime = stopTime - startTime;
        System.out.println(drawImageTime+", "+drawScreenTime+", "+elapsedTime); 
    }

    private void drawRandomScreen(Graphics2D g2) {
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        for(int i=0;i<WIDTH;i++){
            for(int j=0;j<HEIGHT;j++){
                    color = new Color((int) (Math.random() * 255),(int) (Math.random() * 255),(int) (Math.random() * 255));
                    g2.setColor(color);
                    g2.drawLine(i, j, i, j);
            }
        }
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
CodeMed
  • 9,527
  • 70
  • 212
  • 364
  • Since when OpenGL is considered platform dependent? – Vincent Cantin Jun 26 '13 at 02:43
  • not appropriate for stackoverflow try programmers.stackexchange.com – BevynQ Jun 26 '13 at 02:55
  • I can't see something wrong with yout question, simple +1, 1. have to calculating with latency in Native OS, 2. for example in WinXP is about 26-30 ms, then 10milis overloading this value 2,5-3 times(a few times touched this issue here), 3. follows this idea, [create an BufferedImage and inside paitnCOmponent to draw only image stored in local variable](http://stackoverflow.com/a/7944388/714968) – mKorbel Jun 26 '13 at 05:35

3 Answers3

2

In the example given, drawRandomScreen() executes on the event dispatch thread. As an alternative, let the model evolve on a separate thread (or threads) and sample the resulting image at a sustainable rate, say 25 Hz, using the observer pattern. Synchronize access to the shared image buffer. A complete example is examined here. Profile to verify ongoing optimization efforts.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
1

instead of interacting through Graphics2D you should interact direct with image data. This is my code, with my laptop can run with 20 frame/second (fullscreen)

 @SuppressWarnings("serial")
class TestPanel extends JPanel {
    int w, h;
    private static int WIDTH = 700;
    private static int HEIGHT = 500;
    private static final Color BACKGROUND_COLOR = Color.white;
    private BufferedImage bImg;
    private Color color = Color.black;

    public TestPanel() {
        bImg =  new BufferedImage(WIDTH, HEIGHT,BufferedImage.TYPE_INT_RGB);
        Graphics g = bImg.getGraphics();
        g.setColor(BACKGROUND_COLOR);
        g.fillRect(0, 0, WIDTH, HEIGHT);

        Timer myTimer = new Timer(10, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if(w!=0&&h!=0){
                    if(WIDTH!=w&&HEIGHT!=h){
                        WIDTH = w; HEIGHT = h;
                        System.out.println("create");
                        bImg =  new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
                    }
                }
                repaint();
            }
        });
        myTimer.setInitialDelay(0);
        myTimer.setRepeats(true);
        myTimer.setCoalesce(true);
        myTimer.start();
        g.dispose();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        w = getWidth();
        h = getHeight();
//      System.out.println("w, h are: "+w+", "+h);
        long startTime = System.currentTimeMillis();
        long endDrawImageTime = System.currentTimeMillis();
//        Graphics2D g2 = (Graphics2D) g;
        drawRandomScreen(bImg);
        g.drawImage(bImg, 0, 0, null);
        long endDrawScreenTime = System.currentTimeMillis();
        long stopTime = System.currentTimeMillis();
        long drawImageTime = endDrawImageTime - startTime;
        long drawScreenTime = endDrawScreenTime - endDrawImageTime;
        long elapsedTime = stopTime - startTime;
        System.out.println(drawImageTime+", "+drawScreenTime+", "+elapsedTime); 
    }

    private void drawRandomScreen(BufferedImage image) {

        final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
        final int width = image.getWidth();
        final int height = image.getHeight();

        long startTime = System.currentTimeMillis();
//        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
//                RenderingHints.VALUE_ANTIALIAS_ON);
        Random r = new Random();
        for(int i=0;i<width;i++){
            for(int j=0;j<height;j++){
                    color = new Color(r.nextInt(255),r.nextInt(255),r.nextInt(255));
                    int pos = j*width+i;
                    pixels[pos] = color.hashCode();

            }
        }
        long stopTime = System.currentTimeMillis();
        System.out.println("time "+(stopTime-startTime));
    }
}
  • +1 Thank you very much. This runs at 12 frames per second full screen on my laptop, perhaps because I have a 17" screen. I have a question about the line pos = j*width+i; Does this mean that the databuffer for a raster is always a 1d array? Also, I could not find the getData() method at http://docs.oracle.com/javase/6/docs/api/java/awt/image/DataBuffer.html even though it seems to work fine on my computer. Where can I find documentation for getData()? Finally, your use of java.util.random reduced the frame cycle time by 50ms compared to Math.random() on my machine. – CodeMed Jun 26 '13 at 19:01
  • You can find getData() method in http://docs.oracle.com/javase/6/docs/api/java/awt/image/DataBufferInt.html#getData%28%29 – namthienmenh Jun 27 '13 at 01:55
  • 1
    Yes, databuffer is 1d array. And I think you should change your algorithm to render. It repeat million times for render an frame is the worst. Such as this problem, you can random change a number of pixel (ie 100k times) and perform random swap data of pixels array (ie 1000 times, using arraycopy). I thinks the performace can increase 10-20 times – namthienmenh Jun 27 '13 at 02:05
  • +1 again for helping again. Thank you. But where specifically in the code should I use arraycopy to get the 10-20 times increase in performance? Do you mind editing your code in your response to show what you mean? – CodeMed Jun 28 '13 at 02:20
1

This is your new code. In my laptop, fullscreen can run at 150 frames/ second. At you can see, time execution of function drawRandomScreen only 1/2 time of drawImageTime.

private void drawRandomScreen(BufferedImage image) {

    final int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
    final int width = image.getWidth();
    final int height = image.getHeight();

    long startTime = System.currentTimeMillis();
    Random r = new Random();
    int size = pixels.length;
    //swap 100 times
    int maxPixelsSwap = 1000;
    size-=maxPixelsSwap;
    for (int i = 0; i < 100; i++) {
        int src = r.nextInt(size);            
        int des = src+r.nextInt(size-src);
        int swapsize = r.nextInt(maxPixelsSwap); //maximium
        int[] temp = new int[swapsize];
        System.arraycopy(pixels, des, temp, 0, swapsize);
        System.arraycopy(pixels, src, pixels, des, swapsize);
        System.arraycopy(temp, 0, pixels, src, swapsize);

    }

    size = pixels.length;
    int randomTimes = size/10; //only change 10% of pixels
    size--;

    for (int i = 0; i < randomTimes; i++) {
        pixels[r.nextInt(size)]=r.nextInt();
    }
    long stopTime = System.currentTimeMillis();
    System.out.println("time "+(stopTime-startTime));
}
  • Thank you again. +1 for giving more ideas. I am marking your prior answer as the answer to this because it focuses on between frame optimization, while this answer seems to focus on within-frame optimization. The random colors are just a placeholder that allow comparisons of different between-frame optimizations. My actual plotting will be of meaningful(non-random) stuff, and I will need to optimize those within-frame algorithms separately, probably using techniques like the one in your second answer. Thanks again. – CodeMed Jun 28 '13 at 19:24