2010-07-20 2 views
3

Продолжая с previous question, я продолжаю поиск оптимального способа объединения активного рендеринга с текстовыми полями в Java. Я попробовал несколько вариантов, используя BufferStrategy, VolatileImage или переопределяя update() и paint() в стандартном AWT, но в итоге я использовал Swing.Эффективная эффективность рендеринга Swing или как объединить активный рендеринг с gui-виджетами

Я размещаю текущее состояние дел здесь, на случай, если у кого-то возникнет новая идея, основанная на моем примере кода, и, возможно, другие, которые работают над подобным приложением, могут извлечь выгоду из моих результатов.

Цель состоит в том, чтобы выполнить эти три умения:

  • делает объект оживлённого поверх фонового буфера, который обновляется только при необходимости
  • использовать текстовые поля в верхней части оказанных результате
  • изменения размера окно без проблем

Ниже приведен код демонстрационного приложения, разработанного с большой помощью stackoverflower trashgod.
Две ноты:

1) Обновление, строго нарушенное предыдущим шагом в анимации, похоже, настолько подвержено визуальным ошибкам, что я отказался от него. Это означает, что я теперь перерисовываю весь фоновый буфер в каждом кадре.

2) Эффективность рисования BufferedImage на экране сильно зависит от платформы. Реализация Mac, похоже, не поддерживает аппаратное ускорение должным образом, что делает перерисовку фонового изображения на выходное окно утомительной задачей, в зависимости от размера окна.

я нашел следующие результаты на моем 2,93 ГГц DualCore ИМАК:

Mac OS 10.5:
640 х 480: 0,9 мс, 8 - 9%
1920 х 1100: 5 мс, 35 - 40%

Windows XP:
640 х 480: 0.05 мс, 0%
1920 х 1100: 0,05 мс, 0%

Легенда:
размер экрана : среднее время рисования кадра, использование процессора приложением.

Насколько я вижу, приведенный ниже код является наиболее эффективным способом достижения моих целей. Любые новые идеи, оптимизация или результаты тестов очень приветствуются!

С уважением, 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

У меня есть несколько замечаний:

  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>(); 
    
+0

1) Спасибо за указатели. Я буду использовать использование процессора, как сообщается собственным диспетчером активности/диспетчером задач системы, как наиболее надежным эталоном. Я включил эти данные в свой первоначальный пост, он все еще указывает, что Windows использует аппаратное ускорение для перерисовки изображения, в отличие от Mac OS. 2) Ах, действительно, без дополнительного JPanel он просто меняется. 3) И правда снова. К счастью, это приложение предназначено только для демонстрации, но в моем реальном приложении я буду помнить об этом. – Mattijs

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 как аргумент, но никогда не использовать его.

+0

Я согласен, это, должно быть, осталось от более ранней версии. Мне плохо, спасибо, что указали это. – Mattijs

 Смежные вопросы

  • Нет связанных вопросов^_^