Java Swing 2D优化:如何高效绘制大量移动小星星?
Swing 2D星背景渲染性能优化问题
我正在开发一款游戏,游戏背景包含大量从右向左缓慢移动的小星星。全屏状态下每帧需绘制约170颗星,渲染每帧的CPU时间主要消耗在星星绘制上。请问是否有更高效的资源利用方式?
我已参考过相关Swing 2D性能优化要点,尝试过两种实现方案:
- 使用设备兼容的VolatileImage(采用三种变体之一以避免BITMASK透明度带来的缩放问题),平均每帧渲染耗时约22ms,分析器输出显示drawImage操作占主要CPU耗时。
- 改用*fillOval()绘制星星而非图片,每帧运行时间降至约20ms,分析器输出显示fillOval()*为主要耗时操作。
测试环境为搭载Intel UHD 620显卡、3840x2160显示器的笔记本电脑。我希望这款游戏能在多种配置的设备上流畅运行,因此需要在这类入门级显卡设备上也有良好表现。
我愿意尝试其他实现相同星背景效果的策略,但核心目标是深入理解Swing 2D的性能优化逻辑,从而全面提升游戏的整体性能。
以下是展示当前实现方式的最小可复现示例(MRE),在我的测试设备上使用VolatileImage的平均渲染速度比直接绘制椭圆更慢:
package space; import java.awt.AWTException; import java.awt.BufferCapabilities; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.ImageCapabilities; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Transparency; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.VolatileImage; import java.util.Random; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.RepaintManager; import javax.swing.SwingUtilities; import javax.swing.Timer; public class MRE { private static final int OVAL_SIZE = 4; private static final int NUM_STARS = 150; private static final boolean useImages = false; private static VolatileImage volatileImage; public static void main(String[] args) { System.setProperty("sun.java2d.opengl", "true"); System.setProperty("sun.java2d.accthreshold", "0"); JFrame frame = new JFrame(); frame.setSize(new Dimension(1000, 800)); frame.setIgnoreRepaint(true); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); RepaintManager manager = new RepaintManager() { @Override public synchronized void addDirtyRegion(JComponent c, int x, int y, int w, int h) { // Ignored } @Override public Rectangle getDirtyRegion(JComponent aComponent) { return aComponent.getBounds(); } @Override public void markCompletelyDirty(JComponent aComponent) { // Ignored } @Override public void markCompletelyClean(JComponent aComponent) { // Ignored } }; createVolatileImage(frame); RepaintManager.setCurrentManager(manager); Timer st = new Timer(250, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { long startTime; startTime = System.nanoTime(); JComponent contentPanel = ((JComponent) frame.getContentPane()); contentPanel.paintImmediately(contentPanel.getBounds()); long elapsed = System.nanoTime() - startTime; System.out.println("Rendering time: " + elapsed / 1000000.0 + "ms."); } }); frame.setContentPane(createContentPane()); setWindowVisible(frame, true); st.start(); } private static void createVolatileImage(JFrame frame) { GraphicsConfiguration gc = frame.getGraphicsConfiguration(); volatileImage = gc.createCompatibleVolatileImage(4, 4, Transparency.BITMASK); Graphics2D g2 = volatileImage.createGraphics(); g2.setColor(Color.WHITE); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.fillOval(0, 0, OVAL_SIZE, OVAL_SIZE); g2.dispose(); } private static JPanel createContentPane() { JPanel contentPane = new JPanel() { private static final long serialVersionUID = 2500224655024127049L; @Override public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D)g; Random rand = new Random(); g2.setColor(Color.BLACK); g2.fillRect(0, 0, getWidth(), getHeight()); for(int i = 0; i < NUM_STARS; i++) { double x = rand.nextDouble() * getWidth(); double y = rand.nextDouble() * getHeight(); g2.translate(x, y); if(useImages) { g2.drawImage(volatileImage, 0, 0, null); } else { g2.setColor(Color.WHITE); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.fillOval(0, 0, OVAL_SIZE, OVAL_SIZE); } g2.translate(-x, -y); } } }; return contentPane; } private static void setWindowVisible(JFrame frame, final boolean visible) { SwingUtilities.invokeLater(() -> { frame.setVisible(visible); try { final BufferCapabilities caps = new BufferCapabilities(new ImageCapabilities(true), new ImageCapabilities(true), BufferCapabilities.FlipContents.UNDEFINED); frame.createBufferStrategy(2, caps); } catch (final AWTException e) { frame.createBufferStrategy(2); System.err.println("Could not set double buffered FLIP strategy, using default double buffer: " + e.getLocalizedMessage()); } }); } }
内容的提问来源于stack exchange,提问作者working_man
相关产品推荐
相关产品推荐

