Java Swing 2D - 快速绘制许多小星星

问题描述 投票:0回答:1

我正在尝试做一个游戏,在游戏中有一个背景,有许多从右到左慢慢移动的小星星。在全屏的情况下,我可能每帧绘制 170 颗星星。渲染每一帧的大部分 CPU 时间都花在绘制星星上。有没有办法更有效地利用资源?

我已经考虑了这个答案中的要点:https://stackoverflow.com/a/200493/21618530

我使用的一种方法是使用设备兼容的 VolatileImages,使用 3 种变体中的一种来避免使用 BITMASK 透明度进行缩放。以这种方式渲染每一帧平均需要 22 毫秒。

jvisualvm profiler output for drawImage

对星星使用

fillOval()
而不是绘制图像将运行时间减少到每帧约 20 毫秒,使用此分析器输出:

jvisualvm profiler output for fillOval

这是在配备 Intel UHD 620 和 3840x2160 显示器的笔记本电脑上。我打算让这个游戏简单易玩,并且可以在各种图形上玩,所以如果它能在这样的设备上运行良好就很好了。


我对其他策略持开放态度以获得相同的结果,但我的目标是更好地理解 Swing 2d 性能,以便我可以整体改进游戏。

MRE 展示了我是如何做到这一点的,在我的机器上,不稳定图像的使用平均比每次绘制椭圆要慢。

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

}
java swing java-2d
1个回答
0
投票

我使用了

BufferedImage
而不是 VolatileImage,时间从 40 下降到 4:

import java.awt.*;
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.*;
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 MRE1 {

    private static final int OVAL_SIZE = 4;
    private static final int NUM_STARS = 150;
    private static final boolean useImages = true;

    private static VolatileImage volatileImage;
    private static BufferedImage bufferedImage;

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

        createVolatileImage(frame);

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

        bufferedImage = new BufferedImage(OVAL_SIZE, OVAL_SIZE, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = bufferedImage.createGraphics();
        g2d.setColor( Color.WHITE );
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.fillOval(0, 0, OVAL_SIZE, OVAL_SIZE);
        g2d.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;
                g2.setColor(Color.BLACK);
                g2.fillRect(0, 0, getWidth(), getHeight());

                Random rand = new Random();

                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);
                        g2.drawImage(bufferedImage, 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);
                }
            }
        };
        contentPane.setBackground( Color.BLACK );
        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());
            }
        });
    }

}

还摆脱了 RepaintManager,因为我认为不需要它。

我使用的是使用设备兼容的 VolatileImages,使用 3 种变体中的一种来避免使用 BITMASK 透明度进行缩放

如果这意味着您不希望图像在使用 150 的缩放因子时从 4 像素缩放到 6 像素,那么也许您可以使用

NoScalingIcon
。查看:https://stackoverflow.com/a/65742492/131872

您应该能够使用您的图像创建图标,然后将图标绘制到 BufferedImage,图像仍然是 4 像素。

© www.soinside.com 2019 - 2024. All rights reserved.