摆动活动渲染效率或如何将活动渲染与gui小部件组合在一起

时间:2010-07-20 11:15:43

标签: java swing rendering textfield

继续previous question,我一直在寻找将活动渲染与Java中的文本字段组合在一起的最佳方法。我尝试了几个选项,使用BufferStrategy,VolatileImage或覆盖标准AWT中的update()和paint(),但我最终使用了Swing。

我发布了当前的事态,以防有人碰巧根据我的代码示例获得新见解,也许其他正在开发类似应用的人可能会从我的调查结果中受益。

目标是完成这三项任务:

  • 在仅在必要时更新的后台缓冲区顶部渲染动画对象
  • 在渲染结果的顶部使用文本字段
  • 调整窗口大小没有问题

以下是在stackoverflower trashgod的帮助下开发的演示应用程序的代码 两个注意事项:

1)严格刷新动画中上一步失效的区域似乎很容易出现我放弃的视觉错误。这意味着我现在每帧重绘整个背景缓冲区。

2)将BufferedImage绘制到屏幕的效率在很大程度上取决于平台。 Mac实现似乎不能正确支持硬件加速,这使得将背景图像重新绘制到输出窗口是一项繁琐的任务,当然这取决于窗口的大小。

我在2.93 GHz双核iMac上发现了以下结果:

Mac OS 10.5:
640 x 480:0.9毫秒,8 - 9%
1920 x 1100:5毫秒,35 - 40%

Windows XP:
640 x 480:0.05毫秒,0%
1920 x 1100:0.05毫秒,0%

注:
屏幕大小:绘制帧的平均时间,应用程序的CPU使用率。

据我所知,下面的代码是实现目标的最有效方式。任何新的见解,优化或测试结果都是非常受欢迎的!

此致 Mattijs

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;

