You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swing图形项目帧率(FPS)优化及VolatileImage重绘帧率过低问题求助

Fixing Low FPS with VolatileImage in Swing

Hey there, let's tackle your Swing FPS bottleneck. It's frustrating when even drawing two pixels caps your frame rate at 250-280 FPS while direct drawing gives way higher numbers but unbearable flicker. Let's break down the issues in your code and fix them for smooth, high-performance rendering.

Key Issues in Your Current Implementation

  1. Unnecessary VolatileImage Recreations
    In your render() method, you're recreating the VolatileImage every single frame with this line:

    vImg = gc.createCompatibleVolatileImage(frame.getWidth(), frame.getHeight());
    

    Creating a VolatileImage is expensive—you should only rebuild it when vImg.validate(gc) returns VolatileImage.IMAGE_RESTORED or VolatileImage.IMAGE_INCOMPATIBLE, not every time it returns IMAGE_OK. This constant recreation is killing your performance.

  2. Using getGraphics() for On-Screen Drawing
    Calling frame.getGraphics() or frame.getContentPane().getGraphics() to draw your off-screen buffer is not the right approach for Swing. This method gives you a temporary graphics context that can be invalidated at any time, and it bypasses Swing's built-in double buffering mechanisms, leading to unnecessary overhead and potential instability.

  3. No Proper Rendering Synchronization
    Your render loop runs in a raw thread without syncing to Java2D's optimized pipeline. While you're using a separate thread for rendering, the way you're updating the screen isn't aligned with how Swing handles graphics under the hood.

Solutions to Boost FPS

Java2D's BufferStrategy is designed exactly for this kind of high-performance off-screen rendering. It handles VolatileImage management under the hood and provides direct access to the display device's buffers, reducing overhead significantly.

Here's a revised Window class using BufferStrategy:

import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.function.Consumer;

public class Window {
    private JFrame frame;
    private Canvas canvas;
    private BufferStrategy bufferStrategy;
    private int canvasWidth, canvasHeight;
    private String title;
    private Color bgColor;
    private boolean isOpen = true;
    private long lastFPSCheck;
    private int totalFrames;
    private int currentFPS;

    public void init(int width, int height, String title, Color bgColor, Consumer<Void> onDestroy) {
        this.canvasWidth = width;
        this.canvasHeight = height;
        this.title = title;
        this.bgColor = bgColor;

        frame = new JFrame(title);
        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                isOpen = false;
                onDestroy.accept(null);
            }

            @Override
            public void windowResized(WindowEvent e) {
                canvas.setSize(frame.getWidth(), frame.getHeight());
                createBufferStrategy();
            }
        });

        canvas = new Canvas();
        canvas.setSize(width, height);
        canvas.setIgnoreRepaint(true); // Disable Swing's automatic repaint
        frame.add(canvas);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

        createBufferStrategy();
        lastFPSCheck = System.nanoTime();
    }

    private void createBufferStrategy() {
        if (bufferStrategy != null) {
            bufferStrategy.dispose();
        }
        canvas.createBufferStrategy(2); // Double buffering
        bufferStrategy = canvas.getBufferStrategy();
    }

    public int render() {
        do {
            do {
                Graphics2D g = null;
                try {
                    g = (Graphics2D) bufferStrategy.getDrawGraphics();
                    g.setColor(bgColor);
                    g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
                    // Draw your pixels here (or pass this Graphics2D to GraphicsManager)
                } finally {
                    if (g != null) {
                        g.dispose();
                    }
                }
            } while (bufferStrategy.contentsRestored());
            bufferStrategy.show();
        } while (bufferStrategy.contentsLost());

        // FPS calculation
        totalFrames++;
        long now = System.nanoTime();
        if (now > lastFPSCheck + 1_000_000_000) {
            currentFPS = totalFrames;
            totalFrames = 0;
            lastFPSCheck = now;
            frame.setTitle(title + "[FPS: " + currentFPS + "]");
        }
        return currentFPS;
    }

    public boolean isOpen() {
        return isOpen;
    }

    public Graphics2D getRenderGraphics() {
        return (Graphics2D) bufferStrategy.getDrawGraphics();
    }
}

And update your Main class to use this new window:

import java.awt.Color;
import java.awt.Toolkit;

public class Main {
    private static void onDestroy() {
        System.exit(0);
    }

    public static void main(String[] args) {
        Window window = new Window();
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        window.init(
            screenSize.width,
            screenSize.height,
            "High FPS Swing Demo",
            new Color(0, 0, 0),
            destroy -> onDestroy()
        );

        Thread renderThread = new Thread(() -> {
            while (window.isOpen()) {
                int fps = window.render();
                // Draw pixels using the buffer's graphics context
                try (Graphics2D g = window.getRenderGraphics()) {
                    g.setColor(new Color(255, 0, 0));
                    g.fillRect(screenSize.width/2, screenSize.height/2, 1, 1);
                    g.setColor(new Color(255, 255, 255));
                    g.fillRect(0, 0, 1, 1);
                }
                System.out.println(fps);
            }
        });
        renderThread.setPriority(Thread.MAX_PRIORITY);
        renderThread.start();
    }
}

Option 2: Fix Your VolatileImage Usage

If you want to stick with VolatileImage, here's how to optimize your existing code:

  • Only recreate the VolatileImage when it's actually invalidated:
    if (vImg.validate(gc) != VolatileImage.IMAGE_OK) {
        vImg = gc.createCompatibleVolatileImage(frame.getWidth(), frame.getHeight());
    }
    
  • Replace frame.getGraphics() with a custom JPanel that overrides paintComponent, leveraging Swing's built-in double buffering:
    private class RenderPanel extends JPanel {
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.drawImage(vImg, 0, 0, getWidth(), getHeight(), null);
        }
    }
    
    Add this panel to your JFrame instead of using the content pane directly, and call repaint() after updating your VolatileImage.

Why This Works

  • BufferStrategy manages off-screen buffers efficiently, avoiding the overhead of constant VolatileImage recreation and providing direct access to the display pipeline.
  • Disabling Swing's automatic repaint for the Canvas prevents conflicts with our custom rendering loop.
  • Properly handling buffer restoration/loss ensures we don't waste cycles on invalid buffers.

With these changes, you should see a massive jump in FPS—easily hitting 600+ FPS (or even higher, depending on your hardware) while maintaining flicker-free rendering.

内容的提问来源于stack exchange,提问作者FallenAngel7882

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 13:18:15