Java 的 g.drawImage 在 Windows 上比 Linux 上花费的时间明显更长

标签 java linux windows swing drawimage

对于一个类项目,我和其他一些人正在开发一个等距游戏。所有内容都使用缓冲图像在一个 JPanel 中绘制。每件艺术品都是用 Fireworks 完成的,并保存为 .png。

在 Linux 上,每个游戏周期重绘 map 所需的时间约为 3 毫秒。在 Windows(以及 OSx)上,大约为 100 毫秒,峰值为 500 毫秒。

在 4 种不同的计算机(从典型笔记本电脑到 i7-3770K + 660 游戏机)上观察到了这种效果。发生这种情况时,CPU 使用率约为 10-20%,程序的 RAM 使用率约为 1GB。这个问题在网上查了很多地方都没有结果,连我们负责项目的科长也很困惑。有什么想法吗?

这里是JPanel中的paint组件和setEntityImage方法。您可以在paintComponent 的底部看到时间戳的提取位置。 setEntityImage 每 500 毫秒设置一次新的 BufferedImage,该新的 BufferedImage 在传递到此 JPanel 之前在另一个线程上预先绘制。我包含该代码是因为我也很好奇这是否可能是线程问题。

此代码每 500 毫秒调用一次,包含一个“游戏刻度”,它绘制图像(全部预加载在静态单例类中)。

    BufferedImage entityImg = ImageUtil.getFromGraphics(map.getWidth() * TILE_WIDTH
            + TILE_WIDTH, map.getHeight() * TILE_HEIGHT + TILE_HEIGHT);

    Graphics2D g2 = (Graphics2D) entityImg.getGraphics();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);

    for (int i = 0; i < map.getHeight(); i++)
    {
        for (int j = 0; j < map.getWidth(); j++)
        {
            try
            {
                int x, y;
                x = (j * TILE_WIDTH_HALF - i * TILE_WIDTH_HALF)
                        + map.getWidth() * TILE_WIDTH_HALF;
                y = j * TILE_HEIGHT_HALF + i * TILE_HEIGHT_HALF;

                Entity e = map.getTile(i, j).getEntity();

                if (e != null)
                {
                    g2.drawImage(e.getImage(), x, y, 32, 32, null);
                }
            }
            catch (Exception e)
            {
            }
        }
    }

    panel.setEntityImage(entityImg);

此代码位于我们的 MapPanel 中,它扩展了 JPanel。

public void setEntityImage(BufferedImage entityImg)
{
    this.entityImg = entityImg;
}

@Override
public void paintComponent(Graphics g)
{
    super.paintComponent(g);

    Graphics g2 = (Graphics2D) g;

    g2.fillRect(0, 0, tileImg.getWidth(), tileImg.getHeight());

    // Draw tiles
    g2.drawImage(tileImg, 0, 0, null);

    // Draw box over square mouse is hovering over
    if (hover)
    {
        g2.setColor(new Color(255, 255, 255, 128));
        int screenX = (this.x * 16 - this.y * 16) + Window.WIDTH * 16;
        int screenY = (this.x * 8 + this.y * 8) + 16;

        g2.drawLine(screenX, screenY + 8, screenX + 16, screenY);
        g2.drawLine(screenX + 16, screenY, screenX + 32, screenY + 8);
        g2.drawLine(screenX, screenY + 8, screenX + 16, screenY + 16);
        g2.drawLine(screenX + 16, screenY + 16, screenX + 32, screenY + 8);
    }

    long time = System.currentTimeMillis();
    g2.drawImage(entityImg, 0, 0, null);
    System.out.println(System.currentTimeMillis() - time);
}

最佳答案

正在进行某种内部优化缓存,具体细节我不知道。本质上,如果 BufferedImage 未更新,它将在 0 毫秒内绘制。

我做了两次优化,不确定是否单独工作,但它对性能产生了影响,但对时间延迟没有影响(即,当我拖动 map 时,它明显更好)

第一个是在Window#tickUpdate中。相反,每 500 毫秒创建一个新的 BufferedImage,这只是重复使用单个 BufferedImage,仅在没有先前实例或大小已更改的情况下创建新实例...

public void tickUpdate(Map map) {
    int width = map.getWidth() * TILE_WIDTH + TILE_WIDTH;
    int height = map.getHeight() * TILE_HEIGHT + TILE_HEIGHT;
    if (entityImg == null || entityImg.getWidth() != width || entityImg.getHeight() != height) {
        entityImg = ImageUtil.getFromGraphics(width, height);
    }

    Graphics2D g2 = (Graphics2D) entityImg.getGraphics();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);

    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
    g2.setBackground(new Color(255, 255, 255, 0));
    g2.clearRect(0, 0, width, height);
    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));

    for (int i = 0; i < map.getHeight(); i++) {
        for (int j = 0; j < map.getWidth(); j++) {

            Entity e = map.getTile(i, j).getEntity();

            if (e != null) {
                int x, y;
                x = (j * TILE_WIDTH_HALF - i * TILE_WIDTH_HALF)
                        + map.getWidth() * TILE_WIDTH_HALF;
                y = j * TILE_HEIGHT_HALF + i * TILE_HEIGHT_HALF;
                g2.drawImage(e.getImage(), x, y, 32, 32, null);
            }
        }
    }
    g2.dispose();

    panel.setEntityImage(entityImg);
    panel.repaint();
}

由于 BufferedImage 可以在绘制时进行更新,因此在 MapPanel 中,我创建了另一个 BufferedImage,在其上绘制了“实体”图像。

public void setEntityImage(BufferedImage img) {
    entityLock.lock();
    try {
        if (entityImg == null || entityImg.getWidth() != img.getWidth() || entityImg.getHeight() != img.getHeight()) {
            entityImg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
        }
        Graphics2D g2d = entityImg.createGraphics();
        g2d.drawImage(img, 0, 0, null);
        g2d.dispose();
    } finally {
        entityLock.unlock();
    }
}

entitytLock 只是一个 ReentrantLock,我用它来阻止 paintComponent 方法在更新时尝试绘制 BufferedImage

这不会改变时间,但在视觉上变得更好......

另一种方法是使用 VolatileImage...

关于Java 的 g.drawImage 在 Windows 上比 Linux 上花费的时间明显更长,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23508187/

相关文章:

linux - SCP:尝试从 Windows 复制到 Linux 服务器时找不到文件

java - 无法从 Java 执行 Linux 命令

c++ - 如何在 VC++ 2005 中使用 OpenProcessToken、AdjustTokenPrivileges 和 GetExitCodeProcess

c# - 找不到 C++ dll 而 C# dll 都找到了(并且在我的电脑上工作)

Java Stream 关​​闭方法的不明确行为

java - ubuntu 上的 Jira 安装问题

java - Websphere 应用程序服务器和 Websphere 商务服务器之间的区别

linux - Apache 背后的 Jira、Tomcat 和 JBoss

c++ - 操作 Windows 注册表

java - MapView 是抽象的 - 无法实例化