public class SwingTest extends JPanel implements 
 ActionListener, 
 Runnable 
{
 private static final long serialVersionUID = 1L;

 private BufferedImage backgroundBuffer;
    private boolean repaintbackground = true;

    private static final int initWidth = 640;
    private static final int initHeight = 480;
    private static final int radius = 25;
    private final Timer t = new Timer(20, this);
    private final Rectangle rect = new Rectangle(); 

    private long totalTime = 0;
    private int frames = 0;
    private long avgTime = 0;

    public static void main(String[] args) {
        EventQueue.invokeLater(new SwingTest());
    }

    public SwingTest() {
        super(true);
        this.setPreferredSize(new Dimension(initWidth, initHeight));
        this.setLayout(null);
        this.setOpaque(false);
        this.addMouseListener(new MouseHandler());
    }

    @Override
    public void run() {
        JFrame f = new JFrame("SwingTest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.addComponentListener(new ResizeHandler());

/*      This extra Panel with GridLayout is necessary to make sure 
   our content panel is properly resized with the window.*/
        JPanel p = new JPanel(new GridLayout()); 
        p.add(this);
        f.add(p);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);

        createBuffer();     
        t.start();
    }    

    @Override
    public void actionPerformed(ActionEvent e) {
        this.repaint();
    }    

    @Override
    protected void paintComponent(Graphics g) {
     long start = System.nanoTime();
     super.paintComponent(g);

     if (backgroundBuffer == null) createBuffer();
     if (repaintbackground) {

/*   Repainting the background may require complex rendering operations, 
   so we don't want to do this every frame.*/       
      repaintBackground(backgroundBuffer);
            repaintbackground = false;
     }

/*  Repainting the pre-rendered background buffer every frame
       seems unavoidable. Previous attempts to keep track of the 
       invalidated area and repaint only that part of the background buffer 
       image have failed. */
     g.drawImage(backgroundBuffer, 0, 0, null);
     repaintBall(g, backgroundBuffer, this.getWidth(), this.getHeight());
     repaintDrawTime(g, System.nanoTime() - start);
    }

    void repaintBackground(BufferedImage buffer) {    
     Graphics2D g = buffer.createGraphics();
  int width = buffer.getWidth();
  int height = buffer.getHeight();

  g.clearRect(0, 0, width, height);
  for (int i = 0; i < 100; i++) {
   g.setColor(new Color(0, 128, 0, 100));
   g.drawLine(width, height, (int)(Math.random() * (width - 1)), (int)(Math.random() * (height - 1)));
  }
    }

    void repaintBall(Graphics g, BufferedImage backBuffer, int width, int height) {
     double time = 2* Math.PI * (System.currentTimeMillis() % 3300) / 3300.;
        rect.setRect((int)(Math.sin(time) * width/3 + width/2 - radius), (int)(Math.cos(time) * height/3 + height/2) - radius, radius * 2, radius * 2);

        g.setColor(Color.BLUE);
        g.fillOval(rect.x, rect.y, rect.width, rect.height);
    }

    void repaintDrawTime(Graphics g, long frameTime) {
     if (frames == 32) {avgTime = totalTime/32; totalTime = 0; frames = 0;}
     else {totalTime += frameTime; ++frames; }
     g.setColor(Color.white);
     String s = String.valueOf(avgTime / 1000000d + " ms");
        g.drawString(s, 5, 16);
    }

    void createBuffer() {
        int width = this.getWidth();
        int height = this.getHeight();

        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gs = ge.getDefaultScreenDevice();
        GraphicsConfiguration gc = gs.getDefaultConfiguration();
        backgroundBuffer = gc.createCompatibleImage(width, height, Transparency.OPAQUE);        

        repaintbackground = true;
    }    

    private class MouseHandler extends MouseAdapter {

        @Override
        public void mousePressed(MouseEvent e) {
            super.mousePressed(e);
            JTextField field = new JTextField("test");
            Dimension d = field.getPreferredSize();
            field.setBounds(e.getX(), e.getY(), d.width, d.height);
            add(field);
        }
    }

    private class ResizeHandler extends ComponentAdapter {

     @Override
     public void componentResized(ComponentEvent e) {
      super.componentResized(e);
      System.out.println("Resized to " + getWidth() + " x " + getHeight());
      createBuffer();
     }    
    }
}

2 个答案:

答案 0 :(得分:2)

我有一些意见:

  1. 您的改进版repaintDrawTime()非常易读,但它是micro-benchmark并且受主机操作系统vagaries的约束。我不禁想知道XP结果是否是该系统limited clock resolution的工件。我在Windows 7和Ubuntu 10上看到了截然不同的结果。

  2. 如果您不使用空布局,则不需要额外的面板; JPanel的默认布局为FlowLayout,而f.add(this)只是将其添加到框架默认BorderLayout的中心。

  3. 重复的构造函数调用可能非常耗时。

    考虑更换

    g.setColor(new Color(0, 128, 0, 100));
    

    private static final Color color = new Color(0, 128, 0, 100);
    ...
    g.setColor(color);
    

    或者,简单的color lookup table可能很有用,例如

    private final Queue<Color> clut = new LinkedList<Color>();
    

答案 1 :(得分:1)

我没有看到将BufferedImage传递给此方法的重点:

void repaintBall(Graphics g, BufferedImage backBuffer, int width, int height) {
 double time = 2* Math.PI * (System.currentTimeMillis() % 3300) / 3300.;
    rect.setRect((int)(Math.sin(time) * width/3 + width/2 - radius), (int)(Math.cos(time) * height/3 + height/2) - radius, radius * 2, radius * 2);

    g.setColor(Color.BLUE);
    g.fillOval(rect.x, rect.y, rect.width, rect.height);
}

看起来你似乎没有在方法体中使用它。

我能够将这个类的图形部分移植到我自己的JPanel构造函数类中,并且它改进了我游戏的图形很多,但是我从来不需要使用像这样的方法来传递BufferedImage作为争论但从不使用它。